michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set ts=4 sw=4 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef MOZ_LOGGING michael@0: #define FORCE_PR_LOG michael@0: #endif michael@0: michael@0: #include "nsSocketTransport2.h" michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsIOService.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsNetSegmentUtils.h" michael@0: #include "nsNetAddr.h" michael@0: #include "nsTransportUtils.h" michael@0: #include "nsProxyInfo.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "plstr.h" michael@0: #include "prerr.h" michael@0: #include "NetworkActivityMonitor.h" michael@0: #include "mozilla/VisualEventTracer.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsISocketProviderService.h" michael@0: #include "nsISocketProvider.h" michael@0: #include "nsISSLSocketControl.h" michael@0: #include "nsINSSErrorsService.h" michael@0: #include "nsIPipe.h" michael@0: #include "nsIProgrammingLanguage.h" michael@0: #include "nsIClassInfoImpl.h" michael@0: #include "nsURLHelper.h" michael@0: #include "nsIDNSService.h" michael@0: #include "nsIDNSRecord.h" michael@0: #include "nsICancelable.h" michael@0: #include michael@0: michael@0: #include "nsPrintfCString.h" michael@0: michael@0: #if defined(XP_WIN) michael@0: #include "nsNativeConnectionHelper.h" michael@0: #endif michael@0: michael@0: /* Following inclusions required for keepalive config not supported by NSPR. */ michael@0: #include "private/pprio.h" michael@0: #if defined(XP_WIN) michael@0: #include michael@0: #include michael@0: #elif defined(XP_UNIX) michael@0: #include michael@0: #include michael@0: #endif michael@0: /* End keepalive config inclusions. */ michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::net; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static NS_DEFINE_CID(kSocketProviderServiceCID, NS_SOCKETPROVIDERSERVICE_CID); michael@0: static NS_DEFINE_CID(kDNSServiceCID, NS_DNSSERVICE_CID); michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsSocketEvent : public nsRunnable michael@0: { michael@0: public: michael@0: nsSocketEvent(nsSocketTransport *transport, uint32_t type, michael@0: nsresult status = NS_OK, nsISupports *param = nullptr) michael@0: : mTransport(transport) michael@0: , mType(type) michael@0: , mStatus(status) michael@0: , mParam(param) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: mTransport->OnSocketEvent(mType, mStatus, mParam); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mTransport; michael@0: michael@0: uint32_t mType; michael@0: nsresult mStatus; michael@0: nsCOMPtr mParam; michael@0: }; michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: //#define TEST_CONNECT_ERRORS michael@0: #ifdef TEST_CONNECT_ERRORS michael@0: #include michael@0: static PRErrorCode RandomizeConnectError(PRErrorCode code) michael@0: { michael@0: // michael@0: // To test out these errors, load http://www.yahoo.com/. It should load michael@0: // correctly despite the random occurrence of these errors. michael@0: // michael@0: int n = rand(); michael@0: if (n > RAND_MAX/2) { michael@0: struct { michael@0: PRErrorCode err_code; michael@0: const char *err_name; michael@0: } michael@0: errors[] = { michael@0: // michael@0: // These errors should be recoverable provided there is another michael@0: // IP address in mDNSRecord. michael@0: // michael@0: { PR_CONNECT_REFUSED_ERROR, "PR_CONNECT_REFUSED_ERROR" }, michael@0: { PR_CONNECT_TIMEOUT_ERROR, "PR_CONNECT_TIMEOUT_ERROR" }, michael@0: // michael@0: // This error will cause this socket transport to error out; michael@0: // however, if the consumer is HTTP, then the HTTP transaction michael@0: // should be restarted when this error occurs. michael@0: // michael@0: { PR_CONNECT_RESET_ERROR, "PR_CONNECT_RESET_ERROR" }, michael@0: }; michael@0: n = n % (sizeof(errors)/sizeof(errors[0])); michael@0: code = errors[n].err_code; michael@0: SOCKET_LOG(("simulating NSPR error %d [%s]\n", code, errors[n].err_name)); michael@0: } michael@0: return code; michael@0: } michael@0: #endif michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static bool michael@0: IsNSSErrorCode(PRErrorCode code) michael@0: { michael@0: return michael@0: ((code >= nsINSSErrorsService::NSS_SEC_ERROR_BASE) && michael@0: (code < nsINSSErrorsService::NSS_SEC_ERROR_LIMIT)) michael@0: || michael@0: ((code >= nsINSSErrorsService::NSS_SSL_ERROR_BASE) && michael@0: (code < nsINSSErrorsService::NSS_SSL_ERROR_LIMIT)); michael@0: } michael@0: michael@0: // this logic is duplicated from the implementation of michael@0: // nsINSSErrorsService::getXPCOMFromNSSError michael@0: // It might have been better to implement that interface here... michael@0: static nsresult michael@0: GetXPCOMFromNSSError(PRErrorCode code) michael@0: { michael@0: // XXX Don't make up nsresults, it's supposed to be an enum (bug 778113) michael@0: return (nsresult)NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, michael@0: -1 * code); michael@0: } michael@0: michael@0: nsresult michael@0: ErrorAccordingToNSPR(PRErrorCode errorCode) michael@0: { michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: switch (errorCode) { michael@0: case PR_WOULD_BLOCK_ERROR: michael@0: rv = NS_BASE_STREAM_WOULD_BLOCK; michael@0: break; michael@0: case PR_CONNECT_ABORTED_ERROR: michael@0: case PR_CONNECT_RESET_ERROR: michael@0: rv = NS_ERROR_NET_RESET; michael@0: break; michael@0: case PR_END_OF_FILE_ERROR: // XXX document this correlation michael@0: rv = NS_ERROR_NET_INTERRUPT; michael@0: break; michael@0: case PR_CONNECT_REFUSED_ERROR: michael@0: // We lump the following NSPR codes in with PR_CONNECT_REFUSED_ERROR. We michael@0: // could get better diagnostics by adding distinct XPCOM error codes for michael@0: // each of these, but there are a lot of places in Gecko that check michael@0: // specifically for NS_ERROR_CONNECTION_REFUSED, all of which would need to michael@0: // be checked. michael@0: case PR_NETWORK_UNREACHABLE_ERROR: michael@0: case PR_HOST_UNREACHABLE_ERROR: michael@0: case PR_ADDRESS_NOT_AVAILABLE_ERROR: michael@0: // Treat EACCES as a soft error since (at least on Linux) connect() returns michael@0: // EACCES when an IPv6 connection is blocked by a firewall. See bug 270784. michael@0: case PR_NO_ACCESS_RIGHTS_ERROR: michael@0: rv = NS_ERROR_CONNECTION_REFUSED; michael@0: break; michael@0: case PR_ADDRESS_NOT_SUPPORTED_ERROR: michael@0: rv = NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; michael@0: break; michael@0: case PR_IO_TIMEOUT_ERROR: michael@0: case PR_CONNECT_TIMEOUT_ERROR: michael@0: rv = NS_ERROR_NET_TIMEOUT; michael@0: break; michael@0: case PR_OUT_OF_MEMORY_ERROR: michael@0: // These really indicate that the descriptor table filled up, or that the michael@0: // kernel ran out of network buffers - but nobody really cares which part of michael@0: // the system ran out of memory. michael@0: case PR_PROC_DESC_TABLE_FULL_ERROR: michael@0: case PR_SYS_DESC_TABLE_FULL_ERROR: michael@0: case PR_INSUFFICIENT_RESOURCES_ERROR: michael@0: rv = NS_ERROR_OUT_OF_MEMORY; michael@0: break; michael@0: case PR_ADDRESS_IN_USE_ERROR: michael@0: rv = NS_ERROR_SOCKET_ADDRESS_IN_USE; michael@0: break; michael@0: // These filename-related errors can arise when using Unix-domain sockets. michael@0: case PR_FILE_NOT_FOUND_ERROR: michael@0: rv = NS_ERROR_FILE_NOT_FOUND; michael@0: break; michael@0: case PR_IS_DIRECTORY_ERROR: michael@0: rv = NS_ERROR_FILE_IS_DIRECTORY; michael@0: break; michael@0: case PR_LOOP_ERROR: michael@0: rv = NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; michael@0: break; michael@0: case PR_NAME_TOO_LONG_ERROR: michael@0: rv = NS_ERROR_FILE_NAME_TOO_LONG; michael@0: break; michael@0: case PR_NO_DEVICE_SPACE_ERROR: michael@0: rv = NS_ERROR_FILE_NO_DEVICE_SPACE; michael@0: break; michael@0: case PR_NOT_DIRECTORY_ERROR: michael@0: rv = NS_ERROR_FILE_NOT_DIRECTORY; michael@0: break; michael@0: case PR_READ_ONLY_FILESYSTEM_ERROR: michael@0: rv = NS_ERROR_FILE_READ_ONLY; michael@0: break; michael@0: default: michael@0: if (IsNSSErrorCode(errorCode)) michael@0: rv = GetXPCOMFromNSSError(errorCode); michael@0: break; michael@0: michael@0: // NSPR's socket code can return these, but they're not worth breaking out michael@0: // into their own error codes, distinct from NS_ERROR_FAILURE: michael@0: // michael@0: // PR_BAD_DESCRIPTOR_ERROR michael@0: // PR_INVALID_ARGUMENT_ERROR michael@0: // PR_NOT_SOCKET_ERROR michael@0: // PR_NOT_TCP_SOCKET_ERROR michael@0: // These would indicate a bug internal to the component. michael@0: // michael@0: // PR_PROTOCOL_NOT_SUPPORTED_ERROR michael@0: // This means that we can't use the given "protocol" (like michael@0: // IPPROTO_TCP or IPPROTO_UDP) with a socket of the given type. As michael@0: // above, this indicates an internal bug. michael@0: // michael@0: // PR_IS_CONNECTED_ERROR michael@0: // This indicates that we've applied a system call like 'bind' or michael@0: // 'connect' to a socket that is already connected. The socket michael@0: // components manage each file descriptor's state, and in some cases michael@0: // handle this error result internally. We shouldn't be returning michael@0: // this to our callers. michael@0: // michael@0: // PR_IO_ERROR michael@0: // This is so vague that NS_ERROR_FAILURE is just as good. michael@0: } michael@0: SOCKET_LOG(("ErrorAccordingToNSPR [in=%d out=%x]\n", errorCode, rv)); michael@0: return rv; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // socket input stream impl michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsSocketInputStream::nsSocketInputStream(nsSocketTransport *trans) michael@0: : mTransport(trans) michael@0: , mReaderRefCnt(0) michael@0: , mCondition(NS_OK) michael@0: , mCallbackFlags(0) michael@0: , mByteCount(0) michael@0: { michael@0: } michael@0: michael@0: nsSocketInputStream::~nsSocketInputStream() michael@0: { michael@0: } michael@0: michael@0: // called on the socket transport thread... michael@0: // michael@0: // condition : failure code if socket has been closed michael@0: // michael@0: void michael@0: nsSocketInputStream::OnSocketReady(nsresult condition) michael@0: { michael@0: SOCKET_LOG(("nsSocketInputStream::OnSocketReady [this=%p cond=%x]\n", michael@0: this, condition)); michael@0: michael@0: NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: michael@0: nsCOMPtr callback; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: // update condition, but be careful not to erase an already michael@0: // existing error condition. michael@0: if (NS_SUCCEEDED(mCondition)) michael@0: mCondition = condition; michael@0: michael@0: // ignore event if only waiting for closure and not closed. michael@0: if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { michael@0: callback = mCallback; michael@0: mCallback = nullptr; michael@0: mCallbackFlags = 0; michael@0: } michael@0: } michael@0: michael@0: if (callback) michael@0: callback->OnInputStreamReady(this); michael@0: } michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(nsSocketInputStream, michael@0: nsIInputStream, michael@0: nsIAsyncInputStream) michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsSocketInputStream::AddRef() michael@0: { michael@0: ++mReaderRefCnt; michael@0: return mTransport->AddRef(); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsSocketInputStream::Release() michael@0: { michael@0: if (--mReaderRefCnt == 0) michael@0: Close(); michael@0: return mTransport->Release(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketInputStream::Close() michael@0: { michael@0: return CloseWithStatus(NS_BASE_STREAM_CLOSED); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketInputStream::Available(uint64_t *avail) michael@0: { michael@0: SOCKET_LOG(("nsSocketInputStream::Available [this=%p]\n", this)); michael@0: michael@0: *avail = 0; michael@0: michael@0: PRFileDesc *fd; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: if (NS_FAILED(mCondition)) michael@0: return mCondition; michael@0: michael@0: fd = mTransport->GetFD_Locked(); michael@0: if (!fd) michael@0: return NS_OK; michael@0: } michael@0: michael@0: // cannot hold lock while calling NSPR. (worried about the fact that PSM michael@0: // synchronously proxies notifications over to the UI thread, which could michael@0: // mistakenly try to re-enter this code.) michael@0: int32_t n = PR_Available(fd); michael@0: michael@0: // PSM does not implement PR_Available() so do a best approximation of it michael@0: // with MSG_PEEK michael@0: if ((n == -1) && (PR_GetError() == PR_NOT_IMPLEMENTED_ERROR)) { michael@0: char c; michael@0: michael@0: n = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0); michael@0: SOCKET_LOG(("nsSocketInputStream::Available [this=%p] " michael@0: "using PEEK backup n=%d]\n", this, n)); michael@0: } michael@0: michael@0: nsresult rv; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: mTransport->ReleaseFD_Locked(fd); michael@0: michael@0: if (n >= 0) michael@0: *avail = n; michael@0: else { michael@0: PRErrorCode code = PR_GetError(); michael@0: if (code == PR_WOULD_BLOCK_ERROR) michael@0: return NS_OK; michael@0: mCondition = ErrorAccordingToNSPR(code); michael@0: } michael@0: rv = mCondition; michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: mTransport->OnInputClosed(rv); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketInputStream::Read(char *buf, uint32_t count, uint32_t *countRead) michael@0: { michael@0: SOCKET_LOG(("nsSocketInputStream::Read [this=%p count=%u]\n", this, count)); michael@0: michael@0: *countRead = 0; michael@0: michael@0: PRFileDesc* fd = nullptr; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: if (NS_FAILED(mCondition)) michael@0: return (mCondition == NS_BASE_STREAM_CLOSED) ? NS_OK : mCondition; michael@0: michael@0: fd = mTransport->GetFD_Locked(); michael@0: if (!fd) michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: michael@0: SOCKET_LOG((" calling PR_Read [count=%u]\n", count)); michael@0: michael@0: // cannot hold lock while calling NSPR. (worried about the fact that PSM michael@0: // synchronously proxies notifications over to the UI thread, which could michael@0: // mistakenly try to re-enter this code.) michael@0: int32_t n = PR_Read(fd, buf, count); michael@0: michael@0: SOCKET_LOG((" PR_Read returned [n=%d]\n", n)); michael@0: michael@0: nsresult rv = NS_OK; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: #ifdef ENABLE_SOCKET_TRACING michael@0: if (n > 0) michael@0: mTransport->TraceInBuf(buf, n); michael@0: #endif michael@0: michael@0: mTransport->ReleaseFD_Locked(fd); michael@0: michael@0: if (n > 0) michael@0: mByteCount += (*countRead = n); michael@0: else if (n < 0) { michael@0: PRErrorCode code = PR_GetError(); michael@0: if (code == PR_WOULD_BLOCK_ERROR) michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: mCondition = ErrorAccordingToNSPR(code); michael@0: } michael@0: rv = mCondition; michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: mTransport->OnInputClosed(rv); michael@0: michael@0: // only send this notification if we have indeed read some data. michael@0: // see bug 196827 for an example of why this is important. michael@0: if (n > 0) michael@0: mTransport->SendStatus(NS_NET_STATUS_RECEIVING_FROM); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure, michael@0: uint32_t count, uint32_t *countRead) michael@0: { michael@0: // socket stream is unbuffered michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketInputStream::IsNonBlocking(bool *nonblocking) michael@0: { michael@0: *nonblocking = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketInputStream::CloseWithStatus(nsresult reason) michael@0: { michael@0: SOCKET_LOG(("nsSocketInputStream::CloseWithStatus [this=%p reason=%x]\n", this, reason)); michael@0: michael@0: // may be called from any thread michael@0: michael@0: nsresult rv; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: if (NS_SUCCEEDED(mCondition)) michael@0: rv = mCondition = reason; michael@0: else michael@0: rv = NS_OK; michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: mTransport->OnInputClosed(rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketInputStream::AsyncWait(nsIInputStreamCallback *callback, michael@0: uint32_t flags, michael@0: uint32_t amount, michael@0: nsIEventTarget *target) michael@0: { michael@0: SOCKET_LOG(("nsSocketInputStream::AsyncWait [this=%p]\n", this)); michael@0: michael@0: bool hasError = false; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: if (callback && target) { michael@0: // michael@0: // build event proxy michael@0: // michael@0: mCallback = NS_NewInputStreamReadyEvent(callback, target); michael@0: } michael@0: else michael@0: mCallback = callback; michael@0: mCallbackFlags = flags; michael@0: michael@0: hasError = NS_FAILED(mCondition); michael@0: } // unlock mTransport->mLock michael@0: michael@0: if (hasError) { michael@0: // OnSocketEvent will call OnInputStreamReady with an error code after michael@0: // going through the event loop. We do this because most socket callers michael@0: // do not expect AsyncWait() to synchronously execute the OnInputStreamReady michael@0: // callback. michael@0: mTransport->PostEvent(nsSocketTransport::MSG_INPUT_PENDING); michael@0: } else { michael@0: mTransport->OnInputPending(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // socket output stream impl michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsSocketOutputStream::nsSocketOutputStream(nsSocketTransport *trans) michael@0: : mTransport(trans) michael@0: , mWriterRefCnt(0) michael@0: , mCondition(NS_OK) michael@0: , mCallbackFlags(0) michael@0: , mByteCount(0) michael@0: { michael@0: } michael@0: michael@0: nsSocketOutputStream::~nsSocketOutputStream() michael@0: { michael@0: } michael@0: michael@0: // called on the socket transport thread... michael@0: // michael@0: // condition : failure code if socket has been closed michael@0: // michael@0: void michael@0: nsSocketOutputStream::OnSocketReady(nsresult condition) michael@0: { michael@0: SOCKET_LOG(("nsSocketOutputStream::OnSocketReady [this=%p cond=%x]\n", michael@0: this, condition)); michael@0: michael@0: NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: michael@0: nsCOMPtr callback; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: // update condition, but be careful not to erase an already michael@0: // existing error condition. michael@0: if (NS_SUCCEEDED(mCondition)) michael@0: mCondition = condition; michael@0: michael@0: // ignore event if only waiting for closure and not closed. michael@0: if (NS_FAILED(mCondition) || !(mCallbackFlags & WAIT_CLOSURE_ONLY)) { michael@0: callback = mCallback; michael@0: mCallback = nullptr; michael@0: mCallbackFlags = 0; michael@0: } michael@0: } michael@0: michael@0: if (callback) michael@0: callback->OnOutputStreamReady(this); michael@0: } michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(nsSocketOutputStream, michael@0: nsIOutputStream, michael@0: nsIAsyncOutputStream) michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsSocketOutputStream::AddRef() michael@0: { michael@0: ++mWriterRefCnt; michael@0: return mTransport->AddRef(); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsSocketOutputStream::Release() michael@0: { michael@0: if (--mWriterRefCnt == 0) michael@0: Close(); michael@0: return mTransport->Release(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketOutputStream::Close() michael@0: { michael@0: return CloseWithStatus(NS_BASE_STREAM_CLOSED); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketOutputStream::Flush() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketOutputStream::Write(const char *buf, uint32_t count, uint32_t *countWritten) michael@0: { michael@0: SOCKET_LOG(("nsSocketOutputStream::Write [this=%p count=%u]\n", this, count)); michael@0: michael@0: *countWritten = 0; michael@0: michael@0: // A write of 0 bytes can be used to force the initial SSL handshake, so do michael@0: // not reject that. michael@0: michael@0: PRFileDesc* fd = nullptr; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: if (NS_FAILED(mCondition)) michael@0: return mCondition; michael@0: michael@0: fd = mTransport->GetFD_Locked(); michael@0: if (!fd) michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: michael@0: SOCKET_LOG((" calling PR_Write [count=%u]\n", count)); michael@0: michael@0: // cannot hold lock while calling NSPR. (worried about the fact that PSM michael@0: // synchronously proxies notifications over to the UI thread, which could michael@0: // mistakenly try to re-enter this code.) michael@0: int32_t n = PR_Write(fd, buf, count); michael@0: michael@0: SOCKET_LOG((" PR_Write returned [n=%d]\n", n)); michael@0: michael@0: nsresult rv = NS_OK; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: #ifdef ENABLE_SOCKET_TRACING michael@0: if (n > 0) michael@0: mTransport->TraceOutBuf(buf, n); michael@0: #endif michael@0: michael@0: mTransport->ReleaseFD_Locked(fd); michael@0: michael@0: if (n > 0) michael@0: mByteCount += (*countWritten = n); michael@0: else if (n < 0) { michael@0: PRErrorCode code = PR_GetError(); michael@0: if (code == PR_WOULD_BLOCK_ERROR) michael@0: return NS_BASE_STREAM_WOULD_BLOCK; michael@0: mCondition = ErrorAccordingToNSPR(code); michael@0: } michael@0: rv = mCondition; michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: mTransport->OnOutputClosed(rv); michael@0: michael@0: // only send this notification if we have indeed written some data. michael@0: // see bug 196827 for an example of why this is important. michael@0: if (n > 0) michael@0: mTransport->SendStatus(NS_NET_STATUS_SENDING_TO); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketOutputStream::WriteSegments(nsReadSegmentFun reader, void *closure, michael@0: uint32_t count, uint32_t *countRead) michael@0: { michael@0: // socket stream is unbuffered michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_METHOD michael@0: nsSocketOutputStream::WriteFromSegments(nsIInputStream *input, michael@0: void *closure, michael@0: const char *fromSegment, michael@0: uint32_t offset, michael@0: uint32_t count, michael@0: uint32_t *countRead) michael@0: { michael@0: nsSocketOutputStream *self = (nsSocketOutputStream *) closure; michael@0: return self->Write(fromSegment, count, countRead); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketOutputStream::WriteFrom(nsIInputStream *stream, uint32_t count, uint32_t *countRead) michael@0: { michael@0: return stream->ReadSegments(WriteFromSegments, this, count, countRead); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketOutputStream::IsNonBlocking(bool *nonblocking) michael@0: { michael@0: *nonblocking = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketOutputStream::CloseWithStatus(nsresult reason) michael@0: { michael@0: SOCKET_LOG(("nsSocketOutputStream::CloseWithStatus [this=%p reason=%x]\n", this, reason)); michael@0: michael@0: // may be called from any thread michael@0: michael@0: nsresult rv; michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: if (NS_SUCCEEDED(mCondition)) michael@0: rv = mCondition = reason; michael@0: else michael@0: rv = NS_OK; michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: mTransport->OnOutputClosed(rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketOutputStream::AsyncWait(nsIOutputStreamCallback *callback, michael@0: uint32_t flags, michael@0: uint32_t amount, michael@0: nsIEventTarget *target) michael@0: { michael@0: SOCKET_LOG(("nsSocketOutputStream::AsyncWait [this=%p]\n", this)); michael@0: michael@0: { michael@0: MutexAutoLock lock(mTransport->mLock); michael@0: michael@0: if (callback && target) { michael@0: // michael@0: // build event proxy michael@0: // michael@0: mCallback = NS_NewOutputStreamReadyEvent(callback, target); michael@0: } michael@0: else michael@0: mCallback = callback; michael@0: michael@0: mCallbackFlags = flags; michael@0: } michael@0: mTransport->OnOutputPending(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // socket transport impl michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsSocketTransport::nsSocketTransport() michael@0: : mTypes(nullptr) michael@0: , mTypeCount(0) michael@0: , mPort(0) michael@0: , mHttpsProxy(false) michael@0: , mProxyUse(false) michael@0: , mProxyTransparent(false) michael@0: , mProxyTransparentResolvesHost(false) michael@0: , mConnectionFlags(0) michael@0: , mState(STATE_CLOSED) michael@0: , mAttached(false) michael@0: , mInputClosed(true) michael@0: , mOutputClosed(true) michael@0: , mResolving(false) michael@0: , mNetAddrIsSet(false) michael@0: , mLock("nsSocketTransport.mLock") michael@0: , mFD(MOZ_THIS_IN_INITIALIZER_LIST()) michael@0: , mFDref(0) michael@0: , mFDconnected(false) michael@0: , mSocketTransportService(gSocketTransportService) michael@0: , mInput(MOZ_THIS_IN_INITIALIZER_LIST()) michael@0: , mOutput(MOZ_THIS_IN_INITIALIZER_LIST()) michael@0: , mQoSBits(0x00) michael@0: , mKeepaliveEnabled(false) michael@0: , mKeepaliveIdleTimeS(-1) michael@0: , mKeepaliveRetryIntervalS(-1) michael@0: , mKeepaliveProbeCount(-1) michael@0: { michael@0: SOCKET_LOG(("creating nsSocketTransport @%p\n", this)); michael@0: michael@0: mTimeouts[TIMEOUT_CONNECT] = UINT16_MAX; // no timeout michael@0: mTimeouts[TIMEOUT_READ_WRITE] = UINT16_MAX; // no timeout michael@0: } michael@0: michael@0: nsSocketTransport::~nsSocketTransport() michael@0: { michael@0: SOCKET_LOG(("destroying nsSocketTransport @%p\n", this)); michael@0: michael@0: // cleanup socket type info michael@0: if (mTypes) { michael@0: uint32_t i; michael@0: for (i=0; i proxyInfo; michael@0: if (givenProxyInfo) { michael@0: proxyInfo = do_QueryInterface(givenProxyInfo); michael@0: NS_ENSURE_ARG(proxyInfo); michael@0: } michael@0: michael@0: // init socket type info michael@0: michael@0: mPort = port; michael@0: mHost = host; michael@0: michael@0: const char *proxyType = nullptr; michael@0: if (proxyInfo) { michael@0: mProxyInfo = proxyInfo; michael@0: // grab proxy type (looking for "socks" for example) michael@0: proxyType = proxyInfo->Type(); michael@0: if (proxyType && (strcmp(proxyType, "http") == 0 || michael@0: strcmp(proxyType, "direct") == 0 || michael@0: strcmp(proxyType, "unknown") == 0)) michael@0: proxyType = nullptr; michael@0: michael@0: mProxyUse = true; michael@0: // check that we don't have a proxyInfo without proxy michael@0: nsCString proxyHost; michael@0: proxyInfo->GetHost(proxyHost); michael@0: if (!proxyType || proxyHost.IsEmpty()) { michael@0: mProxyUse = false; michael@0: } michael@0: } michael@0: michael@0: SOCKET_LOG(("nsSocketTransport::Init [this=%x host=%s:%hu proxy=%s]\n", michael@0: this, mHost.get(), mPort, mProxyUse ? "yes" : "no")); michael@0: michael@0: // include proxy type as a socket type if proxy type is not "http" michael@0: mTypeCount = typeCount + (proxyType != nullptr); michael@0: if (!mTypeCount) michael@0: return NS_OK; michael@0: michael@0: // if we have socket types, then the socket provider service had michael@0: // better exist! michael@0: nsresult rv; michael@0: nsCOMPtr spserv = michael@0: do_GetService(kSocketProviderServiceCID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mTypes = (char **) malloc(mTypeCount * sizeof(char *)); michael@0: if (!mTypes) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // now verify that each socket type has a registered socket provider. michael@0: for (uint32_t i = 0, type = 0; i < mTypeCount; ++i) { michael@0: // store socket types michael@0: if (i == 0 && proxyType) michael@0: mTypes[i] = PL_strdup(proxyType); michael@0: else michael@0: mTypes[i] = PL_strdup(types[type++]); michael@0: michael@0: if (!mTypes[i]) { michael@0: mTypeCount = i; michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: nsCOMPtr provider; michael@0: rv = spserv->GetSocketProvider(mTypes[i], getter_AddRefs(provider)); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("no registered socket provider"); michael@0: return rv; michael@0: } michael@0: michael@0: // note if socket type corresponds to a transparent proxy michael@0: // XXX don't hardcode SOCKS here (use proxy info's flags instead). michael@0: if ((strcmp(mTypes[i], "socks") == 0) || michael@0: (strcmp(mTypes[i], "socks4") == 0)) { michael@0: mProxyTransparent = true; michael@0: michael@0: if (proxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) { michael@0: // we want the SOCKS layer to send the hostname michael@0: // and port to the proxy and let it do the DNS. michael@0: mProxyTransparentResolvesHost = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSocketTransport::InitWithFilename(const char *filename) michael@0: { michael@0: #if defined(XP_UNIX) michael@0: size_t filenameLength = strlen(filename); michael@0: michael@0: if (filenameLength > sizeof(mNetAddr.local.path) - 1) michael@0: return NS_ERROR_FILE_NAME_TOO_LONG; michael@0: michael@0: mHost.Assign(filename); michael@0: mPort = 0; michael@0: mTypeCount = 0; michael@0: michael@0: mNetAddr.local.family = AF_LOCAL; michael@0: memcpy(mNetAddr.local.path, filename, filenameLength); michael@0: mNetAddr.local.path[filenameLength] = '\0'; michael@0: mNetAddrIsSet = true; michael@0: michael@0: return NS_OK; michael@0: #else michael@0: return NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED; michael@0: #endif michael@0: } michael@0: michael@0: nsresult michael@0: nsSocketTransport::InitWithConnectedSocket(PRFileDesc *fd, const NetAddr *addr) michael@0: { michael@0: NS_ASSERTION(!mFD.IsInitialized(), "already initialized"); michael@0: michael@0: char buf[kNetAddrMaxCStrBufSize]; michael@0: NetAddrToString(addr, buf, sizeof(buf)); michael@0: mHost.Assign(buf); michael@0: michael@0: uint16_t port; michael@0: if (addr->raw.family == AF_INET) michael@0: port = addr->inet.port; michael@0: else if (addr->raw.family == AF_INET6) michael@0: port = addr->inet6.port; michael@0: else michael@0: port = 0; michael@0: mPort = ntohs(port); michael@0: michael@0: memcpy(&mNetAddr, addr, sizeof(NetAddr)); michael@0: michael@0: mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT); michael@0: mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; michael@0: mState = STATE_TRANSFERRING; michael@0: mNetAddrIsSet = true; michael@0: michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: mFD = fd; michael@0: mFDref = 1; michael@0: mFDconnected = 1; michael@0: } michael@0: michael@0: // make sure new socket is non-blocking michael@0: PRSocketOptionData opt; michael@0: opt.option = PR_SockOpt_Nonblocking; michael@0: opt.value.non_blocking = true; michael@0: PR_SetSocketOption(fd, &opt); michael@0: michael@0: SOCKET_LOG(("nsSocketTransport::InitWithConnectedSocket [this=%p addr=%s:%hu]\n", michael@0: this, mHost.get(), mPort)); michael@0: michael@0: // jump to InitiateSocket to get ourselves attached to the STS poll list. michael@0: return PostEvent(MSG_RETRY_INIT_SOCKET); michael@0: } michael@0: michael@0: nsresult michael@0: nsSocketTransport::PostEvent(uint32_t type, nsresult status, nsISupports *param) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::PostEvent [this=%p type=%u status=%x param=%p]\n", michael@0: this, type, status, param)); michael@0: michael@0: nsCOMPtr event = new nsSocketEvent(this, type, status, param); michael@0: if (!event) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: return mSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: void michael@0: nsSocketTransport::SendStatus(nsresult status) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::SendStatus [this=%p status=%x]\n", this, status)); michael@0: michael@0: nsCOMPtr sink; michael@0: uint64_t progress; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: sink = mEventSink; michael@0: switch (status) { michael@0: case NS_NET_STATUS_SENDING_TO: michael@0: progress = mOutput.ByteCount(); michael@0: break; michael@0: case NS_NET_STATUS_RECEIVING_FROM: michael@0: progress = mInput.ByteCount(); michael@0: break; michael@0: default: michael@0: progress = 0; michael@0: break; michael@0: } michael@0: } michael@0: if (sink) michael@0: sink->OnTransportStatus(this, status, progress, UINT64_MAX); michael@0: } michael@0: michael@0: nsresult michael@0: nsSocketTransport::ResolveHost() michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::ResolveHost [this=%p %s:%d%s]\n", michael@0: this, SocketHost().get(), SocketPort(), michael@0: mConnectionFlags & nsSocketTransport::BYPASS_CACHE ? michael@0: " bypass cache" : "")); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mProxyUse) { michael@0: if (!mProxyTransparent || mProxyTransparentResolvesHost) { michael@0: #if defined(XP_UNIX) michael@0: NS_ABORT_IF_FALSE(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL, michael@0: "Unix domain sockets can't be used with proxies"); michael@0: #endif michael@0: // When not resolving mHost locally, we still want to ensure that michael@0: // it only contains valid characters. See bug 304904 for details. michael@0: if (!net_IsValidHostName(mHost)) michael@0: return NS_ERROR_UNKNOWN_HOST; michael@0: } michael@0: if (mProxyTransparentResolvesHost) { michael@0: // Name resolution is done on the server side. Just pretend michael@0: // client resolution is complete, this will get picked up later. michael@0: // since we don't need to do DNS now, we bypass the resolving michael@0: // step by initializing mNetAddr to an empty address, but we michael@0: // must keep the port. The SOCKS IO layer will use the hostname michael@0: // we send it when it's created, rather than the empty address michael@0: // we send with the connect call. michael@0: mState = STATE_RESOLVING; michael@0: mNetAddr.raw.family = AF_INET; michael@0: mNetAddr.inet.port = htons(SocketPort()); michael@0: mNetAddr.inet.ip = htonl(INADDR_ANY); michael@0: return PostEvent(MSG_DNS_LOOKUP_COMPLETE, NS_OK, nullptr); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr dns = do_GetService(kDNSServiceCID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mResolving = true; michael@0: michael@0: uint32_t dnsFlags = 0; michael@0: if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE) michael@0: dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE; michael@0: if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6) michael@0: dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6; michael@0: if (mConnectionFlags & nsSocketTransport::DISABLE_IPV4) michael@0: dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV4; michael@0: michael@0: NS_ASSERTION(!(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV6) || michael@0: !(dnsFlags & nsIDNSService::RESOLVE_DISABLE_IPV4), michael@0: "Setting both RESOLVE_DISABLE_IPV6 and RESOLVE_DISABLE_IPV4"); michael@0: michael@0: SendStatus(NS_NET_STATUS_RESOLVING_HOST); michael@0: rv = dns->AsyncResolve(SocketHost(), dnsFlags, this, nullptr, michael@0: getter_AddRefs(mDNSRequest)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: SOCKET_LOG((" advancing to STATE_RESOLVING\n")); michael@0: mState = STATE_RESOLVING; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsSocketTransport::BuildSocket(PRFileDesc *&fd, bool &proxyTransparent, bool &usingSSL) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::BuildSocket [this=%p]\n", this)); michael@0: michael@0: nsresult rv; michael@0: michael@0: proxyTransparent = false; michael@0: usingSSL = false; michael@0: michael@0: if (mTypeCount == 0) { michael@0: fd = PR_OpenTCPSocket(mNetAddr.raw.family); michael@0: rv = fd ? NS_OK : NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: else { michael@0: #if defined(XP_UNIX) michael@0: NS_ABORT_IF_FALSE(!mNetAddrIsSet || mNetAddr.raw.family != AF_LOCAL, michael@0: "Unix domain sockets can't be used with socket types"); michael@0: #endif michael@0: michael@0: fd = nullptr; michael@0: michael@0: nsCOMPtr spserv = michael@0: do_GetService(kSocketProviderServiceCID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: const char *host = mHost.get(); michael@0: int32_t port = (int32_t) mPort; michael@0: uint32_t proxyFlags = 0; michael@0: nsCOMPtr proxy = mProxyInfo; michael@0: michael@0: uint32_t i; michael@0: for (i=0; i provider; michael@0: michael@0: SOCKET_LOG((" pushing io layer [%u:%s]\n", i, mTypes[i])); michael@0: michael@0: rv = spserv->GetSocketProvider(mTypes[i], getter_AddRefs(provider)); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: if (mProxyTransparentResolvesHost) michael@0: proxyFlags |= nsISocketProvider::PROXY_RESOLVES_HOST; michael@0: michael@0: if (mConnectionFlags & nsISocketTransport::ANONYMOUS_CONNECT) michael@0: proxyFlags |= nsISocketProvider::ANONYMOUS_CONNECT; michael@0: michael@0: if (mConnectionFlags & nsISocketTransport::NO_PERMANENT_STORAGE) michael@0: proxyFlags |= nsISocketProvider::NO_PERMANENT_STORAGE; michael@0: michael@0: michael@0: nsCOMPtr secinfo; michael@0: if (i == 0) { michael@0: // if this is the first type, we'll want the michael@0: // service to allocate a new socket michael@0: nsCString proxyHost; michael@0: GetHost(proxyHost); michael@0: int32_t proxyPort; michael@0: GetPort(&proxyPort); michael@0: rv = provider->NewSocket(mNetAddr.raw.family, michael@0: mHttpsProxy ? proxyHost.get() : host, michael@0: mHttpsProxy ? proxyPort : port, michael@0: proxy, michael@0: proxyFlags, &fd, michael@0: getter_AddRefs(secinfo)); michael@0: michael@0: if (NS_SUCCEEDED(rv) && !fd) { michael@0: NS_NOTREACHED("NewSocket succeeded but failed to create a PRFileDesc"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: else { michael@0: // the socket has already been allocated, michael@0: // so we just want the service to add itself michael@0: // to the stack (such as pushing an io layer) michael@0: rv = provider->AddToSocket(mNetAddr.raw.family, michael@0: host, port, proxy, michael@0: proxyFlags, fd, michael@0: getter_AddRefs(secinfo)); michael@0: } michael@0: // proxyFlags = 0; not used below this point... michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: // if the service was ssl or starttls, we want to hold onto the socket info michael@0: bool isSSL = (strcmp(mTypes[i], "ssl") == 0); michael@0: if (isSSL || (strcmp(mTypes[i], "starttls") == 0)) { michael@0: // remember security info and give notification callbacks to PSM... michael@0: nsCOMPtr callbacks; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mSecInfo = secinfo; michael@0: callbacks = mCallbacks; michael@0: SOCKET_LOG((" [secinfo=%x callbacks=%x]\n", mSecInfo.get(), mCallbacks.get())); michael@0: } michael@0: // don't call into PSM while holding mLock!! michael@0: nsCOMPtr secCtrl(do_QueryInterface(secinfo)); michael@0: if (secCtrl) michael@0: secCtrl->SetNotificationCallbacks(callbacks); michael@0: // remember if socket type is SSL so we can ProxyStartSSL if need be. michael@0: usingSSL = isSSL; michael@0: } michael@0: else if ((strcmp(mTypes[i], "socks") == 0) || michael@0: (strcmp(mTypes[i], "socks4") == 0)) { michael@0: // since socks is transparent, any layers above michael@0: // it do not have to worry about proxy stuff michael@0: proxy = nullptr; michael@0: proxyTransparent = true; michael@0: } michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: SOCKET_LOG((" error pushing io layer [%u:%s rv=%x]\n", i, mTypes[i], rv)); michael@0: if (fd) michael@0: PR_Close(fd); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsSocketTransport::InitiateSocket() michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::InitiateSocket [this=%p]\n", this)); michael@0: michael@0: static bool crashOnNonLocalConnections = !!getenv("MOZ_DISABLE_NONLOCAL_CONNECTIONS"); michael@0: michael@0: nsresult rv; michael@0: bool isLocal; michael@0: IsLocal(&isLocal); michael@0: michael@0: if (gIOService->IsOffline()) { michael@0: if (!isLocal) michael@0: return NS_ERROR_OFFLINE; michael@0: } else if (!isLocal) { michael@0: if (NS_SUCCEEDED(mCondition) && michael@0: crashOnNonLocalConnections && michael@0: !(IsIPAddrAny(&mNetAddr) || IsIPAddrLocal(&mNetAddr))) { michael@0: nsAutoCString ipaddr; michael@0: nsRefPtr netaddr = new nsNetAddr(&mNetAddr); michael@0: netaddr->GetAddress(ipaddr); michael@0: fprintf_stderr(stderr, michael@0: "FATAL ERROR: Non-local network connections are disabled and a connection " michael@0: "attempt to %s (%s) was made.\nYou should only access hostnames " michael@0: "available via the test networking proxy (if running mochitests) " michael@0: "or from a test-specific httpd.js server (if running xpcshell tests). " michael@0: "Browser services should be disabled or redirected to a local server.\n", michael@0: mHost.get(), ipaddr.get()); michael@0: MOZ_CRASH("Attempting to connect to non-local address!"); michael@0: } michael@0: } michael@0: michael@0: // Hosts/Proxy Hosts that are Local IP Literals should not be speculatively michael@0: // connected - Bug 853423. michael@0: if (mConnectionFlags & nsISocketTransport::DISABLE_RFC1918 && michael@0: IsIPAddrLocal(&mNetAddr)) { michael@0: #ifdef PR_LOGGING michael@0: if (SOCKET_LOG_ENABLED()) { michael@0: nsAutoCString netAddrCString; michael@0: netAddrCString.SetCapacity(kIPv6CStrBufSize); michael@0: if (!NetAddrToString(&mNetAddr, michael@0: netAddrCString.BeginWriting(), michael@0: kIPv6CStrBufSize)) michael@0: netAddrCString = NS_LITERAL_CSTRING(""); michael@0: nsCString proxyHost; michael@0: GetHost(proxyHost); michael@0: int32_t proxyPort; michael@0: GetPort(&proxyPort); michael@0: SOCKET_LOG(("nsSocketTransport::InitiateSocket skipping " michael@0: "speculative connection for host [%s:%d] proxy " michael@0: "[%s:%d] with Local IP address [%s]", michael@0: mHost.get(), mPort, proxyHost.get(), proxyPort, michael@0: netAddrCString.get())); michael@0: } michael@0: #endif michael@0: return NS_ERROR_CONNECTION_REFUSED; michael@0: } michael@0: michael@0: // michael@0: // find out if it is going to be ok to attach another socket to the STS. michael@0: // if not then we have to wait for the STS to tell us that it is ok. michael@0: // the notification is asynchronous, which means that when we could be michael@0: // in a race to call AttachSocket once notified. for this reason, when michael@0: // we get notified, we just re-enter this function. as a result, we are michael@0: // sure to ask again before calling AttachSocket. in this way we deal michael@0: // with the race condition. though it isn't the most elegant solution, michael@0: // it is far simpler than trying to build a system that would guarantee michael@0: // FIFO ordering (which wouldn't even be that valuable IMO). see bug michael@0: // 194402 for more info. michael@0: // michael@0: if (!mSocketTransportService->CanAttachSocket()) { michael@0: nsCOMPtr event = michael@0: new nsSocketEvent(this, MSG_RETRY_INIT_SOCKET); michael@0: if (!event) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: return mSocketTransportService->NotifyWhenCanAttachSocket(event); michael@0: } michael@0: michael@0: // michael@0: // if we already have a connected socket, then just attach and return. michael@0: // michael@0: if (mFD.IsInitialized()) { michael@0: rv = mSocketTransportService->AttachSocket(mFD, this); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mAttached = true; michael@0: return rv; michael@0: } michael@0: michael@0: // michael@0: // create new socket fd, push io layers, etc. michael@0: // michael@0: PRFileDesc *fd; michael@0: bool proxyTransparent; michael@0: bool usingSSL; michael@0: michael@0: rv = BuildSocket(fd, proxyTransparent, usingSSL); michael@0: if (NS_FAILED(rv)) { michael@0: SOCKET_LOG((" BuildSocket failed [rv=%x]\n", rv)); michael@0: return rv; michael@0: } michael@0: michael@0: // Attach network activity monitor michael@0: mozilla::net::NetworkActivityMonitor::AttachIOLayer(fd); michael@0: michael@0: PRStatus status; michael@0: michael@0: // Make the socket non-blocking... michael@0: PRSocketOptionData opt; michael@0: opt.option = PR_SockOpt_Nonblocking; michael@0: opt.value.non_blocking = true; michael@0: status = PR_SetSocketOption(fd, &opt); michael@0: NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking"); michael@0: michael@0: // disable the nagle algorithm - if we rely on it to coalesce writes into michael@0: // full packets the final packet of a multi segment POST/PUT or pipeline michael@0: // sequence is delayed a full rtt michael@0: opt.option = PR_SockOpt_NoDelay; michael@0: opt.value.no_delay = true; michael@0: PR_SetSocketOption(fd, &opt); michael@0: michael@0: // if the network.tcp.sendbuffer preference is set, use it to size SO_SNDBUF michael@0: // The Windows default of 8KB is too small and as of vista sp1, autotuning michael@0: // only applies to receive window michael@0: int32_t sndBufferSize; michael@0: mSocketTransportService->GetSendBufferSize(&sndBufferSize); michael@0: if (sndBufferSize > 0) { michael@0: opt.option = PR_SockOpt_SendBufferSize; michael@0: opt.value.send_buffer_size = sndBufferSize; michael@0: PR_SetSocketOption(fd, &opt); michael@0: } michael@0: michael@0: if (mQoSBits) { michael@0: opt.option = PR_SockOpt_IpTypeOfService; michael@0: opt.value.tos = mQoSBits; michael@0: PR_SetSocketOption(fd, &opt); michael@0: } michael@0: michael@0: // inform socket transport about this newly created socket... michael@0: rv = mSocketTransportService->AttachSocket(fd, this); michael@0: if (NS_FAILED(rv)) { michael@0: PR_Close(fd); michael@0: return rv; michael@0: } michael@0: mAttached = true; michael@0: michael@0: // assign mFD so that we can properly handle OnSocketDetached before we've michael@0: // established a connection. michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mFD = fd; michael@0: mFDref = 1; michael@0: mFDconnected = false; michael@0: } michael@0: michael@0: SOCKET_LOG((" advancing to STATE_CONNECTING\n")); michael@0: mState = STATE_CONNECTING; michael@0: mPollTimeout = mTimeouts[TIMEOUT_CONNECT]; michael@0: SendStatus(NS_NET_STATUS_CONNECTING_TO); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: if (SOCKET_LOG_ENABLED()) { michael@0: char buf[kNetAddrMaxCStrBufSize]; michael@0: NetAddrToString(&mNetAddr, buf, sizeof(buf)); michael@0: SOCKET_LOG((" trying address: %s\n", buf)); michael@0: } michael@0: #endif michael@0: michael@0: // michael@0: // Initiate the connect() to the host... michael@0: // michael@0: PRNetAddr prAddr; michael@0: NetAddrToPRNetAddr(&mNetAddr, &prAddr); michael@0: michael@0: MOZ_EVENT_TRACER_EXEC(this, "net::tcp::connect"); michael@0: status = PR_Connect(fd, &prAddr, NS_SOCKET_CONNECT_TIMEOUT); michael@0: if (status == PR_SUCCESS) { michael@0: // michael@0: // we are connected! michael@0: // michael@0: OnSocketConnected(); michael@0: } michael@0: else { michael@0: PRErrorCode code = PR_GetError(); michael@0: #if defined(TEST_CONNECT_ERRORS) michael@0: code = RandomizeConnectError(code); michael@0: #endif michael@0: // michael@0: // If the PR_Connect(...) would block, then poll for a connection. michael@0: // michael@0: if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) michael@0: mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE); michael@0: // michael@0: // If the socket is already connected, then return success... michael@0: // michael@0: else if (PR_IS_CONNECTED_ERROR == code) { michael@0: // michael@0: // we are connected! michael@0: // michael@0: OnSocketConnected(); michael@0: michael@0: if (mSecInfo && mProxyUse && proxyTransparent && usingSSL) { michael@0: // if the connection phase is finished, and the ssl layer has michael@0: // been pushed, and we were proxying (transparently; ie. nothing michael@0: // has to happen in the protocol layer above us), it's time for michael@0: // the ssl to start doing it's thing. michael@0: nsCOMPtr secCtrl = michael@0: do_QueryInterface(mSecInfo); michael@0: if (secCtrl) { michael@0: SOCKET_LOG((" calling ProxyStartSSL()\n")); michael@0: secCtrl->ProxyStartSSL(); michael@0: } michael@0: // XXX what if we were forced to poll on the socket for a successful michael@0: // connection... wouldn't we need to call ProxyStartSSL after a call michael@0: // to PR_ConnectContinue indicates that we are connected? michael@0: // michael@0: // XXX this appears to be what the old socket transport did. why michael@0: // isn't this broken? michael@0: } michael@0: } michael@0: // michael@0: // A SOCKS request was rejected; get the actual error code from michael@0: // the OS error michael@0: // michael@0: else if (PR_UNKNOWN_ERROR == code && michael@0: mProxyUse && mProxyTransparent) { michael@0: code = PR_GetOSError(); michael@0: rv = ErrorAccordingToNSPR(code); michael@0: } michael@0: // michael@0: // The connection was refused... michael@0: // michael@0: else { michael@0: rv = ErrorAccordingToNSPR(code); michael@0: if (rv == NS_ERROR_CONNECTION_REFUSED && mProxyUse) michael@0: rv = NS_ERROR_PROXY_CONNECTION_REFUSED; michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsSocketTransport::RecoverFromError() michael@0: { michael@0: NS_ASSERTION(NS_FAILED(mCondition), "there should be something wrong"); michael@0: michael@0: SOCKET_LOG(("nsSocketTransport::RecoverFromError [this=%p state=%x cond=%x]\n", michael@0: this, mState, mCondition)); michael@0: michael@0: #if defined(XP_UNIX) michael@0: // Unix domain connections don't have multiple addresses to try, michael@0: // so the recovery techniques here don't apply. michael@0: if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) michael@0: return false; michael@0: #endif michael@0: michael@0: // can only recover from errors in these states michael@0: if (mState != STATE_RESOLVING && mState != STATE_CONNECTING) michael@0: return false; michael@0: michael@0: nsresult rv; michael@0: michael@0: // OK to check this outside mLock michael@0: NS_ASSERTION(!mFDconnected, "socket should not be connected"); michael@0: michael@0: // all connection failures need to be reported to DNS so that the next michael@0: // time we will use a different address if available. michael@0: if (mState == STATE_CONNECTING && mDNSRecord) { michael@0: mDNSRecord->ReportUnusable(SocketPort()); michael@0: } michael@0: michael@0: // can only recover from these errors michael@0: if (mCondition != NS_ERROR_CONNECTION_REFUSED && michael@0: mCondition != NS_ERROR_PROXY_CONNECTION_REFUSED && michael@0: mCondition != NS_ERROR_NET_TIMEOUT && michael@0: mCondition != NS_ERROR_UNKNOWN_HOST && michael@0: mCondition != NS_ERROR_UNKNOWN_PROXY_HOST) michael@0: return false; michael@0: michael@0: bool tryAgain = false; michael@0: michael@0: if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4) && michael@0: mCondition == NS_ERROR_UNKNOWN_HOST && michael@0: mState == STATE_RESOLVING && michael@0: !mProxyTransparentResolvesHost) { michael@0: SOCKET_LOG((" trying lookup again with both ipv4/ipv6 enabled\n")); michael@0: mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4); michael@0: tryAgain = true; michael@0: } michael@0: michael@0: // try next ip address only if past the resolver stage... michael@0: if (mState == STATE_CONNECTING && mDNSRecord) { michael@0: nsresult rv = mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: SOCKET_LOG((" trying again with next ip address\n")); michael@0: tryAgain = true; michael@0: } michael@0: else if (mConnectionFlags & (DISABLE_IPV6 | DISABLE_IPV4)) { michael@0: // Drop state to closed. This will trigger new round of DNS michael@0: // resolving bellow. michael@0: // XXX Could be optimized to only switch the flags to save duplicate michael@0: // connection attempts. michael@0: SOCKET_LOG((" failed to connect all ipv4-only or ipv6-only hosts," michael@0: " trying lookup/connect again with both ipv4/ipv6\n")); michael@0: mState = STATE_CLOSED; michael@0: mConnectionFlags &= ~(DISABLE_IPV6 | DISABLE_IPV4); michael@0: tryAgain = true; michael@0: } michael@0: } michael@0: michael@0: #if defined(XP_WIN) michael@0: // If not trying next address, try to make a connection using dialup. michael@0: // Retry if that connection is made. michael@0: if (!tryAgain) { michael@0: bool autodialEnabled; michael@0: mSocketTransportService->GetAutodialEnabled(&autodialEnabled); michael@0: if (autodialEnabled) { michael@0: tryAgain = nsNativeConnectionHelper::OnConnectionFailed( michael@0: NS_ConvertUTF8toUTF16(SocketHost()).get()); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // prepare to try again. michael@0: if (tryAgain) { michael@0: uint32_t msg; michael@0: michael@0: if (mState == STATE_CONNECTING) { michael@0: mState = STATE_RESOLVING; michael@0: msg = MSG_DNS_LOOKUP_COMPLETE; michael@0: } michael@0: else { michael@0: mState = STATE_CLOSED; michael@0: msg = MSG_ENSURE_CONNECT; michael@0: } michael@0: michael@0: rv = PostEvent(msg, NS_OK); michael@0: if (NS_FAILED(rv)) michael@0: tryAgain = false; michael@0: } michael@0: michael@0: return tryAgain; michael@0: } michael@0: michael@0: // called on the socket thread only michael@0: void michael@0: nsSocketTransport::OnMsgInputClosed(nsresult reason) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::OnMsgInputClosed [this=%p reason=%x]\n", michael@0: this, reason)); michael@0: michael@0: NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: michael@0: mInputClosed = true; michael@0: // check if event should affect entire transport michael@0: if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) michael@0: mCondition = reason; // XXX except if NS_FAILED(mCondition), right?? michael@0: else if (mOutputClosed) michael@0: mCondition = NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right?? michael@0: else { michael@0: if (mState == STATE_TRANSFERRING) michael@0: mPollFlags &= ~PR_POLL_READ; michael@0: mInput.OnSocketReady(reason); michael@0: } michael@0: } michael@0: michael@0: // called on the socket thread only michael@0: void michael@0: nsSocketTransport::OnMsgOutputClosed(nsresult reason) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::OnMsgOutputClosed [this=%p reason=%x]\n", michael@0: this, reason)); michael@0: michael@0: NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: michael@0: mOutputClosed = true; michael@0: // check if event should affect entire transport michael@0: if (NS_FAILED(reason) && (reason != NS_BASE_STREAM_CLOSED)) michael@0: mCondition = reason; // XXX except if NS_FAILED(mCondition), right?? michael@0: else if (mInputClosed) michael@0: mCondition = NS_BASE_STREAM_CLOSED; // XXX except if NS_FAILED(mCondition), right?? michael@0: else { michael@0: if (mState == STATE_TRANSFERRING) michael@0: mPollFlags &= ~PR_POLL_WRITE; michael@0: mOutput.OnSocketReady(reason); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSocketTransport::OnSocketConnected() michael@0: { michael@0: SOCKET_LOG((" advancing to STATE_TRANSFERRING\n")); michael@0: michael@0: mPollFlags = (PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT); michael@0: mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; michael@0: mState = STATE_TRANSFERRING; michael@0: michael@0: // Set the mNetAddrIsSet flag only when state has reached TRANSFERRING michael@0: // because we need to make sure its value does not change due to failover michael@0: mNetAddrIsSet = true; michael@0: michael@0: // assign mFD (must do this within the transport lock), but take care not michael@0: // to trample over mFDref if mFD is already set. michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: NS_ASSERTION(mFD.IsInitialized(), "no socket"); michael@0: NS_ASSERTION(mFDref == 1, "wrong socket ref count"); michael@0: mFDconnected = true; michael@0: } michael@0: michael@0: // Ensure keepalive is configured correctly if previously enabled. michael@0: if (mKeepaliveEnabled) { michael@0: nsresult rv = SetKeepaliveEnabledInternal(true); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%x]", rv)); michael@0: } michael@0: } michael@0: michael@0: MOZ_EVENT_TRACER_DONE(this, "net::tcp::connect"); michael@0: michael@0: SendStatus(NS_NET_STATUS_CONNECTED_TO); michael@0: } michael@0: michael@0: PRFileDesc * michael@0: nsSocketTransport::GetFD_Locked() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: // mFD is not available to the streams while disconnected. michael@0: if (!mFDconnected) michael@0: return nullptr; michael@0: michael@0: if (mFD.IsInitialized()) michael@0: mFDref++; michael@0: michael@0: return mFD; michael@0: } michael@0: michael@0: class ThunkPRClose : public nsRunnable michael@0: { michael@0: public: michael@0: ThunkPRClose(PRFileDesc *fd) : mFD(fd) {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: PR_Close(mFD); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: PRFileDesc *mFD; michael@0: }; michael@0: michael@0: void michael@0: STS_PRCloseOnSocketTransport(PRFileDesc *fd) michael@0: { michael@0: if (gSocketTransportService) { michael@0: // Can't PR_Close() a socket off STS thread. Thunk it to STS to die michael@0: // FIX - Should use RUN_ON_THREAD once it's generally available michael@0: // RUN_ON_THREAD(gSocketThread,WrapRunnableNM(&PR_Close, mFD); michael@0: gSocketTransportService->Dispatch(new ThunkPRClose(fd), NS_DISPATCH_NORMAL); michael@0: } else { michael@0: // something horrible has happened michael@0: NS_ASSERTION(gSocketTransportService, "No STS service"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSocketTransport::ReleaseFD_Locked(PRFileDesc *fd) michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: michael@0: NS_ASSERTION(mFD == fd, "wrong fd"); michael@0: SOCKET_LOG(("JIMB: ReleaseFD_Locked: mFDref = %d\n", mFDref)); michael@0: michael@0: if (--mFDref == 0) { michael@0: if (PR_GetCurrentThread() == gSocketThread) { michael@0: SOCKET_LOG(("nsSocketTransport: calling PR_Close [this=%p]\n", this)); michael@0: PR_Close(mFD); michael@0: } else { michael@0: // Can't PR_Close() a socket off STS thread. Thunk it to STS to die michael@0: STS_PRCloseOnSocketTransport(mFD); michael@0: } michael@0: mFD = nullptr; michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // socket event handler impl michael@0: michael@0: void michael@0: nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status, nsISupports *param) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::OnSocketEvent [this=%p type=%u status=%x param=%p]\n", michael@0: this, type, status, param)); michael@0: michael@0: if (NS_FAILED(mCondition)) { michael@0: // block event since we're apparently already dead. michael@0: SOCKET_LOG((" blocking event [condition=%x]\n", mCondition)); michael@0: // michael@0: // notify input/output streams in case either has a pending notify. michael@0: // michael@0: mInput.OnSocketReady(mCondition); michael@0: mOutput.OnSocketReady(mCondition); michael@0: return; michael@0: } michael@0: michael@0: switch (type) { michael@0: case MSG_ENSURE_CONNECT: michael@0: SOCKET_LOG((" MSG_ENSURE_CONNECT\n")); michael@0: // michael@0: // ensure that we have created a socket, attached it, and have a michael@0: // connection. michael@0: // michael@0: if (mState == STATE_CLOSED) { michael@0: // Unix domain sockets are ready to connect; mNetAddr is all we michael@0: // need. Internet address families require a DNS lookup (or possibly michael@0: // several) before we can connect. michael@0: #if defined(XP_UNIX) michael@0: if (mNetAddrIsSet && mNetAddr.raw.family == AF_LOCAL) michael@0: mCondition = InitiateSocket(); michael@0: else michael@0: #endif michael@0: mCondition = ResolveHost(); michael@0: michael@0: } else { michael@0: SOCKET_LOG((" ignoring redundant event\n")); michael@0: } michael@0: break; michael@0: michael@0: case MSG_DNS_LOOKUP_COMPLETE: michael@0: if (mDNSRequest) // only send this if we actually resolved anything michael@0: SendStatus(NS_NET_STATUS_RESOLVED_HOST); michael@0: michael@0: SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n")); michael@0: mDNSRequest = 0; michael@0: if (param) { michael@0: mDNSRecord = static_cast(param); michael@0: mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr); michael@0: } michael@0: // status contains DNS lookup status michael@0: if (NS_FAILED(status)) { michael@0: // When using a HTTP proxy, NS_ERROR_UNKNOWN_HOST means the HTTP michael@0: // proxy host is not found, so we fixup the error code. michael@0: // For SOCKS proxies (mProxyTransparent == true), the socket michael@0: // transport resolves the real host here, so there's no fixup michael@0: // (see bug 226943). michael@0: if (status == NS_ERROR_UNKNOWN_HOST && !mProxyTransparent && michael@0: mProxyUse) michael@0: mCondition = NS_ERROR_UNKNOWN_PROXY_HOST; michael@0: else michael@0: mCondition = status; michael@0: } michael@0: else if (mState == STATE_RESOLVING) michael@0: mCondition = InitiateSocket(); michael@0: break; michael@0: michael@0: case MSG_RETRY_INIT_SOCKET: michael@0: mCondition = InitiateSocket(); michael@0: break; michael@0: michael@0: case MSG_INPUT_CLOSED: michael@0: SOCKET_LOG((" MSG_INPUT_CLOSED\n")); michael@0: OnMsgInputClosed(status); michael@0: break; michael@0: michael@0: case MSG_INPUT_PENDING: michael@0: SOCKET_LOG((" MSG_INPUT_PENDING\n")); michael@0: OnMsgInputPending(); michael@0: break; michael@0: michael@0: case MSG_OUTPUT_CLOSED: michael@0: SOCKET_LOG((" MSG_OUTPUT_CLOSED\n")); michael@0: OnMsgOutputClosed(status); michael@0: break; michael@0: michael@0: case MSG_OUTPUT_PENDING: michael@0: SOCKET_LOG((" MSG_OUTPUT_PENDING\n")); michael@0: OnMsgOutputPending(); michael@0: break; michael@0: case MSG_TIMEOUT_CHANGED: michael@0: SOCKET_LOG((" MSG_TIMEOUT_CHANGED\n")); michael@0: mPollTimeout = mTimeouts[(mState == STATE_TRANSFERRING) michael@0: ? TIMEOUT_READ_WRITE : TIMEOUT_CONNECT]; michael@0: break; michael@0: default: michael@0: SOCKET_LOG((" unhandled event!\n")); michael@0: } michael@0: michael@0: if (NS_FAILED(mCondition)) { michael@0: SOCKET_LOG((" after event [this=%p cond=%x]\n", this, mCondition)); michael@0: if (!mAttached) // need to process this error ourselves... michael@0: OnSocketDetached(nullptr); michael@0: } michael@0: else if (mPollFlags == PR_POLL_EXCEPT) michael@0: mPollFlags = 0; // make idle michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // socket handler impl michael@0: michael@0: void michael@0: nsSocketTransport::OnSocketReady(PRFileDesc *fd, int16_t outFlags) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::OnSocketReady [this=%p outFlags=%hd]\n", michael@0: this, outFlags)); michael@0: michael@0: if (outFlags == -1) { michael@0: SOCKET_LOG(("socket timeout expired\n")); michael@0: mCondition = NS_ERROR_NET_TIMEOUT; michael@0: return; michael@0: } michael@0: michael@0: if (mState == STATE_TRANSFERRING) { michael@0: // if waiting to write and socket is writable or hit an exception. michael@0: if ((mPollFlags & PR_POLL_WRITE) && (outFlags & ~PR_POLL_READ)) { michael@0: // assume that we won't need to poll any longer (the stream will michael@0: // request that we poll again if it is still pending). michael@0: mPollFlags &= ~PR_POLL_WRITE; michael@0: mOutput.OnSocketReady(NS_OK); michael@0: } michael@0: // if waiting to read and socket is readable or hit an exception. michael@0: if ((mPollFlags & PR_POLL_READ) && (outFlags & ~PR_POLL_WRITE)) { michael@0: // assume that we won't need to poll any longer (the stream will michael@0: // request that we poll again if it is still pending). michael@0: mPollFlags &= ~PR_POLL_READ; michael@0: mInput.OnSocketReady(NS_OK); michael@0: } michael@0: // Update poll timeout in case it was changed michael@0: mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; michael@0: } michael@0: michael@0: //STATE_SENDINGGET: handshake proceeded to state "sent connect" michael@0: //one more poll to OnSocketReady will trigger the get request, and state STATE_SENTGET michael@0: //STATE_SENTGET: continue and finish handshake michael@0: else if (mState == STATE_SENDINGGET) { michael@0: if ((mPollFlags & PR_POLL_WRITE) && (outFlags & ~PR_POLL_READ)) { michael@0: mOutput.OnSocketReady(NS_OK); michael@0: } michael@0: mPollTimeout = mTimeouts[TIMEOUT_READ_WRITE]; michael@0: mState = STATE_SENTGET; michael@0: } michael@0: michael@0: else if (mState == STATE_CONNECTING || mState == STATE_SENTGET) { michael@0: PRStatus status = PR_ConnectContinue(fd, outFlags); michael@0: if (status == PR_SUCCESS && mState == STATE_CONNECTING) { michael@0: OnSocketConnected(); michael@0: mState = STATE_SENDINGGET; michael@0: } michael@0: else if (status == PR_SUCCESS && mState == STATE_SENTGET) { michael@0: // michael@0: // we are connected! michael@0: // michael@0: OnSocketConnected(); michael@0: } michael@0: else { michael@0: PRErrorCode code = PR_GetError(); michael@0: #if defined(TEST_CONNECT_ERRORS) michael@0: code = RandomizeConnectError(code); michael@0: #endif michael@0: // michael@0: // If the connect is still not ready, then continue polling... michael@0: // michael@0: if ((PR_WOULD_BLOCK_ERROR == code) || (PR_IN_PROGRESS_ERROR == code)) { michael@0: // Set up the select flags for connect... michael@0: mPollFlags = (PR_POLL_EXCEPT | PR_POLL_WRITE); michael@0: // Update poll timeout in case it was changed michael@0: mPollTimeout = mTimeouts[TIMEOUT_CONNECT]; michael@0: } michael@0: // michael@0: // The SOCKS proxy rejected our request. Find out why. michael@0: // michael@0: else if (PR_UNKNOWN_ERROR == code && michael@0: mProxyUse && mProxyTransparent) { michael@0: code = PR_GetOSError(); michael@0: mCondition = ErrorAccordingToNSPR(code); michael@0: } michael@0: else { michael@0: // michael@0: // else, the connection failed... michael@0: // michael@0: mCondition = ErrorAccordingToNSPR(code); michael@0: if (mCondition == NS_ERROR_CONNECTION_REFUSED && mProxyUse) michael@0: mCondition = NS_ERROR_PROXY_CONNECTION_REFUSED; michael@0: SOCKET_LOG((" connection failed! [reason=%x]\n", mCondition)); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: NS_ERROR("unexpected socket state"); michael@0: mCondition = NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (mPollFlags == PR_POLL_EXCEPT) michael@0: mPollFlags = 0; // make idle michael@0: } michael@0: michael@0: // called on the socket thread only michael@0: void michael@0: nsSocketTransport::OnSocketDetached(PRFileDesc *fd) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::OnSocketDetached [this=%p cond=%x]\n", michael@0: this, mCondition)); michael@0: michael@0: NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: michael@0: // if we didn't initiate this detach, then be sure to pass an error michael@0: // condition up to our consumers. (e.g., STS is shutting down.) michael@0: if (NS_SUCCEEDED(mCondition)) { michael@0: if (gIOService->IsOffline()) { michael@0: mCondition = NS_ERROR_OFFLINE; michael@0: } michael@0: else { michael@0: mCondition = NS_ERROR_ABORT; michael@0: } michael@0: } michael@0: michael@0: if (RecoverFromError()) michael@0: mCondition = NS_OK; michael@0: else { michael@0: mState = STATE_CLOSED; michael@0: michael@0: // make sure there isn't any pending DNS request michael@0: if (mDNSRequest) { michael@0: mDNSRequest->Cancel(NS_ERROR_ABORT); michael@0: mDNSRequest = 0; michael@0: } michael@0: michael@0: // michael@0: // notify input/output streams michael@0: // michael@0: mInput.OnSocketReady(mCondition); michael@0: mOutput.OnSocketReady(mCondition); michael@0: } michael@0: michael@0: // break any potential reference cycle between the security info object michael@0: // and ourselves by resetting its notification callbacks object. see michael@0: // bug 285991 for details. michael@0: nsCOMPtr secCtrl = do_QueryInterface(mSecInfo); michael@0: if (secCtrl) michael@0: secCtrl->SetNotificationCallbacks(nullptr); michael@0: michael@0: // finally, release our reference to the socket (must do this within michael@0: // the transport lock) possibly closing the socket. Also release our michael@0: // listeners to break potential refcount cycles. michael@0: michael@0: // We should be careful not to release mEventSink and mCallbacks while michael@0: // we're locked, because releasing it might require acquiring the lock michael@0: // again, so we just null out mEventSink and mCallbacks while we're michael@0: // holding the lock, and let the stack based objects' destuctors take michael@0: // care of destroying it if needed. michael@0: nsCOMPtr ourCallbacks; michael@0: nsCOMPtr ourEventSink; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: if (mFD.IsInitialized()) { michael@0: ReleaseFD_Locked(mFD); michael@0: // flag mFD as unusable; this prevents other consumers from michael@0: // acquiring a reference to mFD. michael@0: mFDconnected = false; michael@0: } michael@0: michael@0: // We must release mCallbacks and mEventSink to avoid memory leak michael@0: // but only when RecoverFromError() above failed. Otherwise we lose michael@0: // link with UI and security callbacks on next connection attempt michael@0: // round. That would lead e.g. to a broken certificate exception page. michael@0: if (NS_FAILED(mCondition)) { michael@0: mCallbacks.swap(ourCallbacks); michael@0: mEventSink.swap(ourEventSink); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsSocketTransport::IsLocal(bool *aIsLocal) michael@0: { michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: michael@0: #if defined(XP_UNIX) michael@0: // Unix-domain sockets are always local. michael@0: if (mNetAddr.raw.family == PR_AF_LOCAL) michael@0: { michael@0: *aIsLocal = true; michael@0: return; michael@0: } michael@0: #endif michael@0: michael@0: *aIsLocal = IsLoopBackAddress(&mNetAddr); michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // xpcom api michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSocketTransport, michael@0: nsISocketTransport, michael@0: nsITransport, michael@0: nsIDNSListener, michael@0: nsIClassInfo) michael@0: NS_IMPL_CI_INTERFACE_GETTER(nsSocketTransport, michael@0: nsISocketTransport, michael@0: nsITransport, michael@0: nsIDNSListener) michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::OpenInputStream(uint32_t flags, michael@0: uint32_t segsize, michael@0: uint32_t segcount, michael@0: nsIInputStream **result) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::OpenInputStream [this=%p flags=%x]\n", michael@0: this, flags)); michael@0: michael@0: NS_ENSURE_TRUE(!mInput.IsReferenced(), NS_ERROR_UNEXPECTED); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr pipeIn; michael@0: michael@0: if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) { michael@0: // XXX if the caller wants blocking, then the caller also gets buffered! michael@0: //bool openBuffered = !(flags & OPEN_UNBUFFERED); michael@0: bool openBlocking = (flags & OPEN_BLOCKING); michael@0: michael@0: net_ResolveSegmentParams(segsize, segcount); michael@0: michael@0: // create a pipe michael@0: nsCOMPtr pipeOut; michael@0: rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), michael@0: !openBlocking, true, segsize, segcount); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // async copy from socket to pipe michael@0: rv = NS_AsyncCopy(&mInput, pipeOut, mSocketTransportService, michael@0: NS_ASYNCCOPY_VIA_WRITESEGMENTS, segsize); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: *result = pipeIn; michael@0: } michael@0: else michael@0: *result = &mInput; michael@0: michael@0: // flag input stream as open michael@0: mInputClosed = false; michael@0: michael@0: rv = PostEvent(MSG_ENSURE_CONNECT); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: NS_ADDREF(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::OpenOutputStream(uint32_t flags, michael@0: uint32_t segsize, michael@0: uint32_t segcount, michael@0: nsIOutputStream **result) michael@0: { michael@0: SOCKET_LOG(("nsSocketTransport::OpenOutputStream [this=%p flags=%x]\n", michael@0: this, flags)); michael@0: michael@0: NS_ENSURE_TRUE(!mOutput.IsReferenced(), NS_ERROR_UNEXPECTED); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr pipeOut; michael@0: if (!(flags & OPEN_UNBUFFERED) || (flags & OPEN_BLOCKING)) { michael@0: // XXX if the caller wants blocking, then the caller also gets buffered! michael@0: //bool openBuffered = !(flags & OPEN_UNBUFFERED); michael@0: bool openBlocking = (flags & OPEN_BLOCKING); michael@0: michael@0: net_ResolveSegmentParams(segsize, segcount); michael@0: michael@0: // create a pipe michael@0: nsCOMPtr pipeIn; michael@0: rv = NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), michael@0: true, !openBlocking, segsize, segcount); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // async copy from socket to pipe michael@0: rv = NS_AsyncCopy(pipeIn, &mOutput, mSocketTransportService, michael@0: NS_ASYNCCOPY_VIA_READSEGMENTS, segsize); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: *result = pipeOut; michael@0: } michael@0: else michael@0: *result = &mOutput; michael@0: michael@0: // flag output stream as open michael@0: mOutputClosed = false; michael@0: michael@0: rv = PostEvent(MSG_ENSURE_CONNECT); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: NS_ADDREF(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::Close(nsresult reason) michael@0: { michael@0: if (NS_SUCCEEDED(reason)) michael@0: reason = NS_BASE_STREAM_CLOSED; michael@0: michael@0: mInput.CloseWithStatus(reason); michael@0: mOutput.CloseWithStatus(reason); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetSecurityInfo(nsISupports **secinfo) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: NS_IF_ADDREF(*secinfo = mSecInfo); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetSecurityCallbacks(nsIInterfaceRequestor **callbacks) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: NS_IF_ADDREF(*callbacks = mCallbacks); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::SetSecurityCallbacks(nsIInterfaceRequestor *callbacks) michael@0: { michael@0: nsCOMPtr threadsafeCallbacks; michael@0: NS_NewNotificationCallbacksAggregation(callbacks, nullptr, michael@0: NS_GetCurrentThread(), michael@0: getter_AddRefs(threadsafeCallbacks)); michael@0: michael@0: nsCOMPtr secinfo; michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mCallbacks = threadsafeCallbacks; michael@0: SOCKET_LOG(("Reset callbacks for secinfo=%p callbacks=%p\n", michael@0: mSecInfo.get(), mCallbacks.get())); michael@0: michael@0: secinfo = mSecInfo; michael@0: } michael@0: michael@0: // don't call into PSM while holding mLock!! michael@0: nsCOMPtr secCtrl(do_QueryInterface(secinfo)); michael@0: if (secCtrl) michael@0: secCtrl->SetNotificationCallbacks(threadsafeCallbacks); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::SetEventSink(nsITransportEventSink *sink, michael@0: nsIEventTarget *target) michael@0: { michael@0: nsCOMPtr temp; michael@0: if (target) { michael@0: nsresult rv = net_NewTransportEventSinkProxy(getter_AddRefs(temp), michael@0: sink, target); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: sink = temp.get(); michael@0: } michael@0: michael@0: MutexAutoLock lock(mLock); michael@0: mEventSink = sink; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::IsAlive(bool *result) michael@0: { michael@0: *result = false; michael@0: michael@0: nsresult conditionWhileLocked = NS_OK; michael@0: PRFileDescAutoLock fd(this, &conditionWhileLocked); michael@0: if (NS_FAILED(conditionWhileLocked) || !fd.IsInitialized()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXX do some idle-time based checks?? michael@0: michael@0: char c; michael@0: int32_t rval = PR_Recv(fd, &c, 1, PR_MSG_PEEK, 0); michael@0: michael@0: if ((rval > 0) || (rval < 0 && PR_GetError() == PR_WOULD_BLOCK_ERROR)) michael@0: *result = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetHost(nsACString &host) michael@0: { michael@0: host = SocketHost(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetPort(int32_t *port) michael@0: { michael@0: *port = (int32_t) SocketPort(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: const nsCString & michael@0: nsSocketTransport::SocketHost() michael@0: { michael@0: if (mProxyInfo && !mProxyTransparent) { michael@0: if (mProxyHostCache.IsEmpty()) { michael@0: mProxyInfo->GetHost(mProxyHostCache); michael@0: } michael@0: return mProxyHostCache; michael@0: } michael@0: else michael@0: return mHost; michael@0: } michael@0: michael@0: uint16_t michael@0: nsSocketTransport::SocketPort() michael@0: { michael@0: if (mProxyInfo && !mProxyTransparent) { michael@0: int32_t result; michael@0: mProxyInfo->GetPort(&result); michael@0: return (uint16_t) result; michael@0: } michael@0: else michael@0: return mPort; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetPeerAddr(NetAddr *addr) michael@0: { michael@0: // once we are in the connected state, mNetAddr will not change. michael@0: // so if we can verify that we are in the connected state, then michael@0: // we can freely access mNetAddr from any thread without being michael@0: // inside a critical section. michael@0: michael@0: if (!mNetAddrIsSet) { michael@0: SOCKET_LOG(("nsSocketTransport::GetPeerAddr [this=%p state=%d] " michael@0: "NOT_AVAILABLE because not yet connected.", this, mState)); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: memcpy(addr, &mNetAddr, sizeof(NetAddr)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetSelfAddr(NetAddr *addr) michael@0: { michael@0: // we must not call any PR methods on our file descriptor michael@0: // while holding mLock since those methods might re-enter michael@0: // socket transport code. michael@0: michael@0: PRFileDescAutoLock fd(this); michael@0: if (!fd.IsInitialized()) { michael@0: return NS_ERROR_NOT_CONNECTED; michael@0: } michael@0: michael@0: PRNetAddr prAddr; michael@0: michael@0: // NSPR doesn't tell us the socket address's length (as provided by michael@0: // the 'getsockname' system call), so we can't distinguish between michael@0: // named, unnamed, and abstract Unix domain socket names. (Server michael@0: // sockets are never unnamed, obviously, but client sockets can use michael@0: // any kind of address.) Clear prAddr first, so that the path for michael@0: // unnamed and abstract addresses will at least be reliably empty, michael@0: // and not garbage for unnamed sockets. michael@0: memset(&prAddr, 0, sizeof(prAddr)); michael@0: michael@0: nsresult rv = michael@0: (PR_GetSockName(fd, &prAddr) == PR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; michael@0: PRNetAddrToNetAddr(&prAddr, addr); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* nsINetAddr getScriptablePeerAddr (); */ michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetScriptablePeerAddr(nsINetAddr * *addr) michael@0: { michael@0: NetAddr rawAddr; michael@0: michael@0: nsresult rv; michael@0: rv = GetPeerAddr(&rawAddr); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: NS_ADDREF(*addr = new nsNetAddr(&rawAddr)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* nsINetAddr getScriptableSelfAddr (); */ michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetScriptableSelfAddr(nsINetAddr * *addr) michael@0: { michael@0: NetAddr rawAddr; michael@0: michael@0: nsresult rv; michael@0: rv = GetSelfAddr(&rawAddr); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: NS_ADDREF(*addr = new nsNetAddr(&rawAddr)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetTimeout(uint32_t type, uint32_t *value) michael@0: { michael@0: NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE); michael@0: *value = (uint32_t) mTimeouts[type]; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::SetTimeout(uint32_t type, uint32_t value) michael@0: { michael@0: NS_ENSURE_ARG_MAX(type, nsISocketTransport::TIMEOUT_READ_WRITE); michael@0: // truncate overly large timeout values. michael@0: mTimeouts[type] = (uint16_t) std::min(value, UINT16_MAX); michael@0: PostEvent(MSG_TIMEOUT_CHANGED); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::SetQoSBits(uint8_t aQoSBits) michael@0: { michael@0: // Don't do any checking here of bits. Why? Because as of RFC-4594 michael@0: // several different Class Selector and Assured Forwarding values michael@0: // have been defined, but that isn't to say more won't be added later. michael@0: // In that case, any checking would be an impediment to interoperating michael@0: // with newer QoS definitions. michael@0: michael@0: mQoSBits = aQoSBits; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetQoSBits(uint8_t *aQoSBits) michael@0: { michael@0: *aQoSBits = mQoSBits; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetRecvBufferSize(uint32_t *aSize) michael@0: { michael@0: PRFileDescAutoLock fd(this); michael@0: if (!fd.IsInitialized()) michael@0: return NS_ERROR_NOT_CONNECTED; michael@0: michael@0: nsresult rv = NS_OK; michael@0: PRSocketOptionData opt; michael@0: opt.option = PR_SockOpt_RecvBufferSize; michael@0: if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) michael@0: *aSize = opt.value.recv_buffer_size; michael@0: else michael@0: rv = NS_ERROR_FAILURE; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetSendBufferSize(uint32_t *aSize) michael@0: { michael@0: PRFileDescAutoLock fd(this); michael@0: if (!fd.IsInitialized()) michael@0: return NS_ERROR_NOT_CONNECTED; michael@0: michael@0: nsresult rv = NS_OK; michael@0: PRSocketOptionData opt; michael@0: opt.option = PR_SockOpt_SendBufferSize; michael@0: if (PR_GetSocketOption(fd, &opt) == PR_SUCCESS) michael@0: *aSize = opt.value.send_buffer_size; michael@0: else michael@0: rv = NS_ERROR_FAILURE; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::SetRecvBufferSize(uint32_t aSize) michael@0: { michael@0: PRFileDescAutoLock fd(this); michael@0: if (!fd.IsInitialized()) michael@0: return NS_ERROR_NOT_CONNECTED; michael@0: michael@0: nsresult rv = NS_OK; michael@0: PRSocketOptionData opt; michael@0: opt.option = PR_SockOpt_RecvBufferSize; michael@0: opt.value.recv_buffer_size = aSize; michael@0: if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) michael@0: rv = NS_ERROR_FAILURE; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::SetSendBufferSize(uint32_t aSize) michael@0: { michael@0: PRFileDescAutoLock fd(this); michael@0: if (!fd.IsInitialized()) michael@0: return NS_ERROR_NOT_CONNECTED; michael@0: michael@0: nsresult rv = NS_OK; michael@0: PRSocketOptionData opt; michael@0: opt.option = PR_SockOpt_SendBufferSize; michael@0: opt.value.send_buffer_size = aSize; michael@0: if (PR_SetSocketOption(fd, &opt) != PR_SUCCESS) michael@0: rv = NS_ERROR_FAILURE; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::OnLookupComplete(nsICancelable *request, michael@0: nsIDNSRecord *rec, michael@0: nsresult status) michael@0: { michael@0: // flag host lookup complete for the benefit of the ResolveHost method. michael@0: mResolving = false; michael@0: michael@0: MOZ_EVENT_TRACER_WAIT(this, "net::tcp::connect"); michael@0: nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, rec); michael@0: michael@0: // if posting a message fails, then we should assume that the socket michael@0: // transport has been shutdown. this should never happen! if it does michael@0: // it means that the socket transport service was shutdown before the michael@0: // DNS service. michael@0: if (NS_FAILED(rv)) michael@0: NS_WARNING("unable to post DNS lookup complete message"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetInterfaces(uint32_t *count, nsIID * **array) michael@0: { michael@0: return NS_CI_INTERFACE_GETTER_NAME(nsSocketTransport)(count, array); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetHelperForLanguage(uint32_t language, nsISupports **_retval) michael@0: { michael@0: *_retval = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetContractID(char * *aContractID) michael@0: { michael@0: *aContractID = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetClassDescription(char * *aClassDescription) michael@0: { michael@0: *aClassDescription = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetClassID(nsCID * *aClassID) michael@0: { michael@0: *aClassID = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetImplementationLanguage(uint32_t *aImplementationLanguage) michael@0: { michael@0: *aImplementationLanguage = nsIProgrammingLanguage::CPLUSPLUS; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetFlags(uint32_t *aFlags) michael@0: { michael@0: *aFlags = nsIClassInfo::THREADSAFE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc) michael@0: { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetConnectionFlags(uint32_t *value) michael@0: { michael@0: *value = mConnectionFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::SetConnectionFlags(uint32_t value) michael@0: { michael@0: mConnectionFlags = value; michael@0: mIsPrivate = value & nsISocketTransport::NO_PERMANENT_STORAGE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSocketTransport::OnKeepaliveEnabledPrefChange(bool aEnabled) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: michael@0: // The global pref toggles keepalive as a system feature; it only affects michael@0: // an individual socket if keepalive has been specifically enabled for it. michael@0: // So, ensure keepalive is configured correctly if previously enabled. michael@0: if (mKeepaliveEnabled) { michael@0: nsresult rv = SetKeepaliveEnabledInternal(aEnabled); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: SOCKET_LOG((" SetKeepaliveEnabledInternal [%s] failed rv[0x%x]", michael@0: aEnabled ? "enable" : "disable", rv)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsSocketTransport::SetKeepaliveEnabledInternal(bool aEnable) michael@0: { michael@0: MOZ_ASSERT(mKeepaliveIdleTimeS > 0 && michael@0: mKeepaliveIdleTimeS <= kMaxTCPKeepIdle); michael@0: MOZ_ASSERT(mKeepaliveRetryIntervalS > 0 && michael@0: mKeepaliveRetryIntervalS <= kMaxTCPKeepIntvl); michael@0: MOZ_ASSERT(mKeepaliveProbeCount > 0 && michael@0: mKeepaliveProbeCount <= kMaxTCPKeepCount); michael@0: michael@0: PRFileDescAutoLock fd(this); michael@0: if (NS_WARN_IF(!fd.IsInitialized())) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: // Only enable if keepalives are globally enabled, but ensure other michael@0: // options are set correctly on the fd. michael@0: bool enable = aEnable && mSocketTransportService->IsKeepaliveEnabled(); michael@0: nsresult rv = fd.SetKeepaliveVals(enable, michael@0: mKeepaliveIdleTimeS, michael@0: mKeepaliveRetryIntervalS, michael@0: mKeepaliveProbeCount); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: SOCKET_LOG((" SetKeepaliveVals failed rv[0x%x]", rv)); michael@0: return rv; michael@0: } michael@0: rv = fd.SetKeepaliveEnabled(enable); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: SOCKET_LOG((" SetKeepaliveEnabled failed rv[0x%x]", rv)); michael@0: return rv; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::GetKeepaliveEnabled(bool *aResult) michael@0: { michael@0: MOZ_ASSERT(aResult); michael@0: michael@0: *aResult = mKeepaliveEnabled; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSocketTransport::EnsureKeepaliveValsAreInitialized() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: int32_t val = -1; michael@0: if (mKeepaliveIdleTimeS == -1) { michael@0: rv = mSocketTransportService->GetKeepaliveIdleTime(&val); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: mKeepaliveIdleTimeS = val; michael@0: } michael@0: if (mKeepaliveRetryIntervalS == -1) { michael@0: rv = mSocketTransportService->GetKeepaliveRetryInterval(&val); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: mKeepaliveRetryIntervalS = val; michael@0: } michael@0: if (mKeepaliveProbeCount == -1) { michael@0: rv = mSocketTransportService->GetKeepaliveProbeCount(&val); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: mKeepaliveProbeCount = val; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::SetKeepaliveEnabled(bool aEnable) michael@0: { michael@0: #if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX) michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: michael@0: if (aEnable == mKeepaliveEnabled) { michael@0: SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] already %s.", michael@0: this, aEnable ? "enabled" : "disabled")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (aEnable) { michael@0: rv = EnsureKeepaliveValsAreInitialized(); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: SOCKET_LOG((" SetKeepaliveEnabled [%p] " michael@0: "error [0x%x] initializing keepalive vals", michael@0: this, rv)); michael@0: return rv; michael@0: } michael@0: } michael@0: SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled [%p] " michael@0: "%s, idle time[%ds] retry interval[%ds] packet count[%d]: " michael@0: "globally %s.", michael@0: this, aEnable ? "enabled" : "disabled", michael@0: mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS, michael@0: mKeepaliveProbeCount, michael@0: mSocketTransportService->IsKeepaliveEnabled() ? michael@0: "enabled" : "disabled")); michael@0: michael@0: // Set mKeepaliveEnabled here so that state is maintained; it is possible michael@0: // that we're in between fds, e.g. the 1st IP address failed, so we're about michael@0: // to retry on a 2nd from the DNS record. michael@0: mKeepaliveEnabled = aEnable; michael@0: michael@0: rv = SetKeepaliveEnabledInternal(aEnable); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: SOCKET_LOG((" SetKeepaliveEnabledInternal failed rv[0x%x]", rv)); michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: #else /* !(defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX)) */ michael@0: SOCKET_LOG(("nsSocketTransport::SetKeepaliveEnabled unsupported platform")); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSocketTransport::SetKeepaliveVals(int32_t aIdleTime, michael@0: int32_t aRetryInterval) michael@0: { michael@0: #if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX) michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: if (NS_WARN_IF(aRetryInterval <= 0 || michael@0: kMaxTCPKeepIntvl < aRetryInterval)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: if (aIdleTime == mKeepaliveIdleTimeS && michael@0: aRetryInterval == mKeepaliveRetryIntervalS) { michael@0: SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals [%p] idle time " michael@0: "already %ds and retry interval already %ds.", michael@0: this, mKeepaliveIdleTimeS, michael@0: mKeepaliveRetryIntervalS)); michael@0: return NS_OK; michael@0: } michael@0: mKeepaliveIdleTimeS = aIdleTime; michael@0: mKeepaliveRetryIntervalS = aRetryInterval; michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (mKeepaliveProbeCount == -1) { michael@0: int32_t val = -1; michael@0: nsresult rv = mSocketTransportService->GetKeepaliveProbeCount(&val); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: mKeepaliveProbeCount = val; michael@0: } michael@0: michael@0: SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals [%p] " michael@0: "keepalive %s, idle time[%ds] retry interval[%ds] " michael@0: "packet count[%d]", michael@0: this, mKeepaliveEnabled ? "enabled" : "disabled", michael@0: mKeepaliveIdleTimeS, mKeepaliveRetryIntervalS, michael@0: mKeepaliveProbeCount)); michael@0: michael@0: PRFileDescAutoLock fd(this); michael@0: if (NS_WARN_IF(!fd.IsInitialized())) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: rv = fd.SetKeepaliveVals(mKeepaliveEnabled, michael@0: mKeepaliveIdleTimeS, michael@0: mKeepaliveRetryIntervalS, michael@0: mKeepaliveProbeCount); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: return NS_OK; michael@0: #else michael@0: SOCKET_LOG(("nsSocketTransport::SetKeepaliveVals unsupported platform")); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: #endif michael@0: } michael@0: michael@0: #ifdef ENABLE_SOCKET_TRACING michael@0: michael@0: #include michael@0: #include michael@0: #include "prenv.h" michael@0: michael@0: static void michael@0: DumpBytesToFile(const char *path, const char *header, const char *buf, int32_t n) michael@0: { michael@0: FILE *fp = fopen(path, "a"); michael@0: michael@0: fprintf(fp, "\n%s [%d bytes]\n", header, n); michael@0: michael@0: const unsigned char *p; michael@0: while (n) { michael@0: p = (const unsigned char *) buf; michael@0: michael@0: int32_t i, row_max = std::min(16, n); michael@0: michael@0: for (i = 0; i < row_max; ++i) michael@0: fprintf(fp, "%02x ", *p++); michael@0: for (i = row_max; i < 16; ++i) michael@0: fprintf(fp, " "); michael@0: michael@0: p = (const unsigned char *) buf; michael@0: for (i = 0; i < row_max; ++i, ++p) { michael@0: if (isprint(*p)) michael@0: fprintf(fp, "%c", *p); michael@0: else michael@0: fprintf(fp, "."); michael@0: } michael@0: michael@0: fprintf(fp, "\n"); michael@0: buf += row_max; michael@0: n -= row_max; michael@0: } michael@0: michael@0: fprintf(fp, "\n"); michael@0: fclose(fp); michael@0: } michael@0: michael@0: void michael@0: nsSocketTransport::TraceInBuf(const char *buf, int32_t n) michael@0: { michael@0: char *val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG"); michael@0: if (!val || !*val) michael@0: return; michael@0: michael@0: nsAutoCString header; michael@0: header.Assign(NS_LITERAL_CSTRING("Reading from: ") + mHost); michael@0: header.Append(':'); michael@0: header.AppendInt(mPort); michael@0: michael@0: DumpBytesToFile(val, header.get(), buf, n); michael@0: } michael@0: michael@0: void michael@0: nsSocketTransport::TraceOutBuf(const char *buf, int32_t n) michael@0: { michael@0: char *val = PR_GetEnv("NECKO_SOCKET_TRACE_LOG"); michael@0: if (!val || !*val) michael@0: return; michael@0: michael@0: nsAutoCString header; michael@0: header.Assign(NS_LITERAL_CSTRING("Writing to: ") + mHost); michael@0: header.Append(':'); michael@0: header.AppendInt(mPort); michael@0: michael@0: DumpBytesToFile(val, header.get(), buf, n); michael@0: } michael@0: michael@0: #endif michael@0: michael@0: static void LogNSPRError(const char* aPrefix, const void *aObjPtr) michael@0: { michael@0: #if defined(PR_LOGGING) && defined(DEBUG) michael@0: PRErrorCode errCode = PR_GetError(); michael@0: int errLen = PR_GetErrorTextLength(); michael@0: nsAutoCString errStr; michael@0: if (errLen > 0) { michael@0: errStr.SetLength(errLen); michael@0: PR_GetErrorText(errStr.BeginWriting()); michael@0: } michael@0: NS_WARNING(nsPrintfCString( michael@0: "%s [%p] NSPR error[0x%x] %s.", michael@0: aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode, michael@0: errLen > 0 ? errStr.BeginReading() : "").get()); michael@0: #endif michael@0: } michael@0: michael@0: nsresult michael@0: nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled(bool aEnable) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: MOZ_ASSERT(!(aEnable && !gSocketTransportService->IsKeepaliveEnabled()), michael@0: "Cannot enable keepalive if global pref is disabled!"); michael@0: if (aEnable && !gSocketTransportService->IsKeepaliveEnabled()) { michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: } michael@0: michael@0: PRSocketOptionData opt; michael@0: michael@0: opt.option = PR_SockOpt_Keepalive; michael@0: opt.value.keep_alive = aEnable; michael@0: PRStatus status = PR_SetSocketOption(mFd, &opt); michael@0: if (NS_WARN_IF(status != PR_SUCCESS)) { michael@0: LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveEnabled", michael@0: mSocketTransport); michael@0: return ErrorAccordingToNSPR(PR_GetError()); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void LogOSError(const char *aPrefix, const void *aObjPtr) michael@0: { michael@0: #if defined(PR_LOGGING) && defined(DEBUG) michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: michael@0: #ifdef XP_WIN michael@0: DWORD errCode = WSAGetLastError(); michael@0: LPVOID errMessage; michael@0: FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | michael@0: FORMAT_MESSAGE_FROM_SYSTEM | michael@0: FORMAT_MESSAGE_IGNORE_INSERTS, michael@0: NULL, michael@0: errCode, michael@0: MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), michael@0: (LPTSTR) &errMessage, michael@0: 0, NULL); michael@0: #else michael@0: int errCode = errno; michael@0: char *errMessage = strerror(errno); michael@0: #endif michael@0: NS_WARNING(nsPrintfCString( michael@0: "%s [%p] OS error[0x%x] %s", michael@0: aPrefix ? aPrefix : "nsSocketTransport", aObjPtr, errCode, michael@0: errMessage ? errMessage : "").get()); michael@0: #ifdef XP_WIN michael@0: LocalFree(errMessage); michael@0: #endif michael@0: #endif michael@0: } michael@0: michael@0: /* XXX PR_SetSockOpt does not support setting keepalive values, so native michael@0: * handles and platform specific apis (setsockopt, WSAIOCtl) are used in this michael@0: * file. Requires inclusion of NSPR private/pprio.h, and platform headers. michael@0: */ michael@0: michael@0: nsresult michael@0: nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals(bool aEnabled, michael@0: int aIdleTime, michael@0: int aRetryInterval, michael@0: int aProbeCount) michael@0: { michael@0: #if defined(XP_WIN) || defined(XP_UNIX) || defined(XP_MACOSX) michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread, "wrong thread"); michael@0: if (NS_WARN_IF(aIdleTime <= 0 || kMaxTCPKeepIdle < aIdleTime)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: if (NS_WARN_IF(aRetryInterval <= 0 || michael@0: kMaxTCPKeepIntvl < aRetryInterval)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: if (NS_WARN_IF(aProbeCount <= 0 || kMaxTCPKeepCount < aProbeCount)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: PROsfd sock = PR_FileDesc2NativeHandle(mFd); michael@0: if (NS_WARN_IF(sock == -1)) { michael@0: LogNSPRError("nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals", michael@0: mSocketTransport); michael@0: return ErrorAccordingToNSPR(PR_GetError()); michael@0: } michael@0: #endif michael@0: michael@0: #if defined(XP_WIN) michael@0: // Windows allows idle time and retry interval to be set; NOT ping count. michael@0: struct tcp_keepalive keepalive_vals = { michael@0: (int)aEnabled, michael@0: // Windows uses msec. michael@0: aIdleTime * 1000, michael@0: aRetryInterval * 1000 michael@0: }; michael@0: DWORD bytes_returned; michael@0: int err = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &keepalive_vals, michael@0: sizeof(keepalive_vals), NULL, 0, &bytes_returned, NULL, michael@0: NULL); michael@0: if (NS_WARN_IF(err)) { michael@0: LogOSError("nsSocketTransport WSAIoctl failed", mSocketTransport); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: return NS_OK; michael@0: michael@0: #elif defined(XP_MACOSX) michael@0: // OS X uses sec; only supports idle time being set. michael@0: int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, michael@0: &aIdleTime, sizeof(aIdleTime)); michael@0: if (NS_WARN_IF(err)) { michael@0: LogOSError("nsSocketTransport Failed setting TCP_KEEPALIVE", michael@0: mSocketTransport); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: return NS_OK; michael@0: michael@0: #elif defined(XP_UNIX) michael@0: // Not all *nix OSes support the following setsockopt() options michael@0: // ... but we assume they are supported in the Android kernel; michael@0: // build errors will tell us if they are not. michael@0: #if defined(ANDROID) || defined(TCP_KEEPIDLE) michael@0: // Idle time until first keepalive probe; interval between ack'd probes; seconds. michael@0: int err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, michael@0: &aIdleTime, sizeof(aIdleTime)); michael@0: if (NS_WARN_IF(err)) { michael@0: LogOSError("nsSocketTransport Failed setting TCP_KEEPIDLE", michael@0: mSocketTransport); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: #endif michael@0: #if defined(ANDROID) || defined(TCP_KEEPINTVL) michael@0: // Interval between unack'd keepalive probes; seconds. michael@0: err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, michael@0: &aRetryInterval, sizeof(aRetryInterval)); michael@0: if (NS_WARN_IF(err)) { michael@0: LogOSError("nsSocketTransport Failed setting TCP_KEEPINTVL", michael@0: mSocketTransport); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: #endif michael@0: #if defined(ANDROID) || defined(TCP_KEEPCNT) michael@0: // Number of unack'd keepalive probes before connection times out. michael@0: err = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, michael@0: &aProbeCount, sizeof(aProbeCount)); michael@0: if (NS_WARN_IF(err)) { michael@0: LogOSError("nsSocketTransport Failed setting TCP_KEEPCNT", michael@0: mSocketTransport); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: #endif michael@0: return NS_OK; michael@0: #else michael@0: MOZ_ASSERT(false, "nsSocketTransport::PRFileDescAutoLock::SetKeepaliveVals " michael@0: "called on unsupported platform!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: #endif michael@0: }