/* -*- 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/. */
#ifdefined(FUZZING) # include "FuzzyLayer.h" # include "FuzzySocketControl.h" # include "mozilla/StaticPrefs_fuzzing.h" #endif
#ifdefined(XP_WIN) # include "ShutdownLayer.h" #endif
/* Following inclusions required for keepalive config not supported by NSPR. */ #include"private/pprio.h" #ifdefined(XP_WIN) # include <winsock2.h> # include <mstcpip.h> #elifdefined(XP_UNIX) # include <errno.h> # include <netinet/tcp.h> #endif /* End keepalive config inclusions. */
// #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; constchar* 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;
}
// 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");
// 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);
}
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;
// 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);
// 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;
}
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();
}
// 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");
// 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);
}
// 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);
// only send this notification if we have indeed written some data. // see bug 196827 for an example of why this is important. if ((n > 0)) {
mTransport->SendStatus(NS_NET_STATUS_SENDING_TO);
}
// 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();
}
constchar* 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;
}
}
// 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++]);
}
// 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;
}
}
}
if (!name[0] && length > 1) { // name is abstract address name that is supported on Linux only # ifdefined(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;
if (!mProxyHost.IsEmpty()) { if (!mProxyTransparent || mProxyTransparentResolvesHost) { #ifdefined(XP_UNIX)
MOZ_ASSERT(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL, "Unix domain sockets can't be used with proxies"); #endif // When not resolving mHost locally, we still want to ensure that // it only contains valid characters. See bug 304904 for details. // Sometimes the end host is not yet known and mHost is * if (!net_IsValidDNSHost(mHost) && !mHost.EqualsLiteral("*")) {
SOCKET_LOG((" invalid hostname %s\n", mHost.get())); return NS_ERROR_UNKNOWN_HOST;
}
} if (mProxyTransparentResolvesHost) { // Name resolution is done on the server side. Just pretend // client resolution is complete, this will get picked up later. // since we don't need to do DNS now, we bypass the resolving // step by initializing mNetAddr to an empty address, but we // must keep the port. The SOCKS IO layer will use the hostname // we send it when it's created, rather than the empty address // we send with the connect call.
mState = STATE_RESOLVING;
mNetAddr.raw.family = AF_INET;
mNetAddr.inet.port = htons(SocketPort());
mNetAddr.inet.ip = htonl(INADDR_ANY); return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr);
}
}
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;
}
// 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;
}
if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT) {
controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT;
}
if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE) {
controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE;
}
if (mConnectionFlags & nsISocketTransport::BE_CONSERVATIVE) {
controlFlags |= nsISocketProvider::BE_CONSERVATIVE;
}
if (mConnectionFlags & nsISocketTransport::DONT_TRY_ECH) {
controlFlags |= nsISocketProvider::DONT_TRY_ECH;
}
if (mConnectionFlags & nsISocketTransport::IS_RETRY) {
controlFlags |= nsISocketProvider::IS_RETRY;
}
if (mConnectionFlags &
nsISocketTransport::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT) {
controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
}
if (mConnectionFlags & nsISocketTransport::IS_SPECULATIVE_CONNECTION) {
controlFlags |= nsISocketProvider::IS_SPECULATIVE_CONNECTION;
}
if (mResolvedByTRR) {
controlFlags |= nsISocketProvider::USED_PRIVATE_DNS;
}
// by setting host to mOriginHost, instead of mHost we send the // SocketProvider (e.g. PSM) the origin hostname but can still do DNS // on an explicit alternate service host name constchar* host = mOriginHost.get();
int32_t port = (int32_t)mOriginPort;
uint32_t i; for (i = 0; i < mTypes.Length(); ++i) {
nsCOMPtr<nsISocketProvider> provider;
SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i].get()));
rv = spserv->GetSocketProvider(mTypes[i].get(), getter_AddRefs(provider)); if (NS_FAILED(rv)) break;
nsCOMPtr<nsITLSSocketControl> tlsSocketControl; if (i == 0) { // if this is the first type, we'll want the // service to allocate a new socket
// Most layers _ESPECIALLY_ PSM want the origin name here as they // will use it for secure checks, etc.. and any connection management // differences between the origin name and the routed name can be // taken care of via DNS. However, SOCKS is a special case as there is // no DNS. in the case of SOCKS and PSM the PSM is a separate layer // and receives the origin name. constchar* 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)
if (NS_SUCCEEDED(rv) && !fd) {
MOZ_ASSERT_UNREACHABLE( "NewSocket succeeded but failed to " "create a PRFileDesc");
rv = NS_ERROR_UNEXPECTED;
}
} else { // the socket has already been allocated, // so we just want the service to add itself // to the stack (such as pushing an io layer)
rv = provider->AddToSocket(mNetAddr.raw.family, host, port, proxyInfo,
mOriginAttributes, controlFlags, mTlsFlags, fd,
getter_AddRefs(tlsSocketControl));
}
// controlFlags = 0; not used below this point... if (NS_FAILED(rv)) break;
// if the service was ssl or starttls, we want to hold onto the socket // info bool isSSL = mTypes[i].EqualsLiteral("ssl"); if (isSSL || mTypes[i].EqualsLiteral("starttls")) { // remember security info
{
MutexAutoLock lock(mLock);
mTLSSocketControl = tlsSocketControl;
SOCKET_LOG((" [tlsSocketControl=%p callbacks=%p]\n",
mTLSSocketControl.get(), mCallbacks.get()));
} // remember if socket type is SSL so we can ProxyStartSSL if need be.
usingSSL = isSSL;
} elseif (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;
}
staticbool ShouldBlockAddress(const NetAddr& aAddr) { if (!xpc::AreNonLocalConnectionsDisabled()) { returnfalse;
}
if (gIOService->IsNetTearingDown()) { return NS_ERROR_ABORT;
}
// Since https://github.com/whatwg/fetch/pull/1763, // we need to disable access to 0.0.0.0 for non-test purposes if (mNetAddr.IsIPAddrAny() && !mProxyTransparentResolvesHost) { if (StaticPrefs::network_socket_ip_addr_any_disabled()) {
mozilla::glean::networking::http_ip_addr_any_count
.Get("blocked_requests"_ns)
.Add(1);
SOCKET_LOG(("connection refused NS_ERROR_CONNECTION_REFUSED\n")); return NS_ERROR_CONNECTION_REFUSED;
}
if (gIOService->IsOffline()) { if (StaticPrefs::network_disable_localhost_when_offline() || !isLocal) { return NS_ERROR_OFFLINE;
}
} elseif (!isLocal) { #ifdef DEBUG // all IP networking has to be done from the parent if (NS_SUCCEEDED(mCondition) && ((mNetAddr.raw.family == AF_INET) ||
(mNetAddr.raw.family == AF_INET6))) {
MOZ_ASSERT(!IsNeckoChild());
} #endif
if (NS_SUCCEEDED(mCondition) && ShouldBlockAddress(mNetAddr)) {
nsAutoCString ipaddr;
RefPtr<nsNetAddr> netaddr = new nsNetAddr(&mNetAddr);
netaddr->GetAddress(ipaddr);
fprintf_stderr(
stderr, "FATAL ERROR: Non-local network connections are disabled and a " "connection " "attempt to %s (%s) was made.\nYou should only access hostnames " "available via the test networking proxy (if running mochitests) " "or from a test-specific httpd.js server (if running xpcshell " "tests). " "Browser services should be disabled or redirected to a local " "server.\n",
mHost.get(), ipaddr.get()); return NS_ERROR_NON_LOCAL_CONNECTION_REFUSED;
}
}
// Hosts/Proxy Hosts that are Local IP Literals should not be speculatively // connected - Bug 853423. if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 &&
mNetAddr.IsIPAddrLocal()) { if (SOCKET_LOG_ENABLED()) {
nsAutoCString netAddrCString;
netAddrCString.SetLength(kIPv6CStrBufSize); if (!mNetAddr.ToStringBuffer(netAddrCString.BeginWriting(),
kIPv6CStrBufSize)) {
netAddrCString = ""_ns;
}
SOCKET_LOG(
("nsSocketTransport::InitiateSocket skipping " "speculative connection for host [%s:%d] proxy " "[%s:%d] with Local IP address [%s]",
mHost.get(), mPort, mProxyHost.get(), mProxyPort,
netAddrCString.get()));
}
mCondition = NS_ERROR_CONNECTION_REFUSED;
OnSocketDetached(nullptr); return mCondition;
}
// // find out if it is going to be ok to attach another socket to the STS. // if not then we have to wait for the STS to tell us that it is ok. // the notification is asynchronous, which means that when we could be // in a race to call AttachSocket once notified. for this reason, when // we get notified, we just re-enter this function. as a result, we are // sure to ask again before calling AttachSocket. in this way we deal // with the race condition. though it isn't the most elegant solution, // it is far simpler than trying to build a system that would guarantee // FIFO ordering (which wouldn't even be that valuable IMO). see bug // 194402 for more info. // if (!mSocketTransportService->CanAttachSocket()) {
nsCOMPtr<nsIRunnable> event = new nsSocketEvent(this, MSG_RETRY_INIT_SOCKET); if (!event) return NS_ERROR_OUT_OF_MEMORY; return mSocketTransportService->NotifyWhenCanAttachSocket(event);
}
// // if we already have a connected socket, then just attach and return. //
{
MutexAutoLock lock(mLock); if (mFD.IsInitialized()) {
rv = mSocketTransportService->AttachSocket(mFD, this); if (NS_SUCCEEDED(rv)) mAttached = true; return rv;
}
}
// // create new socket fd, push io layers, etc. //
PRFileDesc* fd; bool proxyTransparent; bool usingSSL;
#ifdef FUZZING if (StaticPrefs::fuzzing_necko_enabled()) {
rv = AttachFuzzyIOLayer(fd); if (NS_FAILED(rv)) {
SOCKET_LOG(("Failed to attach fuzzing IOLayer [rv=%" PRIx32 "].\n", static_cast<uint32_t>(rv))); return rv;
}
SOCKET_LOG(("Successfully attached fuzzing IOLayer.\n"));
if (usingSSL) {
mTLSSocketControl = new FuzzySocketControl();
}
} #endif
PRStatus status;
// Make the socket non-blocking...
PRSocketOptionData opt;
opt.option = PR_SockOpt_Nonblocking;
opt.value.non_blocking = true;
status = PR_SetSocketOption(fd, &opt);
NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking");
if (mReuseAddrPort) {
SOCKET_LOG((" Setting port/addr reuse socket options\n"));
// Set ReuseAddr for TCP sockets to enable having several // sockets bound to same local IP and port
PRSocketOptionData opt_reuseaddr;
opt_reuseaddr.option = PR_SockOpt_Reuseaddr;
opt_reuseaddr.value.reuse_addr = PR_TRUE;
status = PR_SetSocketOption(fd, &opt_reuseaddr); if (status != PR_SUCCESS) {
SOCKET_LOG((" Couldn't set reuse addr socket option: %d\n", status));
}
// And also set ReusePort for platforms supporting this socket option
PRSocketOptionData opt_reuseport;
opt_reuseport.option = PR_SockOpt_Reuseport;
opt_reuseport.value.reuse_port = PR_TRUE;
status = PR_SetSocketOption(fd, &opt_reuseport); if (status != PR_SUCCESS &&
PR_GetError() != PR_OPERATION_NOT_SUPPORTED_ERROR) {
SOCKET_LOG((" Couldn't set reuse port socket option: %d\n", status));
}
}
// disable the nagle algorithm - if we rely on it to coalesce writes into // full packets the final packet of a multi segment POST/PUT or pipeline // sequence is delayed a full rtt
opt.option = PR_SockOpt_NoDelay;
opt.value.no_delay = true;
PR_SetSocketOption(fd, &opt);
// if the network.tcp.sendbuffer preference is set, use it to size SO_SNDBUF // The Windows default of 8KB is too small and as of vista sp1, autotuning // only applies to receive window
int32_t sndBufferSize;
mSocketTransportService->GetSendBufferSize(&sndBufferSize); if (sndBufferSize > 0) {
opt.option = PR_SockOpt_SendBufferSize;
opt.value.send_buffer_size = sndBufferSize;
PR_SetSocketOption(fd, &opt);
}
#ifdefined(XP_WIN) // The linger is turned off by default. This is not a hard close, but // closesocket should return immediately and operating system tries to send // remaining data for certain, implementation specific, amount of time. // https://msdn.microsoft.com/en-us/library/ms739165.aspx // // Turn the linger option on an set the interval to 0. This will cause hard // close of the socket.
opt.option = PR_SockOpt_Linger;
opt.value.linger.polarity = 1;
opt.value.linger.linger = 0;
PR_SetSocketOption(fd, &opt); #endif
// up to here, mFD will only be accessed by us
// assign mFD so that we can properly handle OnSocketDetached before we've // established a connection.
{
MutexAutoLock lock(mLock); // inform socket transport about this newly created socket...
rv = mSocketTransportService->AttachSocket(fd, this); if (NS_FAILED(rv)) {
CloseSocket(
fd, mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()); return rv;
}
mAttached = true;
// // Initiate the connect() to the host... //
PRNetAddr prAddr;
memset(&prAddr, 0, sizeof(prAddr));
{ if (mBindAddr) {
MutexAutoLock lock(mLock);
NetAddrToPRNetAddr(mBindAddr.get(), &prAddr);
status = PR_Bind(fd, &prAddr); if (status != PR_SUCCESS) { return NS_ERROR_FAILURE;
}
mBindAddr = nullptr;
}
}
NetAddrToPRNetAddr(&mNetAddr, &prAddr);
#ifdef XP_WIN // Find the real tcp socket and set non-blocking once again! // Bug 1158189.
PRFileDesc* bottom = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER); if (bottom) {
PROsfd osfd = PR_FileDesc2NativeHandle(bottom);
u_long nonblocking = 1; if (ioctlsocket(osfd, FIONBIO, &nonblocking) != 0) {
NS_WARNING("Socket could not be set non-blocking!"); return NS_ERROR_FAILURE;
}
} #endif
if (mTLSSocketControl) { if (!mEchConfig.IsEmpty() &&
!(mConnectionFlags & (DONT_TRY_ECH | BE_CONSERVATIVE))) {
SOCKET_LOG(("nsSocketTransport::InitiateSocket set echconfig."));
rv = mTLSSocketControl->SetEchConfig(mEchConfig); if (NS_FAILED(rv)) { return rv;
}
mEchConfigUsed = true;
}
}
// We use PRIntervalTime here because we need // nsIOService::LastOfflineStateChange time and // nsIOService::LastConectivityChange time to be atomic.
PRIntervalTime connectStarted = 0; if (gSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
connectStarted = PR_IntervalNow();
}
if (Telemetry::CanRecordPrereleaseData() ||
Telemetry::CanRecordReleaseData()) { if (NS_FAILED(AttachNetworkDataCountLayer(fd))) {
SOCKET_LOG(
("nsSocketTransport::InitiateSocket " "AttachNetworkDataCountLayer failed [this=%p]\n", this));
}
} if (StaticPrefs::network_socket_attach_mock_network_layer() &&
xpc::AreNonLocalConnectionsDisabled()) { if (NS_FAILED(AttachMockNetworkLayer(fd))) {
SOCKET_LOG(
("nsSocketTransport::InitiateSocket " "AttachMockNetworkLayer failed [this=%p]\n", this));
}
}
bool connectCalled = true; // This is only needed for telemetry.
status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT);
PRErrorCode code = PR_GetError(); if (status == PR_SUCCESS) {
PR_SetFDInheritable(fd, false);
}
if (status == PR_SUCCESS) { // // we are connected! //
OnSocketConnected();
} else { #ifdefined(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... //
} elseif (PR_IS_CONNECTED_ERROR == code) { // // we are connected! //
OnSocketConnected();
if (mTLSSocketControl && !mProxyHost.IsEmpty() && proxyTransparent &&
usingSSL) { // if the connection phase is finished, and the ssl layer has // been pushed, and we were proxying (transparently; ie. nothing // has to happen in the protocol layer above us), it's time for // the ssl to start doing it's thing.
SOCKET_LOG((" calling ProxyStartSSL()\n"));
mTLSSocketControl->ProxyStartSSL(); // XXX what if we were forced to poll on the socket for a successful // connection... wouldn't we need to call ProxyStartSSL after a call // to PR_ConnectContinue indicates that we are connected? // // XXX this appears to be what the old socket transport did. why // isn't this broken?
}
} // // A SOCKS request was rejected; get the actual error code from // the OS error // elseif (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);
}
if (mDoNotRetryToConnect) {
SOCKET_LOG(
("nsSocketTransport::RecoverFromError do not retry because " "mDoNotRetryToConnect is set [this=%p]\n", this)); returnfalse;
}
#ifdefined(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) returnfalse; #endif
// can only recover from errors in these states if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) {
SOCKET_LOG((" not in a recoverable state")); returnfalse;
}
nsresult rv;
#ifdef DEBUG
{
MutexAutoLock lock(mLock);
NS_ASSERTION(!mFDconnected, "socket should not be connected");
} #endif
// all connection failures need to be reported to DNS so that the next // time we will use a different address if available. // NS_BASE_STREAM_CLOSED is not an actual connection failure, so don't report // to DNS. if (mState == STATE_CONNECTING && mDNSRecord &&
mCondition != NS_BASE_STREAM_CLOSED) {
mDNSRecord->ReportUnusable(SocketPort());
}
if ((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);
}
} elseif (mNetAddr.raw.family == AF_INET6) { if (mSocketTransportService->IsTelemetryEnabledAndNotSleepPhase()) {
Telemetry::Accumulate(Telemetry::IPV4_AND_IPV6_ADDRESS_CONNECTIVITY,
UNSUCCESSFUL_CONNECTING_TO_IPV6_ADDRESS);
}
}
}
if (mConnectionFlags & RETRY_WITH_DIFFERENT_IP_FAMILY &&
mCondition == NS_ERROR_UNKNOWN_HOST && mState == STATE_RESOLVING &&
!mProxyTransparentResolvesHost) {
SOCKET_LOG((" trying lookup again with opposite ip family\n"));
mConnectionFlags ^= (DISABLE_IPV6 | DISABLE_IPV4);
mConnectionFlags &= ~RETRY_WITH_DIFFERENT_IP_FAMILY; // This will tell the consuming half-open to reset preference on the // connection entry
mResetFamilyPreference = true;
tryAgain = true;
}
// try next ip address only if past the resolver stage... if (mState == STATE_CONNECTING && mDNSRecord) {
nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
mDNSRecord->IsTRR(&mResolvedByTRR);
mDNSRecord->GetEffectiveTRRMode(&mEffectiveTRRMode);
mDNSRecord->GetTrrSkipReason(&mTRRSkipReason); if (NS_SUCCEEDED(rv)) {
SOCKET_LOG((" trying again with next ip address\n"));
tryAgain = true;
} elseif (mExternalDNSResolution) {
mRetryDnsIfPossible = true; bool trrEnabled;
mDNSRecord->IsTRR(&trrEnabled); // Bug 1648147 - If the server responded with `0.0.0.0` or `::` then we // should intentionally not fallback to regular DNS. if (trrEnabled && !StaticPrefs::network_trr_fallback_on_zero_response() &&
((mNetAddr.raw.family == AF_INET && mNetAddr.inet.ip == 0) ||
(mNetAddr.raw.family == AF_INET6 && mNetAddr.inet6.ip.u64[0] == 0 &&
mNetAddr.inet6.ip.u64[1] == 0))) {
SOCKET_LOG((" TRR returned 0.0.0.0 and there are no other IPs"));
mRetryDnsIfPossible = false;
}
} elseif (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;
} elseif (!(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"));
} elseif (trrEnabled) {
nsIRequest::TRRMode trrMode = nsIRequest::TRR_DEFAULT_MODE;
mDNSRecord->GetEffectiveTRRMode(&trrMode); // If current trr mode is trr only, we should not retry. if (trrMode != nsIRequest::TRR_ONLY_MODE) { // Drop state to closed. This will trigger a new round of // DNS resolving. Bypass the cache this time since the // cached data came from TRR and failed already!
SOCKET_LOG((" failed to connect with TRR enabled, try w/o\n"));
mState = STATE_CLOSED;
mConnectionFlags |= DISABLE_TRR | BYPASS_CACHE | REFRESH_CACHE;
tryAgain = true;
}
}
}
}
// prepare to try again. if (tryAgain) {
uint32_t msg;
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??
} elseif (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??
} elseif (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"));
// Set the m*AddrIsSet flags only when state has reached TRANSFERRING // because we need to make sure its value does not change due to failover
mNetAddrIsSet = true;
// assign mFD (must do this within the transport lock), but take care not // to trample over mFDref if mFD is already set.
{
MutexAutoLock lock(mLock);
NS_ASSERTION(mFD.IsInitialized(), "no socket");
NS_ASSERTION(mFDref == 1, "wrong socket ref count");
SetSocketName(mFD);
mFDconnected = true;
mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE];
}
// Ensure keepalive is configured correctly if previously enabled. if (mKeepaliveEnabled) {
nsresult rv = SetKeepaliveEnabledInternal(true); if (NS_WARN_IF(NS_FAILED(rv))) {
SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%" PRIx32 "]", static_cast<uint32_t>(rv)));
}
}
SendStatus(NS_NET_STATUS_CONNECTED_TO);
}
void nsSocketTransport::SetSocketName(PRFileDesc* fd) {
MOZ_ASSERT(OnSocketThread(), "not on socket thread"); if (mSelfAddrIsSet) { return;
}
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");
}
}
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;
}
}