зеркало из https://github.com/mozilla/gecko-dev.git
3598 строки
114 KiB
C++
3598 строки
114 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=4 sw=2 et cindent: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nsSocketTransport2.h"
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/SyncRunnable.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "nsIOService.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsNetSegmentUtils.h"
|
|
#include "nsNetAddr.h"
|
|
#include "nsTransportUtils.h"
|
|
#include "nsProxyInfo.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "plstr.h"
|
|
#include "prerr.h"
|
|
#include "IOActivityMonitor.h"
|
|
#include "NSSErrorsService.h"
|
|
#include "mozilla/dom/ToJSValue.h"
|
|
#include "mozilla/net/NeckoChild.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsSocketProviderService.h"
|
|
#include "nsISocketProvider.h"
|
|
#include "nsISSLSocketControl.h"
|
|
#include "nsIPipe.h"
|
|
#include "nsIClassInfoImpl.h"
|
|
#include "nsURLHelper.h"
|
|
#include "nsIDNSService.h"
|
|
#include "nsIDNSRecord.h"
|
|
#include "nsIDNSByTypeRecord.h"
|
|
#include "nsICancelable.h"
|
|
#include "NetworkDataCountLayer.h"
|
|
#include "QuicSocketControl.h"
|
|
#include "TCPFastOpenLayer.h"
|
|
#include <algorithm>
|
|
#include "sslexp.h"
|
|
#include "mozilla/net/SSLTokensCache.h"
|
|
#include "mozilla/StaticPrefs_network.h"
|
|
|
|
#include "nsPrintfCString.h"
|
|
#include "xpcpublic.h"
|
|
|
|
#if defined(FUZZING)
|
|
# include "FuzzyLayer.h"
|
|
# include "FuzzySecurityInfo.h"
|
|
# include "mozilla/StaticPrefs_fuzzing.h"
|
|
#endif
|
|
|
|
#if defined(XP_WIN)
|
|
# include "ShutdownLayer.h"
|
|
#endif
|
|
|
|
/* Following inclusions required for keepalive config not supported by NSPR. */
|
|
#include "private/pprio.h"
|
|
#if defined(XP_WIN)
|
|
# include <winsock2.h>
|
|
# include <mstcpip.h>
|
|
#elif defined(XP_UNIX)
|
|
# include <errno.h>
|
|
# include <netinet/tcp.h>
|
|
#endif
|
|
/* End keepalive config inclusions. */
|
|
|
|
#define SUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 0
|
|
#define UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS 1
|
|
#define SUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 2
|
|
#define UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS 3
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
class nsSocketEvent : public Runnable {
|
|
public:
|
|
nsSocketEvent(nsSocketTransport* transport, uint32_t type,
|
|
nsresult status = NS_OK, nsISupports* param = nullptr)
|
|
: Runnable("net::nsSocketEvent"),
|
|
mTransport(transport),
|
|
mType(type),
|
|
mStatus(status),
|
|
mParam(param) {}
|
|
|
|
NS_IMETHOD Run() override {
|
|
mTransport->OnSocketEvent(mType, mStatus, mParam);
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
RefPtr<nsSocketTransport> mTransport;
|
|
|
|
uint32_t mType;
|
|
nsresult mStatus;
|
|
nsCOMPtr<nsISupports> mParam;
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//#define TEST_CONNECT_ERRORS
|
|
#ifdef TEST_CONNECT_ERRORS
|
|
# include <stdlib.h>
|
|
static PRErrorCode RandomizeConnectError(PRErrorCode code) {
|
|
//
|
|
// To test out these errors, load http://www.yahoo.com/. It should load
|
|
// correctly despite the random occurrence of these errors.
|
|
//
|
|
int n = rand();
|
|
if (n > RAND_MAX / 2) {
|
|
struct {
|
|
PRErrorCode err_code;
|
|
const char* err_name;
|
|
} errors[] = {
|
|
//
|
|
// These errors should be recoverable provided there is another
|
|
// IP address in mDNSRecord.
|
|
//
|
|
{PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR"},
|
|
{PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR"},
|
|
//
|
|
// This error will cause this socket transport to error out;
|
|
// however, if the consumer is HTTP, then the HTTP transaction
|
|
// should be restarted when this error occurs.
|
|
//
|
|
{PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR"},
|
|
};
|
|
n = n % (sizeof(errors) / sizeof(errors[0]));
|
|
code = errors[n].err_code;
|
|
SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name));
|
|
}
|
|
return code;
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) {
|
|
nsresult rv = NS_ERROR_FAILURE;
|
|
switch (errorCode) {
|
|
case PR_WOULD_BLOCK_ERROR:
|
|
rv = NS_BASE_STREAM_WOULD_BLOCK;
|
|
break;
|
|
case PR_CONNECT_ABORTED_ERROR:
|
|
case PR_CONNECT_RESET_ERROR:
|
|
rv = NS_ERROR_NET_RESET;
|
|
break;
|
|
case PR_END_OF_FILE_ERROR: // XXX document this correlation
|
|
rv = NS_ERROR_NET_INTERRUPT;
|
|
break;
|
|
case PR_CONNECT_REFUSED_ERROR:
|
|
// We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We
|
|
// could get better diagnostics by adding distinct XPCOM error codes for
|
|
// each of these, but there are a lot of places in Gecko that check
|
|
// specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to
|
|
// be checked.
|
|
case PR_NETWORK_UNREACHABLE_ERROR:
|
|
case PR_HOST_UNREACHABLE_ERROR:
|
|
case PR_ADDRESS_NOT_AVAILABLE_ERROR:
|
|
// Treat EACCES as a soft error since (at least on Linux) connect() returns
|
|
// EACCES when an IPv6 connection is blocked by a firewall. See bug 270784.
|
|
case PR_NO_ACCESS_RIGHTS_ERROR:
|
|
rv = NS_ERROR_CONNECTION_REFUSED;
|
|
break;
|
|
case PR_ADDRESS_NOT_SUPPORTED_ERROR:
|
|
rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
|
|
break;
|
|
case PR_IO_TIMEOUT_ERROR:
|
|
case PR_CONNECT_TIMEOUT_ERROR:
|
|
rv = NS_ERROR_NET_TIMEOUT;
|
|
break;
|
|
case PR_OUT_OF_MEMORY_ERROR:
|
|
// These really indicate that the descriptor table filled up, or that the
|
|
// kernel ran out of network buffers - but nobody really cares which part of
|
|
// the system ran out of memory.
|
|
case PR_PROC_DESC_TABLE_FULL_ERROR:
|
|
case PR_SYS_DESC_TABLE_FULL_ERROR:
|
|
case PR_INSUFFICIENT_RESOURCES_ERROR:
|
|
rv = NS_ERROR_OUT_OF_MEMORY;
|
|
break;
|
|
case PR_ADDRESS_IN_USE_ERROR:
|
|
rv = NS_ERROR_SOCKET_ADDRESS_IN_USE;
|
|
break;
|
|
// These filename-related errors can arise when using Unix-domain sockets.
|
|
case PR_FILE_NOT_FOUND_ERROR:
|
|
rv = NS_ERROR_FILE_NOT_FOUND;
|
|
break;
|
|
case PR_IS_DIRECTORY_ERROR:
|
|
rv = NS_ERROR_FILE_IS_DIRECTORY;
|
|
break;
|
|
case PR_LOOP_ERROR:
|
|
rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
|
|
break;
|
|
case PR_NAME_TOO_LONG_ERROR:
|
|
rv = NS_ERROR_FILE_NAME_TOO_LONG;
|
|
break;
|
|
case PR_NO_DEVICE_SPACE_ERROR:
|
|
rv = NS_ERROR_FILE_NO_DEVICE_SPACE;
|
|
break;
|
|
case PR_NOT_DIRECTORY_ERROR:
|
|
rv = NS_ERROR_FILE_NOT_DIRECTORY;
|
|
break;
|
|
case PR_READ_ONLY_FILESYSTEM_ERROR:
|
|
rv = NS_ERROR_FILE_READ_ONLY;
|
|
break;
|
|
case PR_BAD_ADDRESS_ERROR:
|
|
rv = NS_ERROR_UNKNOWN_HOST;
|
|
break;
|
|
default:
|
|
if (psm::IsNSSErrorCode(errorCode)) {
|
|
rv = psm::GetXPCOMFromNSSError(errorCode);
|
|
}
|
|
break;
|
|
|
|
// NSPR's socket code can return these, but they're not worth breaking out
|
|
// into their own error codes, distinct from NS_ERROR_FAILURE:
|
|
//
|
|
// PR_BAD_DESCRIPTOR_ERROR
|
|
// PR_INVALID_ARGUMENT_ERROR
|
|
// PR_NOT_SOCKET_ERROR
|
|
// PR_NOT_TCP_SOCKET_ERROR
|
|
// These would indicate a bug internal to the component.
|
|
//
|
|
// PR_PROTOCOL_NOT_SUPPORTED_ERROR
|
|
// This means that we can't use the given "protocol" (like
|
|
// IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As
|
|
// above, this indicates an internal bug.
|
|
//
|
|
// PR_IS_CONNECTED_ERROR
|
|
// This indicates that we've applied a system call like 'bind' or
|
|
// 'connect' to a socket that is already connected. The socket
|
|
// components manage each file descriptor's state, and in some cases
|
|
// handle this error result internally. We shouldn't be returning
|
|
// this to our callers.
|
|
//
|
|
// PR_IO_ERROR
|
|
// This is so vague that NS_ERROR_FAILURE is just as good.
|
|
}
|
|
SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%" PRIx32 "]\n", errorCode,
|
|
static_cast<uint32_t>(rv)));
|
|
return rv;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// socket input stream impl
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsSocketInputStream::nsSocketInputStream(nsSocketTransport* trans)
|
|
: mTransport(trans),
|
|
mReaderRefCnt(0),
|
|
mCondition(NS_OK),
|
|
mCallbackFlags(0),
|
|
mByteCount(0) {}
|
|
|
|
// called on the socket transport thread...
|
|
//
|
|
// condition : failure code if socket has been closed
|
|
//
|
|
void nsSocketInputStream::OnSocketReady(nsresult condition) {
|
|
SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%" PRIx32 "]\n",
|
|
this, static_cast<uint32_t>(condition)));
|
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
|
|
nsCOMPtr<nsIInputStreamCallback> callback;
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
|
|
// update condition, but be careful not to erase an already
|
|
// existing error condition.
|
|
if (NS_SUCCEEDED(mCondition)) mCondition = condition;
|
|
|
|
// ignore event if only waiting for closure and not closed.
|
|
if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
|
|
callback = std::move(mCallback);
|
|
mCallbackFlags = 0;
|
|
}
|
|
}
|
|
|
|
if (callback) callback->OnInputStreamReady(this);
|
|
}
|
|
|
|
NS_IMPL_QUERY_INTERFACE(nsSocketInputStream, nsIInputStream,
|
|
nsIAsyncInputStream)
|
|
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
nsSocketInputStream::AddRef() {
|
|
++mReaderRefCnt;
|
|
return mTransport->AddRef();
|
|
}
|
|
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
nsSocketInputStream::Release() {
|
|
if (--mReaderRefCnt == 0) Close();
|
|
return mTransport->Release();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketInputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketInputStream::Available(uint64_t* avail) {
|
|
SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n", this));
|
|
|
|
*avail = 0;
|
|
|
|
PRFileDesc* fd;
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
|
|
if (NS_FAILED(mCondition)) return mCondition;
|
|
|
|
fd = mTransport->GetFD_Locked();
|
|
if (!fd) return NS_OK;
|
|
}
|
|
|
|
// cannot hold lock while calling NSPR. (worried about the fact that PSM
|
|
// synchronously proxies notifications over to the UI thread, which could
|
|
// mistakenly try to re-enter this code.)
|
|
int32_t n = PR_Available(fd);
|
|
|
|
// PSM does not implement PR_Available() so do a best approximation of it
|
|
// with MSG_PEEK
|
|
if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) {
|
|
char c;
|
|
|
|
n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
|
|
SOCKET_LOG(
|
|
("nsSocketInputStream::Available [this=%p] "
|
|
"using PEEK backup n=%d]\n",
|
|
this, n));
|
|
}
|
|
|
|
nsresult rv;
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
|
|
mTransport->ReleaseFD_Locked(fd);
|
|
|
|
if (n >= 0)
|
|
*avail = n;
|
|
else {
|
|
PRErrorCode code = PR_GetError();
|
|
if (code == PR_WOULD_BLOCK_ERROR) return NS_OK;
|
|
mCondition = ErrorAccordingToNSPR(code);
|
|
}
|
|
rv = mCondition;
|
|
}
|
|
if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketInputStream::Read(char* buf, uint32_t count, uint32_t* countRead) {
|
|
SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count));
|
|
|
|
*countRead = 0;
|
|
|
|
PRFileDesc* fd = nullptr;
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
|
|
if (NS_FAILED(mCondition))
|
|
return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition;
|
|
|
|
fd = mTransport->GetFD_Locked();
|
|
if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
|
|
}
|
|
|
|
SOCKET_LOG((" calling PR_Read [count=%u]\n", count));
|
|
|
|
// cannot hold lock while calling NSPR. (worried about the fact that PSM
|
|
// synchronously proxies notifications over to the UI thread, which could
|
|
// mistakenly try to re-enter this code.)
|
|
int32_t n = PR_Read(fd, buf, count);
|
|
|
|
SOCKET_LOG((" PR_Read returned [n=%d]\n", n));
|
|
|
|
nsresult rv = NS_OK;
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
|
|
#ifdef ENABLE_SOCKET_TRACING
|
|
if (n > 0) mTransport->TraceInBuf(buf, n);
|
|
#endif
|
|
|
|
mTransport->ReleaseFD_Locked(fd);
|
|
|
|
if (n > 0)
|
|
mByteCount += (*countRead = n);
|
|
else if (n < 0) {
|
|
PRErrorCode code = PR_GetError();
|
|
if (code == PR_WOULD_BLOCK_ERROR) return NS_BASE_STREAM_WOULD_BLOCK;
|
|
mCondition = ErrorAccordingToNSPR(code);
|
|
}
|
|
rv = mCondition;
|
|
}
|
|
if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
|
|
|
|
// only send this notification if we have indeed read some data.
|
|
// see bug 196827 for an example of why this is important.
|
|
if (n > 0) mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
|
|
uint32_t count, uint32_t* countRead) {
|
|
// socket stream is unbuffered
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketInputStream::IsNonBlocking(bool* nonblocking) {
|
|
*nonblocking = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketInputStream::CloseWithStatus(nsresult reason) {
|
|
SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%" PRIx32
|
|
"]\n",
|
|
this, static_cast<uint32_t>(reason)));
|
|
|
|
// may be called from any thread
|
|
|
|
nsresult rv;
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
|
|
if (NS_SUCCEEDED(mCondition))
|
|
rv = mCondition = reason;
|
|
else
|
|
rv = NS_OK;
|
|
}
|
|
if (NS_FAILED(rv)) mTransport->OnInputClosed(rv);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketInputStream::AsyncWait(nsIInputStreamCallback* callback, uint32_t flags,
|
|
uint32_t amount, nsIEventTarget* target) {
|
|
SOCKET_LOG(("nsSocketInputStream::AsyncWait [this=%p]\n", this));
|
|
|
|
bool hasError = false;
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
|
|
if (callback && target) {
|
|
//
|
|
// build event proxy
|
|
//
|
|
mCallback = NS_NewInputStreamReadyEvent("nsSocketInputStream::AsyncWait",
|
|
callback, target);
|
|
} else
|
|
mCallback = callback;
|
|
mCallbackFlags = flags;
|
|
|
|
hasError = NS_FAILED(mCondition);
|
|
} // unlock mTransport->mLock
|
|
|
|
if (hasError) {
|
|
// OnSocketEvent will call OnInputStreamReady with an error code after
|
|
// going through the event loop. We do this because most socket callers
|
|
// do not expect AsyncWait() to synchronously execute the OnInputStreamReady
|
|
// callback.
|
|
mTransport->PostEvent(nsSocketTransport::MSG_INPUT_PENDING);
|
|
} else {
|
|
mTransport->OnInputPending();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// socket output stream impl
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsSocketOutputStream::nsSocketOutputStream(nsSocketTransport* trans)
|
|
: mTransport(trans),
|
|
mWriterRefCnt(0),
|
|
mCondition(NS_OK),
|
|
mCallbackFlags(0),
|
|
mByteCount(0) {}
|
|
|
|
// called on the socket transport thread...
|
|
//
|
|
// condition : failure code if socket has been closed
|
|
//
|
|
void nsSocketOutputStream::OnSocketReady(nsresult condition) {
|
|
SOCKET_LOG(("nsSocketOutputStream::OnSocketReady [this=%p cond=%" PRIx32
|
|
"]\n",
|
|
this, static_cast<uint32_t>(condition)));
|
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
|
|
nsCOMPtr<nsIOutputStreamCallback> callback;
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
|
|
// update condition, but be careful not to erase an already
|
|
// existing error condition.
|
|
if (NS_SUCCEEDED(mCondition)) mCondition = condition;
|
|
|
|
// ignore event if only waiting for closure and not closed.
|
|
if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) {
|
|
callback = std::move(mCallback);
|
|
mCallbackFlags = 0;
|
|
}
|
|
}
|
|
|
|
if (callback) callback->OnOutputStreamReady(this);
|
|
}
|
|
|
|
NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream, nsIOutputStream,
|
|
nsIAsyncOutputStream)
|
|
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
nsSocketOutputStream::AddRef() {
|
|
++mWriterRefCnt;
|
|
return mTransport->AddRef();
|
|
}
|
|
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
nsSocketOutputStream::Release() {
|
|
if (--mWriterRefCnt == 0) Close();
|
|
return mTransport->Release();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketOutputStream::Close() { return CloseWithStatus(NS_BASE_STREAM_CLOSED); }
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketOutputStream::Flush() { return NS_OK; }
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketOutputStream::Write(const char* buf, uint32_t count,
|
|
uint32_t* countWritten) {
|
|
SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n", this, count));
|
|
|
|
*countWritten = 0;
|
|
|
|
// A write of 0 bytes can be used to force the initial SSL handshake, so do
|
|
// not reject that.
|
|
|
|
PRFileDesc* fd = nullptr;
|
|
bool fastOpenInProgress;
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
|
|
if (NS_FAILED(mCondition)) return mCondition;
|
|
|
|
fd = mTransport->GetFD_LockedAlsoDuringFastOpen();
|
|
if (!fd) return NS_BASE_STREAM_WOULD_BLOCK;
|
|
|
|
fastOpenInProgress = mTransport->FastOpenInProgress();
|
|
}
|
|
|
|
if (fastOpenInProgress) {
|
|
// If we are in the fast open phase, we should not write more data
|
|
// than TCPFastOpenLayer can accept. If we write more data, this data
|
|
// will be buffered in tls and we want to avoid that.
|
|
uint32_t availableSpace = TCPFastOpenGetBufferSizeLeft(fd);
|
|
count = (count > availableSpace) ? availableSpace : count;
|
|
if (!count) {
|
|
{
|
|
MutexAutoLock lock(mTransport->mLock);
|
|
mTransport->ReleaseFD_Locked(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.
|
|
// During a fast open we are actually not sending data, the data will be
|
|
// only buffered in the TCPFastOpenLayer. Therefore we will call
|
|
// SendStatus(NS_NET_STATUS_SENDING_TO) when we really send data (i.e. when
|
|
// TCPFastOpenFinish is called.
|
|
if ((n > 0) && !fastOpenInProgress) {
|
|
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()
|
|
: mPort(0),
|
|
mProxyPort(0),
|
|
mOriginPort(0),
|
|
mProxyTransparent(false),
|
|
mProxyTransparentResolvesHost(false),
|
|
mHttpsProxy(false),
|
|
mConnectionFlags(0),
|
|
mResetFamilyPreference(false),
|
|
mTlsFlags(0),
|
|
mReuseAddrPort(false),
|
|
mState(STATE_CLOSED),
|
|
mAttached(false),
|
|
mInputClosed(true),
|
|
mOutputClosed(true),
|
|
mResolving(false),
|
|
mEchConfigUsed(false),
|
|
mResolvedByTRR(false),
|
|
mNetAddrIsSet(false),
|
|
mSelfAddrIsSet(false),
|
|
mLock("nsSocketTransport.mLock"),
|
|
mFD(this),
|
|
mFDref(0),
|
|
mFDconnected(false),
|
|
mFDFastOpenInProgress(false),
|
|
mSocketTransportService(gSocketTransportService),
|
|
mInput(this),
|
|
mOutput(this),
|
|
mLingerPolarity(false),
|
|
mLingerTimeout(0),
|
|
mQoSBits(0x00),
|
|
mKeepaliveEnabled(false),
|
|
mKeepaliveIdleTimeS(-1),
|
|
mKeepaliveRetryIntervalS(-1),
|
|
mKeepaliveProbeCount(-1),
|
|
mFastOpenCallback(nullptr),
|
|
mFastOpenLayerHasBufferedData(false),
|
|
mFastOpenStatus(TFO_NOT_SET),
|
|
mFirstRetryError(NS_OK),
|
|
mDoNotRetryToConnect(false),
|
|
mUsingQuic(false) {
|
|
this->mNetAddr.raw.family = 0;
|
|
this->mNetAddr.inet = {};
|
|
this->mSelfAddr.raw.family = 0;
|
|
this->mSelfAddr.inet = {};
|
|
SOCKET_LOG(("creating nsSocketTransport @%p\n", this));
|
|
|
|
mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout
|
|
mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout
|
|
}
|
|
|
|
nsSocketTransport::~nsSocketTransport() {
|
|
SOCKET_LOG(("destroying nsSocketTransport @%p\n", this));
|
|
}
|
|
|
|
nsresult nsSocketTransport::Init(const nsTArray<nsCString>& types,
|
|
const nsACString& host, uint16_t port,
|
|
const nsACString& hostRoute,
|
|
uint16_t portRoute,
|
|
nsIProxyInfo* givenProxyInfo) {
|
|
nsCOMPtr<nsProxyInfo> proxyInfo;
|
|
if (givenProxyInfo) {
|
|
proxyInfo = do_QueryInterface(givenProxyInfo);
|
|
NS_ENSURE_ARG(proxyInfo);
|
|
}
|
|
|
|
// init socket type info
|
|
|
|
mOriginHost = host;
|
|
mOriginPort = port;
|
|
if (!hostRoute.IsEmpty()) {
|
|
mHost = hostRoute;
|
|
mPort = portRoute;
|
|
} else {
|
|
mHost = host;
|
|
mPort = port;
|
|
}
|
|
|
|
// 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++]);
|
|
|
|
// quic does not have a socketProvider.
|
|
if (!mTypes[i].EqualsLiteral("quic")) {
|
|
nsCOMPtr<nsISocketProvider> provider;
|
|
rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("no registered socket provider");
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// note if socket type corresponds to a transparent proxy
|
|
// XXX don't hardcode SOCKS here (use proxy info's flags instead).
|
|
if (mTypes[i].EqualsLiteral("socks") || mTypes[i].EqualsLiteral("socks4")) {
|
|
mProxyTransparent = true;
|
|
|
|
if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) {
|
|
// we want the SOCKS layer to send the hostname
|
|
// and port to the proxy and let it do the DNS.
|
|
mProxyTransparentResolvesHost = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
#if defined(XP_UNIX)
|
|
nsresult nsSocketTransport::InitWithFilename(const char* filename) {
|
|
return InitWithName(filename, strlen(filename));
|
|
}
|
|
|
|
nsresult nsSocketTransport::InitWithName(const char* name, size_t length) {
|
|
if (length > sizeof(mNetAddr.local.path) - 1) {
|
|
return NS_ERROR_FILE_NAME_TOO_LONG;
|
|
}
|
|
|
|
if (!name[0] && length > 1) {
|
|
// name is abstract address name that is supported on Linux only
|
|
# if defined(XP_LINUX)
|
|
mHost.Assign(name + 1, length - 1);
|
|
# else
|
|
return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED;
|
|
# endif
|
|
} else {
|
|
// The name isn't abstract socket address. So this is Unix domain
|
|
// socket that has file path.
|
|
mHost.Assign(name, length);
|
|
}
|
|
mPort = 0;
|
|
|
|
mNetAddr.local.family = AF_LOCAL;
|
|
memcpy(mNetAddr.local.path, name, length);
|
|
mNetAddr.local.path[length] = '\0';
|
|
mNetAddrIsSet = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
#endif
|
|
|
|
nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc* fd,
|
|
const NetAddr* addr) {
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
NS_ASSERTION(!mFD.IsInitialized(), "already initialized");
|
|
|
|
char buf[kNetAddrMaxCStrBufSize];
|
|
addr->ToStringBuffer(buf, sizeof(buf));
|
|
mHost.Assign(buf);
|
|
|
|
uint16_t port;
|
|
if (addr->raw.family == AF_INET)
|
|
port = addr->inet.port;
|
|
else if (addr->raw.family == AF_INET6)
|
|
port = addr->inet6.port;
|
|
else
|
|
port = 0;
|
|
mPort = ntohs(port);
|
|
|
|
memcpy(&mNetAddr, addr, sizeof(NetAddr));
|
|
|
|
mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
|
|
mState = STATE_TRANSFERRING;
|
|
SetSocketName(fd);
|
|
mNetAddrIsSet = true;
|
|
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
|
|
mFD = fd;
|
|
mFDref = 1;
|
|
mFDconnected = true;
|
|
mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
|
|
}
|
|
|
|
// make sure new socket is non-blocking
|
|
PRSocketOptionData opt;
|
|
opt.option = PR_SockOpt_Nonblocking;
|
|
opt.value.non_blocking = true;
|
|
PR_SetSocketOption(fd, &opt);
|
|
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n",
|
|
this, mHost.get(), mPort));
|
|
|
|
// jump to InitiateSocket to get ourselves attached to the STS poll list.
|
|
return PostEvent(MSG_RETRY_INIT_SOCKET);
|
|
}
|
|
|
|
nsresult nsSocketTransport::InitWithConnectedSocket(PRFileDesc* aFD,
|
|
const NetAddr* aAddr,
|
|
nsISupports* aSecInfo) {
|
|
mSecInfo = aSecInfo;
|
|
return InitWithConnectedSocket(aFD, aAddr);
|
|
}
|
|
|
|
nsresult nsSocketTransport::PostEvent(uint32_t type, nsresult status,
|
|
nsISupports* param) {
|
|
SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%" PRIx32
|
|
" param=%p]\n",
|
|
this, type, static_cast<uint32_t>(status), param));
|
|
|
|
nsCOMPtr<nsIRunnable> event = new nsSocketEvent(this, type, status, param);
|
|
if (!event) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
void nsSocketTransport::SendStatus(nsresult status) {
|
|
SOCKET_LOG1(("nsSocketTransport::SendStatus [this=%p status=%" PRIx32 "]\n",
|
|
this, static_cast<uint32_t>(status)));
|
|
|
|
nsCOMPtr<nsITransportEventSink> sink;
|
|
uint64_t progress;
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
sink = mEventSink;
|
|
switch (status) {
|
|
case NS_NET_STATUS_SENDING_TO:
|
|
progress = mOutput.ByteCount();
|
|
// If Fast Open is used, we buffer some data in TCPFastOpenLayer,
|
|
// This data can be only tls data or application data as well.
|
|
// socketTransport should send status only if it really has sent
|
|
// application data. socketTransport cannot query transaction for
|
|
// that info but it can know if transaction has send data if
|
|
// mOutput.ByteCount() is > 0.
|
|
if (progress == 0) {
|
|
return;
|
|
}
|
|
break;
|
|
case NS_NET_STATUS_RECEIVING_FROM:
|
|
progress = mInput.ByteCount();
|
|
break;
|
|
default:
|
|
progress = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (sink) {
|
|
sink->OnTransportStatus(this, status, progress, -1);
|
|
}
|
|
}
|
|
|
|
nsresult nsSocketTransport::ResolveHost() {
|
|
SOCKET_LOG((
|
|
"nsSocketTransport::ResolveHost [this=%p %s:%d%s] "
|
|
"mProxyTransparentResolvesHost=%d\n",
|
|
this, SocketHost().get(), SocketPort(),
|
|
mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? " bypass cache" : "",
|
|
mProxyTransparentResolvesHost));
|
|
|
|
nsresult rv;
|
|
|
|
if (!mProxyHost.IsEmpty()) {
|
|
if (!mProxyTransparent || mProxyTransparentResolvesHost) {
|
|
#if defined(XP_UNIX)
|
|
MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
|
|
"Unix domain sockets can't be used with proxies");
|
|
#endif
|
|
// When not resolving mHost locally, we still want to ensure that
|
|
// it only contains valid characters. See bug 304904 for details.
|
|
// Sometimes the end host is not yet known and mHost is *
|
|
if (!net_IsValidHostName(mHost) && !mHost.EqualsLiteral("*")) {
|
|
SOCKET_LOG((" invalid hostname %s\n", mHost.get()));
|
|
return NS_ERROR_UNKNOWN_HOST;
|
|
}
|
|
}
|
|
if (mProxyTransparentResolvesHost) {
|
|
// Name resolution is done on the server side. Just pretend
|
|
// client resolution is complete, this will get picked up later.
|
|
// since we don't need to do DNS now, we bypass the resolving
|
|
// step by initializing mNetAddr to an empty address, but we
|
|
// must keep the port. The SOCKS IO layer will use the hostname
|
|
// we send it when it's created, rather than the empty address
|
|
// we send with the connect call.
|
|
mState = STATE_RESOLVING;
|
|
mNetAddr.raw.family = AF_INET;
|
|
mNetAddr.inet.port = htons(SocketPort());
|
|
mNetAddr.inet.ip = htonl(INADDR_ANY);
|
|
return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIDNSService> dns = nullptr;
|
|
auto initTask = [&dns]() { dns = do_GetService(kDNSServiceCID); };
|
|
if (!NS_IsMainThread()) {
|
|
// Forward to the main thread synchronously.
|
|
RefPtr<nsIThread> mainThread = do_GetMainThread();
|
|
if (!mainThread) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
SyncRunnable::DispatchToThread(
|
|
mainThread,
|
|
new SyncRunnable(NS_NewRunnableFunction(
|
|
"nsSocketTransport::ResolveHost->GetDNSService", initTask)));
|
|
} else {
|
|
initTask();
|
|
}
|
|
if (!dns) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mResolving = true;
|
|
|
|
uint32_t dnsFlags = 0;
|
|
if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE)
|
|
dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
|
|
if (mConnectionFlags & nsSocketTransport::REFRESH_CACHE)
|
|
dnsFlags = nsIDNSService::RESOLVE_REFRESH_CACHE;
|
|
if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6)
|
|
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
|
|
if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4)
|
|
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
|
|
if (mConnectionFlags & nsSocketTransport::DISABLE_TRR)
|
|
dnsFlags |= nsIDNSService::RESOLVE_DISABLE_TRR;
|
|
|
|
if (mConnectionFlags & nsSocketTransport::USE_IP_HINT_ADDRESS) {
|
|
dnsFlags |= nsIDNSService::RESOLVE_IP_HINT;
|
|
}
|
|
|
|
dnsFlags |= nsIDNSService::GetFlagsFromTRRMode(
|
|
nsISocketTransport::GetTRRModeFromFlags(mConnectionFlags));
|
|
|
|
// When we get here, we are not resolving using any configured proxy likely
|
|
// because of individual proxy setting on the request or because the host is
|
|
// excluded from proxying. Hence, force resolution despite global proxy-DNS
|
|
// configuration.
|
|
dnsFlags |= nsIDNSService::RESOLVE_IGNORE_SOCKS_DNS;
|
|
|
|
NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) ||
|
|
!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4),
|
|
"Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4");
|
|
|
|
SendStatus(NS_NET_STATUS_RESOLVING_HOST);
|
|
|
|
if (!SocketHost().Equals(mOriginHost)) {
|
|
SOCKET_LOG(("nsSocketTransport %p origin %s doing dns for %s\n", this,
|
|
mOriginHost.get(), SocketHost().get()));
|
|
}
|
|
rv =
|
|
dns->AsyncResolveNative(SocketHost(), nsIDNSService::RESOLVE_TYPE_DEFAULT,
|
|
dnsFlags, nullptr, this, mSocketTransportService,
|
|
mOriginAttributes, getter_AddRefs(mDNSRequest));
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
SOCKET_LOG((" advancing to STATE_RESOLVING\n"));
|
|
mState = STATE_RESOLVING;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsSocketTransport::BuildSocket(PRFileDesc*& fd, bool& proxyTransparent,
|
|
bool& usingSSL) {
|
|
SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n", this));
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
proxyTransparent = false;
|
|
usingSSL = false;
|
|
|
|
if (mTypes.IsEmpty()) {
|
|
fd = PR_OpenTCPSocket(mNetAddr.raw.family);
|
|
if (!fd) {
|
|
SOCKET_LOG((" error creating TCP nspr socket [rv=%" PRIx32 "]\n",
|
|
static_cast<uint32_t>(rv)));
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
#if defined(XP_UNIX)
|
|
MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL,
|
|
"Unix domain sockets can't be used with socket types");
|
|
#endif
|
|
|
|
fd = nullptr;
|
|
|
|
uint32_t controlFlags = 0;
|
|
if (mProxyTransparentResolvesHost)
|
|
controlFlags |= nsISocketProvider::PROXY_RESOLVES_HOST;
|
|
|
|
if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT)
|
|
controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
|
|
|
|
if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE)
|
|
controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
|
|
|
|
if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE)
|
|
controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
|
|
|
|
// 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;
|
|
|
|
if (mTypes[0].EqualsLiteral("quic")) {
|
|
fd = PR_OpenUDPSocket(mNetAddr.raw.family);
|
|
if (!fd) {
|
|
SOCKET_LOG((" error creating UDP nspr socket [rv=%" PRIx32 "]\n",
|
|
static_cast<uint32_t>(rv)));
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
mUsingQuic = true;
|
|
// Create security control and info object for quic.
|
|
RefPtr<QuicSocketControl> quicCtrl = new QuicSocketControl(controlFlags);
|
|
quicCtrl->SetHostName(mHttpsProxy ? mProxyHost.get() : host);
|
|
quicCtrl->SetPort(mHttpsProxy ? mProxyPort : port);
|
|
nsCOMPtr<nsISupports> secinfo;
|
|
quicCtrl->QueryInterface(NS_GET_IID(nsISupports), (void**)(&secinfo));
|
|
|
|
// remember security info and give notification callbacks to PSM...
|
|
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
mSecInfo = secinfo;
|
|
callbacks = mCallbacks;
|
|
SOCKET_LOG(
|
|
(" [secinfo=%p callbacks=%p]\n", mSecInfo.get(), mCallbacks.get()));
|
|
}
|
|
// don't call into PSM while holding mLock!!
|
|
quicCtrl->SetNotificationCallbacks(callbacks);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsISocketProviderService> spserv =
|
|
nsSocketProviderService::GetOrCreate();
|
|
nsCOMPtr<nsIProxyInfo> proxyInfo = mProxyInfo;
|
|
|
|
uint32_t i;
|
|
for (i = 0; i < mTypes.Length(); ++i) {
|
|
nsCOMPtr<nsISocketProvider> provider;
|
|
|
|
SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i].get()));
|
|
|
|
rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider));
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
nsCOMPtr<nsISupports> secinfo;
|
|
if (i == 0) {
|
|
// if this is the first type, we'll want the
|
|
// service to allocate a new socket
|
|
|
|
// Most layers _ESPECIALLY_ PSM want the origin name here as they
|
|
// will use it for secure checks, etc.. and any connection management
|
|
// differences between the origin name and the routed name can be
|
|
// taken care of via DNS. However, SOCKS is a special case as there is
|
|
// no DNS. in the case of SOCKS and PSM the PSM is a separate layer
|
|
// and receives the origin name.
|
|
const char* socketProviderHost = host;
|
|
int32_t socketProviderPort = port;
|
|
if (mProxyTransparentResolvesHost &&
|
|
(mTypes[0].EqualsLiteral("socks") ||
|
|
mTypes[0].EqualsLiteral("socks4"))) {
|
|
SOCKET_LOG(("SOCKS %d Host/Route override: %s:%d -> %s:%d\n",
|
|
mHttpsProxy, socketProviderHost, socketProviderPort,
|
|
mHost.get(), mPort));
|
|
socketProviderHost = mHost.get();
|
|
socketProviderPort = mPort;
|
|
}
|
|
|
|
// when https proxying we want to just connect to the proxy as if
|
|
// it were the end host (i.e. expect the proxy's cert)
|
|
|
|
rv = provider->NewSocket(
|
|
mNetAddr.raw.family,
|
|
mHttpsProxy ? mProxyHost.get() : socketProviderHost,
|
|
mHttpsProxy ? mProxyPort : socketProviderPort, proxyInfo,
|
|
mOriginAttributes, controlFlags, mTlsFlags, &fd,
|
|
getter_AddRefs(secinfo));
|
|
|
|
if (NS_SUCCEEDED(rv) && !fd) {
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"NewSocket succeeded but failed to "
|
|
"create a PRFileDesc");
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
}
|
|
} else {
|
|
// the socket has already been allocated,
|
|
// so we just want the service to add itself
|
|
// to the stack (such as pushing an io layer)
|
|
rv = provider->AddToSocket(mNetAddr.raw.family, host, port, proxyInfo,
|
|
mOriginAttributes, controlFlags, mTlsFlags, fd,
|
|
getter_AddRefs(secinfo));
|
|
}
|
|
|
|
// controlFlags = 0; not used below this point...
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
// if the service was ssl or starttls, we want to hold onto the socket
|
|
// info
|
|
bool isSSL = mTypes[i].EqualsLiteral("ssl");
|
|
if (isSSL || mTypes[i].EqualsLiteral("starttls")) {
|
|
// remember security info and give notification callbacks to PSM...
|
|
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
mSecInfo = secinfo;
|
|
callbacks = mCallbacks;
|
|
SOCKET_LOG((" [secinfo=%p callbacks=%p]\n", mSecInfo.get(),
|
|
mCallbacks.get()));
|
|
}
|
|
// don't call into PSM while holding mLock!!
|
|
nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
|
|
if (secCtrl) secCtrl->SetNotificationCallbacks(callbacks);
|
|
// remember if socket type is SSL so we can ProxyStartSSL if need be.
|
|
usingSSL = isSSL;
|
|
} else if (mTypes[i].EqualsLiteral("socks") ||
|
|
mTypes[i].EqualsLiteral("socks4")) {
|
|
// since socks is transparent, any layers above
|
|
// it do not have to worry about proxy stuff
|
|
proxyInfo = nullptr;
|
|
proxyTransparent = true;
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
SOCKET_LOG((" error pushing io layer [%u:%s rv=%" PRIx32 "]\n", i,
|
|
mTypes[i].get(), static_cast<uint32_t>(rv)));
|
|
if (fd) {
|
|
CloseSocket(
|
|
fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult nsSocketTransport::InitiateSocket() {
|
|
SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this));
|
|
|
|
nsresult rv;
|
|
bool isLocal;
|
|
IsLocal(&isLocal);
|
|
|
|
if (gIOService->IsNetTearingDown()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
if (gIOService->IsOffline()) {
|
|
if (!isLocal) return NS_ERROR_OFFLINE;
|
|
} else if (!isLocal) {
|
|
#ifdef DEBUG
|
|
// all IP networking has to be done from the parent
|
|
if (NS_SUCCEEDED(mCondition) && ((mNetAddr.raw.family == AF_INET) ||
|
|
(mNetAddr.raw.family == AF_INET6))) {
|
|
MOZ_ASSERT(!IsNeckoChild());
|
|
}
|
|
#endif
|
|
|
|
if (NS_SUCCEEDED(mCondition) && xpc::AreNonLocalConnectionsDisabled() &&
|
|
!(mNetAddr.IsIPAddrAny() || mNetAddr.IsIPAddrLocal() ||
|
|
mNetAddr.IsIPAddrShared())) {
|
|
nsAutoCString ipaddr;
|
|
RefPtr<nsNetAddr> netaddr = new nsNetAddr(&mNetAddr);
|
|
netaddr->GetAddress(ipaddr);
|
|
fprintf_stderr(
|
|
stderr,
|
|
"FATAL ERROR: Non-local network connections are disabled and a "
|
|
"connection "
|
|
"attempt to %s (%s) was made.\nYou should only access hostnames "
|
|
"available via the test networking proxy (if running mochitests) "
|
|
"or from a test-specific httpd.js server (if running xpcshell "
|
|
"tests). "
|
|
"Browser services should be disabled or redirected to a local "
|
|
"server.\n",
|
|
mHost.get(), ipaddr.get());
|
|
MOZ_CRASH("Attempting to connect to non-local address!");
|
|
}
|
|
}
|
|
|
|
// Hosts/Proxy Hosts that are Local IP Literals should not be speculatively
|
|
// connected - Bug 853423.
|
|
if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 &&
|
|
mNetAddr.IsIPAddrLocal()) {
|
|
if (SOCKET_LOG_ENABLED()) {
|
|
nsAutoCString netAddrCString;
|
|
netAddrCString.SetLength(kIPv6CStrBufSize);
|
|
if (!mNetAddr.ToStringBuffer(netAddrCString.BeginWriting(),
|
|
kIPv6CStrBufSize))
|
|
netAddrCString = "<IP-to-string failed>"_ns;
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::InitiateSocket skipping "
|
|
"speculative connection for host [%s:%d] proxy "
|
|
"[%s:%d] with Local IP address [%s]",
|
|
mHost.get(), mPort, mProxyHost.get(), mProxyPort,
|
|
netAddrCString.get()));
|
|
}
|
|
mCondition = NS_ERROR_CONNECTION_REFUSED;
|
|
OnSocketDetached(nullptr);
|
|
return mCondition;
|
|
}
|
|
|
|
//
|
|
// find out if it is going to be ok to attach another socket to the STS.
|
|
// if not then we have to wait for the STS to tell us that it is ok.
|
|
// the notification is asynchronous, which means that when we could be
|
|
// in a race to call AttachSocket once notified. for this reason, when
|
|
// we get notified, we just re-enter this function. as a result, we are
|
|
// sure to ask again before calling AttachSocket. in this way we deal
|
|
// with the race condition. though it isn't the most elegant solution,
|
|
// it is far simpler than trying to build a system that would guarantee
|
|
// FIFO ordering (which wouldn't even be that valuable IMO). see bug
|
|
// 194402 for more info.
|
|
//
|
|
if (!mSocketTransportService->CanAttachSocket()) {
|
|
nsCOMPtr<nsIRunnable> event =
|
|
new nsSocketEvent(this, MSG_RETRY_INIT_SOCKET);
|
|
if (!event) return NS_ERROR_OUT_OF_MEMORY;
|
|
return mSocketTransportService->NotifyWhenCanAttachSocket(event);
|
|
}
|
|
|
|
//
|
|
// if we already have a connected socket, then just attach and return.
|
|
//
|
|
if (mFD.IsInitialized()) {
|
|
rv = mSocketTransportService->AttachSocket(mFD, this);
|
|
if (NS_SUCCEEDED(rv)) mAttached = true;
|
|
return rv;
|
|
}
|
|
|
|
//
|
|
// create new socket fd, push io layers, etc.
|
|
//
|
|
PRFileDesc* fd;
|
|
bool proxyTransparent;
|
|
bool usingSSL;
|
|
|
|
rv = BuildSocket(fd, proxyTransparent, usingSSL);
|
|
if (NS_FAILED(rv)) {
|
|
SOCKET_LOG(
|
|
(" BuildSocket failed [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
|
|
return rv;
|
|
}
|
|
|
|
// create proxy via IOActivityMonitor
|
|
IOActivityMonitor::MonitorSocket(fd);
|
|
|
|
#ifdef FUZZING
|
|
if (StaticPrefs::fuzzing_necko_enabled()) {
|
|
rv = AttachFuzzyIOLayer(fd);
|
|
if (NS_FAILED(rv)) {
|
|
SOCKET_LOG(("Failed to attach fuzzing IOLayer [rv=%" PRIx32 "].\n",
|
|
static_cast<uint32_t>(rv)));
|
|
return rv;
|
|
}
|
|
SOCKET_LOG(("Successfully attached fuzzing IOLayer.\n"));
|
|
|
|
if (usingSSL) {
|
|
mSecInfo = static_cast<nsISupports*>(
|
|
static_cast<nsISSLSocketControl*>(new FuzzySecurityInfo()));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
PRStatus status;
|
|
|
|
// Make the socket non-blocking...
|
|
PRSocketOptionData opt;
|
|
opt.option = PR_SockOpt_Nonblocking;
|
|
opt.value.non_blocking = true;
|
|
status = PR_SetSocketOption(fd, &opt);
|
|
NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking");
|
|
|
|
if (mUsingQuic) {
|
|
opt.option = PR_SockOpt_RecvBufferSize;
|
|
opt.value.recv_buffer_size =
|
|
StaticPrefs::network_http_http3_recvBufferSize();
|
|
status = PR_SetSocketOption(fd, &opt);
|
|
if (status != PR_SUCCESS) {
|
|
SOCKET_LOG((" Couldn't set recv buffer size"));
|
|
}
|
|
}
|
|
|
|
if (!mUsingQuic) {
|
|
if (mReuseAddrPort) {
|
|
SOCKET_LOG((" Setting port/addr reuse socket options\n"));
|
|
|
|
// Set ReuseAddr for TCP sockets to enable having several
|
|
// sockets bound to same local IP and port
|
|
PRSocketOptionData opt_reuseaddr;
|
|
opt_reuseaddr.option = PR_SockOpt_Reuseaddr;
|
|
opt_reuseaddr.value.reuse_addr = PR_TRUE;
|
|
status = PR_SetSocketOption(fd, &opt_reuseaddr);
|
|
if (status != PR_SUCCESS) {
|
|
SOCKET_LOG((" Couldn't set reuse addr socket option: %d\n", status));
|
|
}
|
|
|
|
// And also set ReusePort for platforms supporting this socket option
|
|
PRSocketOptionData opt_reuseport;
|
|
opt_reuseport.option = PR_SockOpt_Reuseport;
|
|
opt_reuseport.value.reuse_port = PR_TRUE;
|
|
status = PR_SetSocketOption(fd, &opt_reuseport);
|
|
if (status != PR_SUCCESS &&
|
|
PR_GetError() != PR_OPERATION_NOT_SUPPORTED_ERROR) {
|
|
SOCKET_LOG((" Couldn't set reuse port socket option: %d\n", status));
|
|
}
|
|
}
|
|
|
|
// disable the nagle algorithm - if we rely on it to coalesce writes into
|
|
// full packets the final packet of a multi segment POST/PUT or pipeline
|
|
// sequence is delayed a full rtt
|
|
opt.option = PR_SockOpt_NoDelay;
|
|
opt.value.no_delay = true;
|
|
PR_SetSocketOption(fd, &opt);
|
|
|
|
// if the network.tcp.sendbuffer preference is set, use it to size SO_SNDBUF
|
|
// The Windows default of 8KB is too small and as of vista sp1, autotuning
|
|
// only applies to receive window
|
|
int32_t sndBufferSize;
|
|
mSocketTransportService->GetSendBufferSize(&sndBufferSize);
|
|
if (sndBufferSize > 0) {
|
|
opt.option = PR_SockOpt_SendBufferSize;
|
|
opt.value.send_buffer_size = sndBufferSize;
|
|
PR_SetSocketOption(fd, &opt);
|
|
}
|
|
|
|
if (mQoSBits) {
|
|
opt.option = PR_SockOpt_IpTypeOfService;
|
|
opt.value.tos = mQoSBits;
|
|
PR_SetSocketOption(fd, &opt);
|
|
}
|
|
|
|
#if defined(XP_WIN)
|
|
// The linger is turned off by default. This is not a hard close, but
|
|
// closesocket should return immediately and operating system tries to send
|
|
// remaining data for certain, implementation specific, amount of time.
|
|
// https://msdn.microsoft.com/en-us/library/ms739165.aspx
|
|
//
|
|
// Turn the linger option on an set the interval to 0. This will cause hard
|
|
// close of the socket.
|
|
opt.option = PR_SockOpt_Linger;
|
|
opt.value.linger.polarity = 1;
|
|
opt.value.linger.linger = 0;
|
|
PR_SetSocketOption(fd, &opt);
|
|
#endif
|
|
}
|
|
|
|
// inform socket transport about this newly created socket...
|
|
rv = mSocketTransportService->AttachSocket(fd, this);
|
|
if (NS_FAILED(rv)) {
|
|
CloseSocket(fd,
|
|
mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
|
|
return rv;
|
|
}
|
|
mAttached = true;
|
|
|
|
// assign mFD so that we can properly handle OnSocketDetached before we've
|
|
// established a connection.
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
mFD = fd;
|
|
mFDref = 1;
|
|
mFDconnected = false;
|
|
mPollTimeout = mTimeouts[TIMEOUT_CONNECT];
|
|
}
|
|
|
|
SOCKET_LOG((" advancing to STATE_CONNECTING\n"));
|
|
mState = STATE_CONNECTING;
|
|
SendStatus(NS_NET_STATUS_CONNECTING_TO);
|
|
|
|
if (SOCKET_LOG_ENABLED()) {
|
|
char buf[kNetAddrMaxCStrBufSize];
|
|
mNetAddr.ToStringBuffer(buf, sizeof(buf));
|
|
SOCKET_LOG((" trying address: %s\n", buf));
|
|
}
|
|
|
|
//
|
|
// Initiate the connect() to the host...
|
|
//
|
|
PRNetAddr prAddr;
|
|
memset(&prAddr, 0, sizeof(prAddr));
|
|
{
|
|
if (mBindAddr) {
|
|
MutexAutoLock lock(mLock);
|
|
NetAddrToPRNetAddr(mBindAddr.get(), &prAddr);
|
|
status = PR_Bind(fd, &prAddr);
|
|
if (status != PR_SUCCESS) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mBindAddr = nullptr;
|
|
}
|
|
}
|
|
|
|
NetAddrToPRNetAddr(&mNetAddr, &prAddr);
|
|
|
|
#ifdef XP_WIN
|
|
// Find the real tcp socket and set non-blocking once again!
|
|
// Bug 1158189.
|
|
PRFileDesc* bottom = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER);
|
|
if (bottom) {
|
|
PROsfd osfd = PR_FileDesc2NativeHandle(bottom);
|
|
u_long nonblocking = 1;
|
|
if (ioctlsocket(osfd, FIONBIO, &nonblocking) != 0) {
|
|
NS_WARNING("Socket could not be set non-blocking!");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
|
|
if (secCtrl) {
|
|
if (!mEchConfig.IsEmpty() &&
|
|
!(mConnectionFlags & (DONT_TRY_ECH | BE_CONSERVATIVE))) {
|
|
SOCKET_LOG(("nsSocketTransport::InitiateSocket set echconfig."));
|
|
rv = secCtrl->SetEchConfig(mEchConfig);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
mEchConfigUsed = true;
|
|
}
|
|
}
|
|
|
|
if (mUsingQuic) {
|
|
//
|
|
// we pretend that we are connected!
|
|
//
|
|
if (PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT) == PR_SUCCESS) {
|
|
OnSocketConnected();
|
|
return NS_OK;
|
|
}
|
|
PRErrorCode code = PR_GetError();
|
|
return ErrorAccordingToNSPR(code);
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
bool tfo = false;
|
|
if (!mProxyTransparent && mFastOpenCallback &&
|
|
mFastOpenCallback->FastOpenEnabled()) {
|
|
if (NS_SUCCEEDED(AttachTCPFastOpenIOLayer(fd))) {
|
|
tfo = true;
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::InitiateSocket TCP Fast Open "
|
|
"started [this=%p]\n",
|
|
this));
|
|
}
|
|
}
|
|
|
|
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 ((status == PR_SUCCESS) && tfo) {
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
mFDFastOpenInProgress = true;
|
|
}
|
|
SOCKET_LOG(("Using TCP Fast Open."));
|
|
rv = mFastOpenCallback->StartFastOpen();
|
|
if (NS_FAILED(rv)) {
|
|
if (NS_SUCCEEDED(mCondition)) {
|
|
mCondition = rv;
|
|
}
|
|
mFastOpenCallback = nullptr;
|
|
MutexAutoLock lock(mLock);
|
|
mFDFastOpenInProgress = false;
|
|
return rv;
|
|
}
|
|
status = PR_FAILURE;
|
|
connectCalled = false;
|
|
bool fastOpenNotSupported = false;
|
|
TCPFastOpenFinish(fd, code, fastOpenNotSupported, mFastOpenStatus);
|
|
|
|
// If we have sent data, trigger a socket status event.
|
|
if (mFastOpenStatus == TFO_DATA_SENT) {
|
|
SendStatus(NS_NET_STATUS_SENDING_TO);
|
|
}
|
|
|
|
// If we have still some data buffered this data must be flush before
|
|
// mOutput.OnSocketReady(NS_OK) is called in
|
|
// nsSocketTransport::OnSocketReady, partially to keep socket status
|
|
// event in order.
|
|
mFastOpenLayerHasBufferedData = TCPFastOpenGetCurrentBufferSize(fd);
|
|
|
|
MOZ_ASSERT((mFastOpenStatus == TFO_NOT_TRIED) ||
|
|
(mFastOpenStatus == TFO_DISABLED) ||
|
|
(mFastOpenStatus == TFO_DATA_SENT) ||
|
|
(mFastOpenStatus == TFO_TRIED));
|
|
mFastOpenCallback->SetFastOpenStatus(mFastOpenStatus);
|
|
SOCKET_LOG(
|
|
("called StartFastOpen - code=%d; fastOpen is %s "
|
|
"supported.\n",
|
|
code, fastOpenNotSupported ? "not" : ""));
|
|
SOCKET_LOG(("TFO status %d\n", mFastOpenStatus));
|
|
|
|
if (fastOpenNotSupported) {
|
|
// When TCP_FastOpen is turned off on the local host
|
|
// SendTo will return PR_NOT_TCP_SOCKET_ERROR. This is only
|
|
// on Linux.
|
|
// If a windows version does not support Fast Open, the return value
|
|
// will be PR_NOT_IMPLEMENTED_ERROR. This is only for windows 10
|
|
// versions older than version 1607, because we do not have subverion
|
|
// to check, we need to call PR_SendTo to check if it is supported.
|
|
mFastOpenCallback->FastOpenNotSupported();
|
|
// FastOpenNotSupported will set Fast Open as not supported globally.
|
|
// For this connection we will pretend that we still use fast open,
|
|
// because of the fallback mechanism in case we need to restart the
|
|
// attached transaction.
|
|
connectCalled = true;
|
|
}
|
|
} else {
|
|
mFastOpenCallback = nullptr;
|
|
}
|
|
|
|
if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
|
|
connectStarted && connectCalled) {
|
|
SendPRBlockingTelemetry(
|
|
connectStarted, Telemetry::PRCONNECT_BLOCKING_TIME_NORMAL,
|
|
Telemetry::PRCONNECT_BLOCKING_TIME_SHUTDOWN,
|
|
Telemetry::PRCONNECT_BLOCKING_TIME_CONNECTIVITY_CHANGE,
|
|
Telemetry::PRCONNECT_BLOCKING_TIME_LINK_CHANGE,
|
|
Telemetry::PRCONNECT_BLOCKING_TIME_OFFLINE);
|
|
}
|
|
|
|
if (status == PR_SUCCESS) {
|
|
//
|
|
// we are connected!
|
|
//
|
|
OnSocketConnected();
|
|
} else {
|
|
#if defined(TEST_CONNECT_ERRORS)
|
|
code = RandomizeConnectError(code);
|
|
#endif
|
|
//
|
|
// If the PR_Connect(...) would block, then poll for a connection.
|
|
//
|
|
if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code))
|
|
mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE);
|
|
//
|
|
// If the socket is already connected, then return success...
|
|
//
|
|
else if (PR_IS_CONNECTED_ERROR == code) {
|
|
//
|
|
// we are connected!
|
|
//
|
|
OnSocketConnected();
|
|
|
|
if (mSecInfo && !mProxyHost.IsEmpty() && proxyTransparent && usingSSL) {
|
|
// if the connection phase is finished, and the ssl layer has
|
|
// been pushed, and we were proxying (transparently; ie. nothing
|
|
// has to happen in the protocol layer above us), it's time for
|
|
// the ssl to start doing it's thing.
|
|
nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
|
|
if (secCtrl) {
|
|
SOCKET_LOG((" calling ProxyStartSSL()\n"));
|
|
secCtrl->ProxyStartSSL();
|
|
}
|
|
// XXX what if we were forced to poll on the socket for a successful
|
|
// connection... wouldn't we need to call ProxyStartSSL after a call
|
|
// to PR_ConnectContinue indicates that we are connected?
|
|
//
|
|
// XXX this appears to be what the old socket transport did. why
|
|
// isn't this broken?
|
|
}
|
|
}
|
|
//
|
|
// A SOCKS request was rejected; get the actual error code from
|
|
// the OS error
|
|
//
|
|
else if (PR_UNKNOWN_ERROR == code && mProxyTransparent &&
|
|
!mProxyHost.IsEmpty()) {
|
|
code = PR_GetOSError();
|
|
rv = ErrorAccordingToNSPR(code);
|
|
}
|
|
//
|
|
// The connection was refused...
|
|
//
|
|
else {
|
|
if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase() &&
|
|
connectStarted && connectCalled) {
|
|
SendPRBlockingTelemetry(
|
|
connectStarted, Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_NORMAL,
|
|
Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_SHUTDOWN,
|
|
Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_CONNECTIVITY_CHANGE,
|
|
Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_LINK_CHANGE,
|
|
Telemetry::PRCONNECT_FAIL_BLOCKING_TIME_OFFLINE);
|
|
}
|
|
|
|
rv = ErrorAccordingToNSPR(code);
|
|
if ((rv == NS_ERROR_CONNECTION_REFUSED) && !mProxyHost.IsEmpty())
|
|
rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
bool nsSocketTransport::RecoverFromError() {
|
|
NS_ASSERTION(NS_FAILED(mCondition), "there should be something wrong");
|
|
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::RecoverFromError [this=%p state=%x cond=%" PRIx32
|
|
"]\n",
|
|
this, mState, static_cast<uint32_t>(mCondition)));
|
|
|
|
if (mDoNotRetryToConnect) {
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::RecoverFromError do not retry because "
|
|
"mDoNotRetryToConnect is set [this=%p]\n",
|
|
this));
|
|
return false;
|
|
}
|
|
|
|
#if defined(XP_UNIX)
|
|
// Unix domain connections don't have multiple addresses to try,
|
|
// so the recovery techniques here don't apply.
|
|
if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) return false;
|
|
#endif
|
|
|
|
// can only recover from errors in these states
|
|
if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) {
|
|
SOCKET_LOG((" not in a recoverable state"));
|
|
return false;
|
|
}
|
|
|
|
nsresult rv;
|
|
|
|
// OK to check this outside mLock
|
|
NS_ASSERTION(!mFDconnected, "socket should not be connected");
|
|
|
|
// all connection failures need to be reported to DNS so that the next
|
|
// time we will use a different address if available.
|
|
// Skip conditions that can be cause by TCP Fast Open.
|
|
if ((!mFDFastOpenInProgress ||
|
|
((mCondition != NS_ERROR_CONNECTION_REFUSED) &&
|
|
(mCondition != NS_ERROR_NET_TIMEOUT) &&
|
|
(mCondition != NS_BASE_STREAM_CLOSED) &&
|
|
(mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED))) &&
|
|
mState == STATE_CONNECTING && mDNSRecord) {
|
|
mDNSRecord->ReportUnusable(SocketPort());
|
|
}
|
|
|
|
#if defined(_WIN64) && defined(WIN95)
|
|
// can only recover from these errors
|
|
if (mCondition != NS_ERROR_CONNECTION_REFUSED &&
|
|
mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED &&
|
|
mCondition != NS_ERROR_NET_TIMEOUT &&
|
|
mCondition != NS_ERROR_UNKNOWN_HOST &&
|
|
mCondition != NS_ERROR_UNKNOWN_PROXY_HOST &&
|
|
!(mFDFastOpenInProgress && (mCondition == NS_ERROR_FAILURE))) {
|
|
SOCKET_LOG((" not a recoverable error %" PRIx32,
|
|
static_cast<uint32_t>(mCondition)));
|
|
return false;
|
|
}
|
|
#else
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
bool tryAgain = false;
|
|
if (mFDFastOpenInProgress &&
|
|
((mCondition == NS_ERROR_CONNECTION_REFUSED) ||
|
|
(mCondition == NS_ERROR_NET_TIMEOUT) ||
|
|
#if defined(_WIN64) && defined(WIN95)
|
|
// On Windows PR_ContinueConnect can return NS_ERROR_FAILURE.
|
|
// This will be fixed in bug 1386719 and this is just a temporary
|
|
// work around.
|
|
(mCondition == NS_ERROR_FAILURE) ||
|
|
#endif
|
|
(mCondition == NS_ERROR_PROXY_CONNECTION_REFUSED))) {
|
|
// TCP Fast Open can be blocked by middle boxes so we will retry
|
|
// without it.
|
|
tryAgain = true;
|
|
// If we cancel the connection because backup socket was successfully
|
|
// connected, mFDFastOpenInProgress will be true but mFastOpenCallback
|
|
// will be nullptr.
|
|
if (mFastOpenCallback) {
|
|
mFastOpenCallback->SetFastOpenConnected(mCondition, true);
|
|
}
|
|
mFastOpenCallback = nullptr;
|
|
|
|
} else {
|
|
// This is only needed for telemetry.
|
|
if (NS_SUCCEEDED(mFirstRetryError)) {
|
|
mFirstRetryError = mCondition;
|
|
}
|
|
if ((mState == STATE_CONNECTING) && mDNSRecord) {
|
|
if (mNetAddr.raw.family == AF_INET) {
|
|
if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
|
|
Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
|
|
UNSUCCESSFUL_CONNECTING_TO_IPV4_ADDRESS);
|
|
}
|
|
} else if (mNetAddr.raw.family == AF_INET6) {
|
|
if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
|
|
Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
|
|
UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY &&
|
|
mCondition == NS_ERROR_UNKNOWN_HOST && mState == STATE_RESOLVING &&
|
|
!mProxyTransparentResolvesHost) {
|
|
SOCKET_LOG((" trying lookup again with opposite ip family\n"));
|
|
mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4);
|
|
mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY;
|
|
// This will tell the consuming half-open to reset preference on the
|
|
// connection entry
|
|
mResetFamilyPreference = true;
|
|
tryAgain = true;
|
|
}
|
|
|
|
// try next ip address only if past the resolver stage...
|
|
if (mState == STATE_CONNECTING && mDNSRecord) {
|
|
nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
|
|
mDNSRecord->IsTRR(&mResolvedByTRR);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
SOCKET_LOG((" trying again with next ip address\n"));
|
|
tryAgain = true;
|
|
} else if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY) {
|
|
SOCKET_LOG((" failed to connect, trying with opposite ip family\n"));
|
|
// Drop state to closed. This will trigger new round of DNS
|
|
// resolving bellow.
|
|
mState = STATE_CLOSED;
|
|
mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4);
|
|
mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY;
|
|
// This will tell the consuming half-open to reset preference on the
|
|
// connection entry
|
|
mResetFamilyPreference = true;
|
|
tryAgain = true;
|
|
} else if (!(mConnectionFlags & DISABLE_TRR)) {
|
|
bool trrEnabled;
|
|
mDNSRecord->IsTRR(&trrEnabled);
|
|
|
|
// Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we
|
|
// should intentionally not fallback to regular DNS.
|
|
if (!StaticPrefs::network_trr_fallback_on_zero_response() &&
|
|
((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) ||
|
|
(mNetAddr.raw.family == AF_INET6 &&
|
|
mNetAddr.inet6.ip.u64[0] == 0 &&
|
|
mNetAddr.inet6.ip.u64[1] == 0))) {
|
|
SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs"));
|
|
} else if (trrEnabled) {
|
|
uint32_t trrMode = 0;
|
|
mDNSRecord->GetEffectiveTRRMode(&trrMode);
|
|
// If current trr mode is trr only, we should not retry.
|
|
if (trrMode != 3) {
|
|
// Drop state to closed. This will trigger a new round of
|
|
// DNS resolving. Bypass the cache this time since the
|
|
// cached data came from TRR and failed already!
|
|
SOCKET_LOG((" failed to connect with TRR enabled, try w/o\n"));
|
|
mState = STATE_CLOSED;
|
|
mConnectionFlags |= DISABLE_TRR | BYPASS_CACHE | REFRESH_CACHE;
|
|
tryAgain = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// prepare to try again.
|
|
if (tryAgain) {
|
|
uint32_t msg;
|
|
|
|
if (mState == STATE_CONNECTING) {
|
|
mState = STATE_RESOLVING;
|
|
msg = MSG_DNS_LOOKUP_COMPLETE;
|
|
} else {
|
|
mState = STATE_CLOSED;
|
|
msg = MSG_ENSURE_CONNECT;
|
|
}
|
|
|
|
rv = PostEvent(msg, NS_OK);
|
|
if (NS_FAILED(rv)) tryAgain = false;
|
|
}
|
|
|
|
return tryAgain;
|
|
}
|
|
|
|
// called on the socket thread only
|
|
void nsSocketTransport::OnMsgInputClosed(nsresult reason) {
|
|
SOCKET_LOG(("nsSocketTransport::OnMsgInputClosed [this=%p reason=%" PRIx32
|
|
"]\n",
|
|
this, static_cast<uint32_t>(reason)));
|
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
|
|
mInputClosed = true;
|
|
// check if event should affect entire transport
|
|
if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED))
|
|
mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
|
|
else if (mOutputClosed)
|
|
mCondition =
|
|
NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
|
|
else {
|
|
if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_READ;
|
|
mInput.OnSocketReady(reason);
|
|
}
|
|
}
|
|
|
|
// called on the socket thread only
|
|
void nsSocketTransport::OnMsgOutputClosed(nsresult reason) {
|
|
SOCKET_LOG(("nsSocketTransport::OnMsgOutputClosed [this=%p reason=%" PRIx32
|
|
"]\n",
|
|
this, static_cast<uint32_t>(reason)));
|
|
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
|
|
mOutputClosed = true;
|
|
// check if event should affect entire transport
|
|
if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED))
|
|
mCondition = reason; // XXX except if NS_FAILED(mCondition), right??
|
|
else if (mInputClosed)
|
|
mCondition =
|
|
NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right??
|
|
else {
|
|
if (mState == STATE_TRANSFERRING) mPollFlags &= ~PR_POLL_WRITE;
|
|
mOutput.OnSocketReady(reason);
|
|
}
|
|
}
|
|
|
|
void nsSocketTransport::OnSocketConnected() {
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
SOCKET_LOG((" advancing to STATE_TRANSFERRING\n"));
|
|
|
|
mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT);
|
|
mState = STATE_TRANSFERRING;
|
|
|
|
// Set the m*AddrIsSet flags only when state has reached TRANSFERRING
|
|
// because we need to make sure its value does not change due to failover
|
|
mNetAddrIsSet = true;
|
|
|
|
if (mFDFastOpenInProgress && mFastOpenCallback) {
|
|
// mFastOpenCallback can be null when for example h2 is negotiated on
|
|
// another connection to the same host and all connections are
|
|
// abandoned.
|
|
mFastOpenCallback->SetFastOpenConnected(NS_OK, false);
|
|
}
|
|
mFastOpenCallback = nullptr;
|
|
|
|
// 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;
|
|
mFDFastOpenInProgress = false;
|
|
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;
|
|
}
|
|
|
|
PRFileDesc* nsSocketTransport::GetFD_LockedAlsoDuringFastOpen() {
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
// mFD is not available to the streams while disconnected.
|
|
if (!mFDconnected && !mFDFastOpenInProgress) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (mFD.IsInitialized()) {
|
|
mFDref++;
|
|
}
|
|
|
|
return mFD;
|
|
}
|
|
|
|
bool nsSocketTransport::FastOpenInProgress() {
|
|
mLock.AssertCurrentThreadOwns();
|
|
return mFDFastOpenInProgress;
|
|
}
|
|
|
|
class ThunkPRClose : public Runnable {
|
|
public:
|
|
explicit ThunkPRClose(PRFileDesc* fd)
|
|
: Runnable("net::ThunkPRClose"), mFD(fd) {}
|
|
|
|
NS_IMETHOD Run() override {
|
|
nsSocketTransport::CloseSocket(
|
|
mFD, gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
PRFileDesc* mFD;
|
|
};
|
|
|
|
void STS_PRCloseOnSocketTransport(PRFileDesc* fd, bool lingerPolarity,
|
|
int16_t lingerTimeout) {
|
|
if (gSocketTransportService) {
|
|
// Can't PR_Close() a socket off STS thread. Thunk it to STS to die
|
|
gSocketTransportService->Dispatch(new ThunkPRClose(fd), NS_DISPATCH_NORMAL);
|
|
} else {
|
|
// something horrible has happened
|
|
NS_ASSERTION(gSocketTransportService, "No STS service");
|
|
}
|
|
}
|
|
|
|
void nsSocketTransport::ReleaseFD_Locked(PRFileDesc* fd) {
|
|
mLock.AssertCurrentThreadOwns();
|
|
|
|
NS_ASSERTION(mFD == fd, "wrong fd");
|
|
|
|
if (--mFDref == 0) {
|
|
if (gIOService->IsNetTearingDown() &&
|
|
((PR_IntervalNow() - gIOService->NetTearingDownStarted()) >
|
|
gSocketTransportService->MaxTimeForPrClosePref())) {
|
|
// If shutdown last to long, let the socket leak and do not close it.
|
|
SOCKET_LOG(("Intentional leak"));
|
|
} else {
|
|
if (mLingerPolarity || mLingerTimeout) {
|
|
PRSocketOptionData socket_linger;
|
|
socket_linger.option = PR_SockOpt_Linger;
|
|
socket_linger.value.linger.polarity = mLingerPolarity;
|
|
socket_linger.value.linger.linger = mLingerTimeout;
|
|
PR_SetSocketOption(mFD, &socket_linger);
|
|
}
|
|
if (OnSocketThread()) {
|
|
SOCKET_LOG(("nsSocketTransport: calling PR_Close [this=%p]\n", this));
|
|
CloseSocket(
|
|
mFD, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase());
|
|
} else {
|
|
// Can't PR_Close() a socket off STS thread. Thunk it to STS to die
|
|
STS_PRCloseOnSocketTransport(mFD, mLingerPolarity, mLingerTimeout);
|
|
}
|
|
}
|
|
mFD = nullptr;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// socket event handler impl
|
|
|
|
void nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status,
|
|
nsISupports* param) {
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%" PRIx32
|
|
" param=%p]\n",
|
|
this, type, static_cast<uint32_t>(status), param));
|
|
|
|
if (NS_FAILED(mCondition)) {
|
|
// block event since we're apparently already dead.
|
|
SOCKET_LOG((" blocking event [condition=%" PRIx32 "]\n",
|
|
static_cast<uint32_t>(mCondition)));
|
|
//
|
|
// notify input/output streams in case either has a pending notify.
|
|
//
|
|
mInput.OnSocketReady(mCondition);
|
|
mOutput.OnSocketReady(mCondition);
|
|
return;
|
|
}
|
|
|
|
switch (type) {
|
|
case MSG_ENSURE_CONNECT:
|
|
SOCKET_LOG((" MSG_ENSURE_CONNECT\n"));
|
|
|
|
// Apply port remapping here so that we do it on the socket thread and
|
|
// before we process the resolved DNS name or create the socket the first
|
|
// time.
|
|
if (!mPortRemappingApplied) {
|
|
mPortRemappingApplied = true;
|
|
|
|
mSocketTransportService->ApplyPortRemap(&mPort);
|
|
mSocketTransportService->ApplyPortRemap(&mOriginPort);
|
|
}
|
|
|
|
//
|
|
// ensure that we have created a socket, attached it, and have a
|
|
// connection.
|
|
//
|
|
if (mState == STATE_CLOSED) {
|
|
// Unix domain sockets are ready to connect; mNetAddr is all we
|
|
// need. Internet address families require a DNS lookup (or possibly
|
|
// several) before we can connect.
|
|
#if defined(XP_UNIX)
|
|
if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL)
|
|
mCondition = InitiateSocket();
|
|
else
|
|
#endif
|
|
mCondition = ResolveHost();
|
|
|
|
} else {
|
|
SOCKET_LOG((" ignoring redundant event\n"));
|
|
}
|
|
break;
|
|
|
|
case MSG_DNS_LOOKUP_COMPLETE:
|
|
if (mDNSRequest) { // only send this if we actually resolved anything
|
|
SendStatus(NS_NET_STATUS_RESOLVED_HOST);
|
|
}
|
|
|
|
SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n"));
|
|
mDNSRequest = nullptr;
|
|
|
|
if (mDNSRecord) {
|
|
mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
|
|
mDNSRecord->IsTRR(&mResolvedByTRR);
|
|
}
|
|
// status contains DNS lookup status
|
|
if (NS_FAILED(status)) {
|
|
// When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP
|
|
// proxy host is not found, so we fixup the error code.
|
|
// For SOCKS proxies (mProxyTransparent == true), the socket
|
|
// transport resolves the real host here, so there's no fixup
|
|
// (see bug 226943).
|
|
if ((status == NS_ERROR_UNKNOWN_HOST) && !mProxyTransparent &&
|
|
!mProxyHost.IsEmpty())
|
|
mCondition = NS_ERROR_UNKNOWN_PROXY_HOST;
|
|
else
|
|
mCondition = status;
|
|
} else if (mState == STATE_RESOLVING) {
|
|
mCondition = InitiateSocket();
|
|
}
|
|
break;
|
|
|
|
case MSG_RETRY_INIT_SOCKET:
|
|
mCondition = InitiateSocket();
|
|
break;
|
|
|
|
case MSG_INPUT_CLOSED:
|
|
SOCKET_LOG((" MSG_INPUT_CLOSED\n"));
|
|
OnMsgInputClosed(status);
|
|
break;
|
|
|
|
case MSG_INPUT_PENDING:
|
|
SOCKET_LOG((" MSG_INPUT_PENDING\n"));
|
|
OnMsgInputPending();
|
|
break;
|
|
|
|
case MSG_OUTPUT_CLOSED:
|
|
SOCKET_LOG((" MSG_OUTPUT_CLOSED\n"));
|
|
OnMsgOutputClosed(status);
|
|
break;
|
|
|
|
case MSG_OUTPUT_PENDING:
|
|
SOCKET_LOG((" MSG_OUTPUT_PENDING\n"));
|
|
OnMsgOutputPending();
|
|
break;
|
|
case MSG_TIMEOUT_CHANGED:
|
|
SOCKET_LOG((" MSG_TIMEOUT_CHANGED\n"));
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
mPollTimeout =
|
|
mTimeouts[(mState == STATE_TRANSFERRING) ? TIMEOUT_READ_WRITE
|
|
: TIMEOUT_CONNECT];
|
|
}
|
|
break;
|
|
default:
|
|
SOCKET_LOG((" unhandled event!\n"));
|
|
}
|
|
|
|
if (NS_FAILED(mCondition)) {
|
|
SOCKET_LOG((" after event [this=%p cond=%" PRIx32 "]\n", this,
|
|
static_cast<uint32_t>(mCondition)));
|
|
if (!mAttached) // need to process this error ourselves...
|
|
OnSocketDetached(nullptr);
|
|
} else if (mPollFlags == PR_POLL_EXCEPT)
|
|
mPollFlags = 0; // make idle
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// socket handler impl
|
|
|
|
void nsSocketTransport::OnSocketReady(PRFileDesc* fd, int16_t outFlags) {
|
|
SOCKET_LOG1(("nsSocketTransport::OnSocketReady [this=%p outFlags=%hd]\n",
|
|
this, outFlags));
|
|
|
|
if (outFlags == -1) {
|
|
SOCKET_LOG(("socket timeout expired\n"));
|
|
mCondition = NS_ERROR_NET_TIMEOUT;
|
|
return;
|
|
}
|
|
|
|
if ((mState == STATE_TRANSFERRING) && mFastOpenLayerHasBufferedData) {
|
|
// We have some data buffered in TCPFastOpenLayer. We will flush them
|
|
// first. We need to do this first before calling OnSocketReady below
|
|
// so that the socket status events are kept in the correct order.
|
|
mFastOpenLayerHasBufferedData = TCPFastOpenFlushBuffer(fd);
|
|
if (mFastOpenLayerHasBufferedData) {
|
|
return;
|
|
}
|
|
SendStatus(NS_NET_STATUS_SENDING_TO);
|
|
|
|
// If we are done sending the buffered data continue with the normal
|
|
// path.
|
|
// In case of an error, TCPFastOpenFlushBuffer will return false and
|
|
// the normal code path will pick up the error.
|
|
mFastOpenLayerHasBufferedData = false;
|
|
}
|
|
|
|
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 defined(_WIN64) && defined(WIN95)
|
|
# ifndef TCP_FASTOPEN
|
|
# define TCP_FASTOPEN 15
|
|
# endif
|
|
|
|
if (mFDFastOpenInProgress && mFastOpenCallback &&
|
|
(mFastOpenStatus == TFO_DATA_SENT)) {
|
|
PROsfd osfd = PR_FileDesc2NativeHandle(fd);
|
|
BOOL option = 0;
|
|
int len = sizeof(option);
|
|
PRInt32 rv = getsockopt((SOCKET)osfd, IPPROTO_TCP, TCP_FASTOPEN,
|
|
(char*)&option, &len);
|
|
if (!rv && !option) {
|
|
// On error, I will let the normal necko paths pickup the error.
|
|
mFastOpenCallback->SetFastOpenStatus(TFO_DATA_COOKIE_NOT_ACCEPTED);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
mFastOpenLayerHasBufferedData = false;
|
|
|
|
// If we are not shutting down try again.
|
|
if (!gIOService->IsNetTearingDown() && RecoverFromError())
|
|
mCondition = NS_OK;
|
|
else {
|
|
mState = STATE_CLOSED;
|
|
|
|
// The error can happened before we start fast open. In that case do not
|
|
// call mFastOpenCallback->SetFastOpenConnected; If error happends during
|
|
// fast open, inform the halfOpenSocket.
|
|
// If we cancel the connection because backup socket was successfully
|
|
// connected, mFDFastOpenInProgress will be true but mFastOpenCallback
|
|
// will be nullptr.
|
|
if (mFDFastOpenInProgress && mFastOpenCallback) {
|
|
mFastOpenCallback->SetFastOpenConnected(mCondition, false);
|
|
}
|
|
mFastOpenCallback = nullptr;
|
|
|
|
// 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 FastOpen has been used (mFDFastOpenInProgress==true),
|
|
// mFastOpenCallback must be nullptr now. We decided to recover from
|
|
// error like NET_TIMEOUT, CONNECTION_REFUSED or we have called
|
|
// SetFastOpenConnected(mCondition) in this function a couple of lines
|
|
// above.
|
|
// If FastOpen has not been used (mFDFastOpenInProgress==false) it can be
|
|
// that mFastOpenCallback is no null, this is the case when we recover from
|
|
// errors like UKNOWN_HOST in which case socket was not been connected yet
|
|
// and mFastOpenCallback-StartFastOpen was not be called yet (but we can
|
|
// still call it in the next try).
|
|
MOZ_ASSERT(!(mFDFastOpenInProgress && mFastOpenCallback));
|
|
|
|
// break any potential reference cycle between the security info object
|
|
// and ourselves by resetting its notification callbacks object. see
|
|
// bug 285991 for details.
|
|
nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
|
|
if (secCtrl) secCtrl->SetNotificationCallbacks(nullptr);
|
|
|
|
// finally, release our reference to the socket (must do this within
|
|
// the transport lock) possibly closing the socket. Also release our
|
|
// listeners to break potential refcount cycles.
|
|
|
|
// We should be careful not to release mEventSink and mCallbacks while
|
|
// we're locked, because releasing it might require acquiring the lock
|
|
// again, so we just null out mEventSink and mCallbacks while we're
|
|
// holding the lock, and let the stack based objects' destuctors take
|
|
// care of destroying it if needed.
|
|
nsCOMPtr<nsIInterfaceRequestor> ourCallbacks;
|
|
nsCOMPtr<nsITransportEventSink> ourEventSink;
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
if (mFD.IsInitialized()) {
|
|
ReleaseFD_Locked(mFD);
|
|
// flag mFD as unusable; this prevents other consumers from
|
|
// acquiring a reference to mFD.
|
|
mFDconnected = false;
|
|
mFDFastOpenInProgress = false;
|
|
}
|
|
|
|
// We must release mCallbacks and mEventSink to avoid memory leak
|
|
// but only when RecoverFromError() above failed. Otherwise we lose
|
|
// link with UI and security callbacks on next connection attempt
|
|
// round. That would lead e.g. to a broken certificate exception page.
|
|
if (NS_FAILED(mCondition)) {
|
|
mCallbacks.swap(ourCallbacks);
|
|
mEventSink.swap(ourEventSink);
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsSocketTransport::IsLocal(bool* aIsLocal) {
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
|
|
#if defined(XP_UNIX)
|
|
// Unix-domain sockets are always local.
|
|
if (mNetAddr.raw.family == PR_AF_LOCAL) {
|
|
*aIsLocal = true;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
*aIsLocal = mNetAddr.IsLoopbackAddr();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// xpcom api
|
|
|
|
NS_IMPL_ISUPPORTS(nsSocketTransport, nsISocketTransport, nsITransport,
|
|
nsIDNSListener, nsIClassInfo, nsIInterfaceRequestor)
|
|
NS_IMPL_CI_INTERFACE_GETTER(nsSocketTransport, nsISocketTransport, nsITransport,
|
|
nsIDNSListener, nsIInterfaceRequestor)
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::OpenInputStream(uint32_t flags, uint32_t segsize,
|
|
uint32_t segcount, nsIInputStream** result) {
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", this, flags));
|
|
|
|
NS_ENSURE_TRUE(!mInput.IsReferenced(), NS_ERROR_UNEXPECTED);
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIAsyncInputStream> pipeIn;
|
|
|
|
if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
|
|
// XXX if the caller wants blocking, then the caller also gets buffered!
|
|
// bool openBuffered = !(flags & OPEN_UNBUFFERED);
|
|
bool openBlocking = (flags & OPEN_BLOCKING);
|
|
|
|
net_ResolveSegmentParams(segsize, segcount);
|
|
|
|
// create a pipe
|
|
nsCOMPtr<nsIAsyncOutputStream> pipeOut;
|
|
rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut),
|
|
!openBlocking, true, segsize, segcount);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// async copy from socket to pipe
|
|
rv = NS_AsyncCopy(&mInput, pipeOut, mSocketTransportService,
|
|
NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
*result = pipeIn;
|
|
} else
|
|
*result = &mInput;
|
|
|
|
// flag input stream as open
|
|
mInputClosed = false;
|
|
|
|
rv = PostEvent(MSG_ENSURE_CONNECT);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
NS_ADDREF(*result);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::OpenOutputStream(uint32_t flags, uint32_t segsize,
|
|
uint32_t segcount,
|
|
nsIOutputStream** result) {
|
|
SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", this,
|
|
flags));
|
|
|
|
NS_ENSURE_TRUE(!mOutput.IsReferenced(), NS_ERROR_UNEXPECTED);
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIAsyncOutputStream> pipeOut;
|
|
if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) {
|
|
// XXX if the caller wants blocking, then the caller also gets buffered!
|
|
// bool openBuffered = !(flags & OPEN_UNBUFFERED);
|
|
bool openBlocking = (flags & OPEN_BLOCKING);
|
|
|
|
net_ResolveSegmentParams(segsize, segcount);
|
|
|
|
// create a pipe
|
|
nsCOMPtr<nsIAsyncInputStream> pipeIn;
|
|
rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true,
|
|
!openBlocking, segsize, segcount);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// async copy from socket to pipe
|
|
rv = NS_AsyncCopy(pipeIn, &mOutput, mSocketTransportService,
|
|
NS_ASYNCCOPY_VIA_READSEGMENTS, segsize);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
*result = pipeOut;
|
|
} else
|
|
*result = &mOutput;
|
|
|
|
// flag output stream as open
|
|
mOutputClosed = false;
|
|
|
|
rv = PostEvent(MSG_ENSURE_CONNECT);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
NS_ADDREF(*result);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::Close(nsresult reason) {
|
|
SOCKET_LOG(("nsSocketTransport::Close %p reason=%" PRIx32, this,
|
|
static_cast<uint32_t>(reason)));
|
|
|
|
if (NS_SUCCEEDED(reason)) reason = NS_BASE_STREAM_CLOSED;
|
|
|
|
mDoNotRetryToConnect = true;
|
|
|
|
if (mFDFastOpenInProgress && mFastOpenCallback) {
|
|
mFastOpenCallback->SetFastOpenConnected(reason, false);
|
|
}
|
|
mFastOpenCallback = nullptr;
|
|
|
|
mInput.CloseWithStatus(reason);
|
|
mOutput.CloseWithStatus(reason);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetSecurityInfo(nsISupports** secinfo) {
|
|
MutexAutoLock lock(mLock);
|
|
NS_IF_ADDREF(*secinfo = mSecInfo);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor** callbacks) {
|
|
MutexAutoLock lock(mLock);
|
|
NS_IF_ADDREF(*callbacks = mCallbacks);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor* callbacks) {
|
|
nsCOMPtr<nsIInterfaceRequestor> threadsafeCallbacks;
|
|
NS_NewNotificationCallbacksAggregation(callbacks, nullptr,
|
|
GetCurrentEventTarget(),
|
|
getter_AddRefs(threadsafeCallbacks));
|
|
|
|
nsCOMPtr<nsISupports> secinfo;
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
mCallbacks = threadsafeCallbacks;
|
|
SOCKET_LOG(("Reset callbacks for secinfo=%p callbacks=%p\n", mSecInfo.get(),
|
|
mCallbacks.get()));
|
|
|
|
secinfo = mSecInfo;
|
|
}
|
|
|
|
// don't call into PSM while holding mLock!!
|
|
nsCOMPtr<nsISSLSocketControl> secCtrl(do_QueryInterface(secinfo));
|
|
if (secCtrl) secCtrl->SetNotificationCallbacks(threadsafeCallbacks);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::SetEventSink(nsITransportEventSink* sink,
|
|
nsIEventTarget* target) {
|
|
nsCOMPtr<nsITransportEventSink> temp;
|
|
if (target) {
|
|
nsresult rv =
|
|
net_NewTransportEventSinkProxy(getter_AddRefs(temp), sink, target);
|
|
if (NS_FAILED(rv)) return rv;
|
|
sink = temp.get();
|
|
}
|
|
|
|
MutexAutoLock lock(mLock);
|
|
mEventSink = sink;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::IsAlive(bool* result) {
|
|
*result = false;
|
|
|
|
// During Fast Open we need to return true here.
|
|
if (mFDFastOpenInProgress) {
|
|
*result = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult conditionWhileLocked = NS_OK;
|
|
PRFileDescAutoLock fd(this, false, &conditionWhileLocked);
|
|
if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX do some idle-time based checks??
|
|
|
|
char c;
|
|
int32_t rval = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0);
|
|
|
|
if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR))
|
|
*result = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetHost(nsACString& host) {
|
|
host = SocketHost();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetPort(int32_t* port) {
|
|
*port = (int32_t)SocketPort();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetScriptableOriginAttributes(
|
|
JSContext* aCx, JS::MutableHandle<JS::Value> aOriginAttributes) {
|
|
if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aOriginAttributes))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::SetScriptableOriginAttributes(
|
|
JSContext* aCx, JS::Handle<JS::Value> aOriginAttributes) {
|
|
MutexAutoLock lock(mLock);
|
|
NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
|
|
|
|
OriginAttributes attrs;
|
|
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
mOriginAttributes = attrs;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsSocketTransport::GetOriginAttributes(
|
|
OriginAttributes* aOriginAttributes) {
|
|
NS_ENSURE_ARG(aOriginAttributes);
|
|
*aOriginAttributes = mOriginAttributes;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsSocketTransport::SetOriginAttributes(
|
|
const OriginAttributes& aOriginAttributes) {
|
|
MutexAutoLock lock(mLock);
|
|
NS_ENSURE_FALSE(mFD.IsInitialized(), NS_ERROR_FAILURE);
|
|
|
|
mOriginAttributes = aOriginAttributes;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetPeerAddr(NetAddr* addr) {
|
|
// once we are in the connected state, mNetAddr will not change.
|
|
// so if we can verify that we are in the connected state, then
|
|
// we can freely access mNetAddr from any thread without being
|
|
// inside a critical section.
|
|
|
|
if (!mNetAddrIsSet) {
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::GetPeerAddr [this=%p state=%d] "
|
|
"NOT_AVAILABLE because not yet connected.",
|
|
this, mState));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
memcpy(addr, &mNetAddr, sizeof(NetAddr));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetSelfAddr(NetAddr* addr) {
|
|
// once we are in the connected state, mSelfAddr will not change.
|
|
// so if we can verify that we are in the connected state, then
|
|
// we can freely access mSelfAddr from any thread without being
|
|
// inside a critical section.
|
|
|
|
if (!mSelfAddrIsSet) {
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::GetSelfAddr [this=%p state=%d] "
|
|
"NOT_AVAILABLE because not yet connected.",
|
|
this, mState));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
memcpy(addr, &mSelfAddr, sizeof(NetAddr));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::Bind(NetAddr* aLocalAddr) {
|
|
NS_ENSURE_ARG(aLocalAddr);
|
|
|
|
MutexAutoLock lock(mLock);
|
|
if (mAttached) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mBindAddr = MakeUnique<NetAddr>();
|
|
memcpy(mBindAddr.get(), aLocalAddr, sizeof(NetAddr));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetScriptablePeerAddr(nsINetAddr** addr) {
|
|
NetAddr rawAddr;
|
|
|
|
nsresult rv;
|
|
rv = GetPeerAddr(&rawAddr);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
|
|
netaddr.forget(addr);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetScriptableSelfAddr(nsINetAddr** addr) {
|
|
NetAddr rawAddr;
|
|
|
|
nsresult rv;
|
|
rv = GetSelfAddr(&rawAddr);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
RefPtr<nsNetAddr> netaddr = new nsNetAddr(&rawAddr);
|
|
netaddr.forget(addr);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetTimeout(uint32_t type, uint32_t* value) {
|
|
NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
|
|
MutexAutoLock lock(mLock);
|
|
*value = (uint32_t)mTimeouts[type];
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::SetTimeout(uint32_t type, uint32_t value) {
|
|
NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE);
|
|
|
|
SOCKET_LOG(("nsSocketTransport::SetTimeout %p type=%u, value=%u", this, type,
|
|
value));
|
|
|
|
// truncate overly large timeout values.
|
|
{
|
|
MutexAutoLock lock(mLock);
|
|
mTimeouts[type] = (uint16_t)std::min<uint32_t>(value, UINT16_MAX);
|
|
}
|
|
PostEvent(MSG_TIMEOUT_CHANGED);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::SetReuseAddrPort(bool reuseAddrPort) {
|
|
mReuseAddrPort = reuseAddrPort;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::SetLinger(bool aPolarity, int16_t aTimeout) {
|
|
MutexAutoLock lock(mLock);
|
|
|
|
mLingerPolarity = aPolarity;
|
|
mLingerTimeout = aTimeout;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::SetQoSBits(uint8_t aQoSBits) {
|
|
// Don't do any checking here of bits. Why? Because as of RFC-4594
|
|
// several different Class Selector and Assured Forwarding values
|
|
// have been defined, but that isn't to say more won't be added later.
|
|
// In that case, any checking would be an impediment to interoperating
|
|
// with newer QoS definitions.
|
|
|
|
mQoSBits = aQoSBits;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetQoSBits(uint8_t* aQoSBits) {
|
|
*aQoSBits = mQoSBits;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetRecvBufferSize(uint32_t* aSize) {
|
|
PRFileDescAutoLock fd(this, false);
|
|
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, false);
|
|
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, false);
|
|
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, false);
|
|
if (!fd.IsInitialized()) return NS_ERROR_NOT_CONNECTED;
|
|
|
|
nsresult rv = NS_OK;
|
|
PRSocketOptionData opt;
|
|
opt.option = PR_SockOpt_SendBufferSize;
|
|
opt.value.send_buffer_size = aSize;
|
|
if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) rv = NS_ERROR_FAILURE;
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
|
|
nsresult status) {
|
|
SOCKET_LOG(("nsSocketTransport::OnLookupComplete: this=%p status %" PRIx32
|
|
".",
|
|
this, static_cast<uint32_t>(status)));
|
|
|
|
if (NS_SUCCEEDED(status)) {
|
|
mDNSRecord = do_QueryInterface(rec);
|
|
MOZ_ASSERT(mDNSRecord);
|
|
}
|
|
|
|
// flag host lookup complete for the benefit of the ResolveHost method.
|
|
mResolving = false;
|
|
nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, nullptr);
|
|
|
|
// if posting a message fails, then we should assume that the socket
|
|
// transport has been shutdown. this should never happen! if it does
|
|
// it means that the socket transport service was shutdown before the
|
|
// DNS service.
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("unable to post DNS lookup complete message");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsIInterfaceRequestor
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetInterface(const nsIID& iid, void** result) {
|
|
if (iid.Equals(NS_GET_IID(nsIDNSRecord)) ||
|
|
iid.Equals(NS_GET_IID(nsIDNSAddrRecord))) {
|
|
return mDNSRecord ? mDNSRecord->QueryInterface(iid, result)
|
|
: NS_ERROR_NO_INTERFACE;
|
|
}
|
|
return this->QueryInterface(iid, result);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetInterfaces(nsTArray<nsIID>& array) {
|
|
return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(array);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetScriptableHelper(nsIXPCScriptable** _retval) {
|
|
*_retval = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetContractID(nsACString& aContractID) {
|
|
aContractID.SetIsVoid(true);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetClassDescription(nsACString& aClassDescription) {
|
|
aClassDescription.SetIsVoid(true);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetClassID(nsCID** aClassID) {
|
|
*aClassID = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetFlags(uint32_t* aFlags) {
|
|
*aFlags = nsIClassInfo::THREADSAFE;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetConnectionFlags(uint32_t* value) {
|
|
*value = mConnectionFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::SetConnectionFlags(uint32_t value) {
|
|
SOCKET_LOG(
|
|
("nsSocketTransport::SetConnectionFlags %p flags=%u", this, value));
|
|
|
|
mConnectionFlags = value;
|
|
mIsPrivate = value & nsISocketTransport::NO_PERMANENT_STORAGE;
|
|
|
|
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, true);
|
|
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, true);
|
|
if (NS_WARN_IF(!fd.IsInitialized())) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
|
|
rv = fd.SetKeepaliveVals(mKeepaliveEnabled, mKeepaliveIdleTimeS,
|
|
mKeepaliveRetryIntervalS, mKeepaliveProbeCount);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
return NS_OK;
|
|
#else
|
|
SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform"));
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
#endif
|
|
}
|
|
|
|
#ifdef ENABLE_SOCKET_TRACING
|
|
|
|
# include <stdio.h>
|
|
# include <ctype.h>
|
|
# include "prenv.h"
|
|
|
|
static void DumpBytesToFile(const char* path, const char* header,
|
|
const char* buf, int32_t n) {
|
|
FILE* fp = fopen(path, "a");
|
|
|
|
fprintf(fp, "\n%s [%d bytes]\n", header, n);
|
|
|
|
const unsigned char* p;
|
|
while (n) {
|
|
p = (const unsigned char*)buf;
|
|
|
|
int32_t i, row_max = std::min(16, n);
|
|
|
|
for (i = 0; i < row_max; ++i) fprintf(fp, "%02x ", *p++);
|
|
for (i = row_max; i < 16; ++i) fprintf(fp, " ");
|
|
|
|
p = (const unsigned char*)buf;
|
|
for (i = 0; i < row_max; ++i, ++p) {
|
|
if (isprint(*p))
|
|
fprintf(fp, "%c", *p);
|
|
else
|
|
fprintf(fp, ".");
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
buf += row_max;
|
|
n -= row_max;
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
fclose(fp);
|
|
}
|
|
|
|
void nsSocketTransport::TraceInBuf(const char* buf, int32_t n) {
|
|
char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
|
|
if (!val || !*val) return;
|
|
|
|
nsAutoCString header;
|
|
header.AssignLiteral("Reading from: ");
|
|
header.Append(mHost);
|
|
header.Append(':');
|
|
header.AppendInt(mPort);
|
|
|
|
DumpBytesToFile(val, header.get(), buf, n);
|
|
}
|
|
|
|
void nsSocketTransport::TraceOutBuf(const char* buf, int32_t n) {
|
|
char* val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG");
|
|
if (!val || !*val) return;
|
|
|
|
nsAutoCString header;
|
|
header.AssignLiteral("Writing to: ");
|
|
header.Append(mHost);
|
|
header.Append(':');
|
|
header.AppendInt(mPort);
|
|
|
|
DumpBytesToFile(val, header.get(), buf, n);
|
|
}
|
|
|
|
#endif
|
|
|
|
static void LogNSPRError(const char* aPrefix, const void* aObjPtr) {
|
|
#if defined(DEBUG)
|
|
PRErrorCode errCode = PR_GetError();
|
|
int errLen = PR_GetErrorTextLength();
|
|
nsAutoCString errStr;
|
|
if (errLen > 0) {
|
|
errStr.SetLength(errLen);
|
|
PR_GetErrorText(errStr.BeginWriting());
|
|
}
|
|
NS_WARNING(
|
|
nsPrintfCString("%s [%p] NSPR error[0x%x] %s.",
|
|
aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode,
|
|
errLen > 0 ? errStr.BeginReading() : "<no error text>")
|
|
.get());
|
|
#endif
|
|
}
|
|
|
|
nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled(
|
|
bool aEnable) {
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()),
|
|
"Cannot enable keepalive if global pref is disabled!");
|
|
if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) {
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
PRSocketOptionData opt;
|
|
|
|
opt.option = PR_SockOpt_Keepalive;
|
|
opt.value.keep_alive = aEnable;
|
|
PRStatus status = PR_SetSocketOption(mFd, &opt);
|
|
if (NS_WARN_IF(status != PR_SUCCESS)) {
|
|
LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled",
|
|
mSocketTransport);
|
|
return ErrorAccordingToNSPR(PR_GetError());
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
static void LogOSError(const char* aPrefix, const void* aObjPtr) {
|
|
#if defined(DEBUG)
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
|
|
# ifdef XP_WIN
|
|
DWORD errCode = WSAGetLastError();
|
|
LPVOID errMessage;
|
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL, errCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR)&errMessage, 0, NULL);
|
|
# else
|
|
int errCode = errno;
|
|
char* errMessage = strerror(errno);
|
|
# endif
|
|
NS_WARNING(nsPrintfCString("%s [%p] OS error[0x%x] %s",
|
|
aPrefix ? aPrefix : "nsSocketTransport", aObjPtr,
|
|
errCode,
|
|
errMessage ? errMessage : "<no error text>")
|
|
.get());
|
|
# ifdef XP_WIN
|
|
LocalFree(errMessage);
|
|
# endif
|
|
#endif
|
|
}
|
|
|
|
/* XXX PR_SetSockOpt does not support setting keepalive values, so native
|
|
* handles and platform specific apis (setsockopt, WSAIOCtl) are used in this
|
|
* file. Requires inclusion of NSPR private/pprio.h, and platform headers.
|
|
*/
|
|
|
|
nsresult nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(
|
|
bool aEnabled, int aIdleTime, int aRetryInterval, int aProbeCount) {
|
|
#if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)
|
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
|
if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
if (NS_WARN_IF(aRetryInterval <= 0 || kMaxTCPKeepIntvl < aRetryInterval)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
PROsfd sock = PR_FileDesc2NativeHandle(mFd);
|
|
if (NS_WARN_IF(sock == -1)) {
|
|
LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals",
|
|
mSocketTransport);
|
|
return ErrorAccordingToNSPR(PR_GetError());
|
|
}
|
|
#endif
|
|
|
|
#if defined(XP_WIN)
|
|
// Windows allows idle time and retry interval to be set; NOT ping count.
|
|
struct tcp_keepalive keepalive_vals = {(u_long)aEnabled,
|
|
// Windows uses msec.
|
|
(u_long)(aIdleTime * 1000UL),
|
|
(u_long)(aRetryInterval * 1000UL)};
|
|
DWORD bytes_returned;
|
|
int err =
|
|
WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals,
|
|
sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL, NULL);
|
|
if (NS_WARN_IF(err)) {
|
|
LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport);
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
return NS_OK;
|
|
|
|
#elif defined(XP_DARWIN)
|
|
// Darwin uses sec; only supports idle time being set.
|
|
int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &aIdleTime,
|
|
sizeof(aIdleTime));
|
|
if (NS_WARN_IF(err)) {
|
|
LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE",
|
|
mSocketTransport);
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
return NS_OK;
|
|
|
|
#elif defined(XP_UNIX)
|
|
// Not all *nix OSes support the following setsockopt() options
|
|
// ... but we assume they are supported in the Android kernel;
|
|
// build errors will tell us if they are not.
|
|
# if defined(ANDROID) || defined(TCP_KEEPIDLE)
|
|
// Idle time until first keepalive probe; interval between ack'd probes;
|
|
// seconds.
|
|
int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &aIdleTime,
|
|
sizeof(aIdleTime));
|
|
if (NS_WARN_IF(err)) {
|
|
LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE",
|
|
mSocketTransport);
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
# endif
|
|
# if defined(ANDROID) || defined(TCP_KEEPINTVL)
|
|
// Interval between unack'd keepalive probes; seconds.
|
|
err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &aRetryInterval,
|
|
sizeof(aRetryInterval));
|
|
if (NS_WARN_IF(err)) {
|
|
LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL",
|
|
mSocketTransport);
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
# endif
|
|
# if defined(ANDROID) || defined(TCP_KEEPCNT)
|
|
// Number of unack'd keepalive probes before connection times out.
|
|
err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &aProbeCount,
|
|
sizeof(aProbeCount));
|
|
if (NS_WARN_IF(err)) {
|
|
LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT",
|
|
mSocketTransport);
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
# endif
|
|
return NS_OK;
|
|
#else
|
|
MOZ_ASSERT(false,
|
|
"nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals "
|
|
"called on unsupported platform!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
#endif
|
|
}
|
|
|
|
void nsSocketTransport::CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled) {
|
|
#if defined(XP_WIN)
|
|
AttachShutdownLayer(aFd);
|
|
#endif
|
|
|
|
// We use PRIntervalTime here because we need
|
|
// nsIOService::LastOfflineStateChange time and
|
|
// nsIOService::LastConectivityChange time to be atomic.
|
|
PRIntervalTime closeStarted;
|
|
if (aTelemetryEnabled) {
|
|
closeStarted = PR_IntervalNow();
|
|
}
|
|
|
|
PR_Close(aFd);
|
|
|
|
if (aTelemetryEnabled) {
|
|
SendPRBlockingTelemetry(
|
|
closeStarted, Telemetry::PRCLOSE_TCP_BLOCKING_TIME_NORMAL,
|
|
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_SHUTDOWN,
|
|
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_CONNECTIVITY_CHANGE,
|
|
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_LINK_CHANGE,
|
|
Telemetry::PRCLOSE_TCP_BLOCKING_TIME_OFFLINE);
|
|
}
|
|
}
|
|
|
|
void nsSocketTransport::SendPRBlockingTelemetry(
|
|
PRIntervalTime aStart, Telemetry::HistogramID aIDNormal,
|
|
Telemetry::HistogramID aIDShutdown,
|
|
Telemetry::HistogramID aIDConnectivityChange,
|
|
Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline) {
|
|
PRIntervalTime now = PR_IntervalNow();
|
|
if (gIOService->IsNetTearingDown()) {
|
|
Telemetry::Accumulate(aIDShutdown, PR_IntervalToMilliseconds(now - aStart));
|
|
|
|
} else if (PR_IntervalToSeconds(now - gIOService->LastConnectivityChange()) <
|
|
60) {
|
|
Telemetry::Accumulate(aIDConnectivityChange,
|
|
PR_IntervalToMilliseconds(now - aStart));
|
|
} else if (PR_IntervalToSeconds(now - gIOService->LastNetworkLinkChange()) <
|
|
60) {
|
|
Telemetry::Accumulate(aIDLinkChange,
|
|
PR_IntervalToMilliseconds(now - aStart));
|
|
|
|
} else if (PR_IntervalToSeconds(now - gIOService->LastOfflineStateChange()) <
|
|
60) {
|
|
Telemetry::Accumulate(aIDOffline, PR_IntervalToMilliseconds(now - aStart));
|
|
} else {
|
|
Telemetry::Accumulate(aIDNormal, PR_IntervalToMilliseconds(now - aStart));
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::SetFastOpenCallback(TCPFastOpen* aFastOpen) {
|
|
mFastOpenCallback = aFastOpen;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsSocketTransport::GetFirstRetryError(nsresult* aFirstRetryError) {
|
|
*aFirstRetryError = mFirstRetryError;
|
|
return NS_OK;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|