diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/base/nsSocketTransport2.h | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/netwerk/base/nsSocketTransport2.h b/netwerk/base/nsSocketTransport2.h new file mode 100644 index 0000000000..cafa679404 --- /dev/null +++ b/netwerk/base/nsSocketTransport2.h @@ -0,0 +1,485 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSocketTransport2_h__ +#define nsSocketTransport2_h__ + +#ifdef DEBUG_darinf +# define ENABLE_SOCKET_TRACING +#endif + +#include <functional> + +#include "mozilla/Mutex.h" +#include "nsSocketTransportService2.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +#include "nsIInterfaceRequestor.h" +#include "nsISocketTransport.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIDNSListener.h" +#include "nsIDNSRecord.h" +#include "nsIClassInfo.h" +#include "mozilla/net/DNS.h" +#include "nsASocketHandler.h" +#include "mozilla/Telemetry.h" + +#include "prerror.h" +#include "ssl.h" + +class nsICancelable; +class nsIDNSRecord; +class nsIInterfaceRequestor; + +//----------------------------------------------------------------------------- + +// after this short interval, we will return to PR_Poll +#define NS_SOCKET_CONNECT_TIMEOUT PR_MillisecondsToInterval(20) + +//----------------------------------------------------------------------------- + +namespace mozilla { +namespace net { + +nsresult ErrorAccordingToNSPR(PRErrorCode errorCode); + +class nsSocketTransport; + +class nsSocketInputStream : public nsIAsyncInputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + explicit nsSocketInputStream(nsSocketTransport*); + virtual ~nsSocketInputStream() = default; + + bool IsReferenced() { return mReaderRefCnt > 0; } + nsresult Condition() { return mCondition; } + uint64_t ByteCount() { return mByteCount; } + + // called by the socket transport on the socket thread... + void OnSocketReady(nsresult condition); + + private: + nsSocketTransport* mTransport; + ThreadSafeAutoRefCnt mReaderRefCnt{0}; + + // access to these is protected by mTransport->mLock + nsresult mCondition{NS_OK}; + nsCOMPtr<nsIInputStreamCallback> mCallback; + uint32_t mCallbackFlags{0}; + uint64_t mByteCount{0}; +}; + +//----------------------------------------------------------------------------- + +class nsSocketOutputStream : public nsIAsyncOutputStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_NSIASYNCOUTPUTSTREAM + + explicit nsSocketOutputStream(nsSocketTransport*); + virtual ~nsSocketOutputStream() = default; + + bool IsReferenced() { return mWriterRefCnt > 0; } + nsresult Condition() { return mCondition; } + uint64_t ByteCount() { return mByteCount; } + + // called by the socket transport on the socket thread... + void OnSocketReady(nsresult condition); + + private: + static nsresult WriteFromSegments(nsIInputStream*, void*, const char*, + uint32_t offset, uint32_t count, + uint32_t* countRead); + + nsSocketTransport* mTransport; + ThreadSafeAutoRefCnt mWriterRefCnt{0}; + + // access to these is protected by mTransport->mLock + nsresult mCondition{NS_OK}; + nsCOMPtr<nsIOutputStreamCallback> mCallback; + uint32_t mCallbackFlags{0}; + uint64_t mByteCount{0}; +}; + +//----------------------------------------------------------------------------- + +class nsSocketTransport final : public nsASocketHandler, + public nsISocketTransport, + public nsIDNSListener, + public nsIClassInfo, + public nsIInterfaceRequestor { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORT + NS_DECL_NSISOCKETTRANSPORT + NS_DECL_NSIDNSLISTENER + NS_DECL_NSICLASSINFO + NS_DECL_NSIINTERFACEREQUESTOR + + nsSocketTransport(); + + // this method instructs the socket transport to open a socket of the + // given type(s) to the given host or proxy. + nsresult Init(const nsTArray<nsCString>& socketTypes, const nsACString& host, + uint16_t port, const nsACString& hostRoute, uint16_t portRoute, + nsIProxyInfo* proxyInfo, nsIDNSRecord* dnsRecord); + + // this method instructs the socket transport to use an already connected + // socket with the given address. + nsresult InitWithConnectedSocket(PRFileDesc* socketFD, const NetAddr* addr); + + // this method instructs the socket transport to use an already connected + // socket with the given address, and additionally supplies the security + // callbacks interface requestor. + nsresult InitWithConnectedSocket(PRFileDesc* aFD, const NetAddr* aAddr, + nsIInterfaceRequestor* aCallbacks); + +#ifdef XP_UNIX + // This method instructs the socket transport to open a socket + // connected to the given Unix domain address. We can only create + // unlayered, simple, stream sockets. + nsresult InitWithFilename(const char* filename); + + // This method instructs the socket transport to open a socket + // connected to the given Unix domain address that includes abstract + // socket address. If using abstract socket address, first character of + // name parameter has to be \0. + // We can only create unlayered, simple, stream sockets. + nsresult InitWithName(const char* name, size_t len); +#endif + + // nsASocketHandler methods: + void OnSocketReady(PRFileDesc*, int16_t outFlags) override; + void OnSocketDetached(PRFileDesc*) override; + void IsLocal(bool* aIsLocal) override; + void OnKeepaliveEnabledPrefChange(bool aEnabled) final; + + // called when a socket event is handled + void OnSocketEvent(uint32_t type, nsresult status, nsISupports* param, + std::function<void()>&& task); + + uint64_t ByteCountReceived() override { return mInput.ByteCount(); } + uint64_t ByteCountSent() override { return mOutput.ByteCount(); } + static void CloseSocket(PRFileDesc* aFd, bool aTelemetryEnabled); + static void SendPRBlockingTelemetry( + PRIntervalTime aStart, Telemetry::HistogramID aIDNormal, + Telemetry::HistogramID aIDShutdown, + Telemetry::HistogramID aIDConnectivityChange, + Telemetry::HistogramID aIDLinkChange, Telemetry::HistogramID aIDOffline); + + protected: + virtual ~nsSocketTransport(); + + private: + // event types + enum { + MSG_ENSURE_CONNECT, + MSG_DNS_LOOKUP_COMPLETE, + MSG_RETRY_INIT_SOCKET, + MSG_TIMEOUT_CHANGED, + MSG_INPUT_CLOSED, + MSG_INPUT_PENDING, + MSG_OUTPUT_CLOSED, + MSG_OUTPUT_PENDING + }; + nsresult PostEvent(uint32_t type, nsresult status = NS_OK, + nsISupports* param = nullptr, + std::function<void()>&& task = nullptr); + + enum { + STATE_CLOSED, + STATE_IDLE, + STATE_RESOLVING, + STATE_CONNECTING, + STATE_TRANSFERRING + }; + + // Safer way to get and automatically release PRFileDesc objects. + class MOZ_STACK_CLASS PRFileDescAutoLock { + public: + explicit PRFileDescAutoLock(nsSocketTransport* aSocketTransport, + nsresult* aConditionWhileLocked = nullptr) + : mSocketTransport(aSocketTransport), mFd(nullptr) { + MOZ_ASSERT(aSocketTransport); + MutexAutoLock lock(mSocketTransport->mLock); + if (aConditionWhileLocked) { + *aConditionWhileLocked = mSocketTransport->mCondition; + if (NS_FAILED(mSocketTransport->mCondition)) { + return; + } + } + mFd = mSocketTransport->GetFD_Locked(); + } + ~PRFileDescAutoLock() { + MutexAutoLock lock(mSocketTransport->mLock); + if (mFd) { + mSocketTransport->ReleaseFD_Locked(mFd); + } + } + bool IsInitialized() { return mFd; } + operator PRFileDesc*() { return mFd; } + nsresult SetKeepaliveEnabled(bool aEnable); + nsresult SetKeepaliveVals(bool aEnabled, int aIdleTime, int aRetryInterval, + int aProbeCount); + + private: + operator PRFileDescAutoLock*() { return nullptr; } + + // Weak ptr to nsSocketTransport since this is a stack class only. + nsSocketTransport* mSocketTransport; + PRFileDesc* mFd; + }; + friend class PRFileDescAutoLock; + + class LockedPRFileDesc { + public: + explicit LockedPRFileDesc(nsSocketTransport* aSocketTransport) + : mSocketTransport(aSocketTransport), mFd(nullptr) { + MOZ_ASSERT(aSocketTransport); + } + ~LockedPRFileDesc() = default; + bool IsInitialized() { return mFd; } + LockedPRFileDesc& operator=(PRFileDesc* aFd) { + mSocketTransport->mLock.AssertCurrentThreadOwns(); + mFd = aFd; + return *this; + } + operator PRFileDesc*() { + if (mSocketTransport->mAttached) { + mSocketTransport->mLock.AssertCurrentThreadOwns(); + } + return mFd; + } + bool operator==(PRFileDesc* aFd) { + mSocketTransport->mLock.AssertCurrentThreadOwns(); + return mFd == aFd; + } + + private: + operator LockedPRFileDesc*() { return nullptr; } + // Weak ptr to nsSocketTransport since it owns this class. + nsSocketTransport* mSocketTransport; + PRFileDesc* mFd; + }; + friend class LockedPRFileDesc; + + //------------------------------------------------------------------------- + // these members are "set" at initialization time and are never modified + // afterwards. this allows them to be safely accessed from any thread. + //------------------------------------------------------------------------- + + // socket type info: + nsTArray<nsCString> mTypes; + nsCString mHost; + nsCString mProxyHost; + nsCString mOriginHost; + uint16_t mPort{0}; + nsCOMPtr<nsIProxyInfo> mProxyInfo; + uint16_t mProxyPort{0}; + uint16_t mOriginPort{0}; + bool mProxyTransparent{false}; + bool mProxyTransparentResolvesHost{false}; + bool mHttpsProxy{false}; + uint32_t mConnectionFlags{0}; + // When we fail to connect using a prefered IP family, we tell the consumer to + // reset the IP family preference on the connection entry. + bool mResetFamilyPreference{false}; + uint32_t mTlsFlags{0}; + bool mReuseAddrPort{false}; + + // The origin attributes are used to create sockets. The first party domain + // will eventually be used to isolate OCSP cache and is only non-empty when + // "privacy.firstparty.isolate" is enabled. Setting this is the only way to + // carry origin attributes down to NSPR layers which are final consumers. + // It must be set before the socket transport is built. + OriginAttributes mOriginAttributes; + + uint16_t SocketPort() { + return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyPort : mPort; + } + const nsCString& SocketHost() { + return (!mProxyHost.IsEmpty() && !mProxyTransparent) ? mProxyHost : mHost; + } + + Atomic<bool> mInputClosed{true}; + Atomic<bool> mOutputClosed{true}; + + //------------------------------------------------------------------------- + // members accessible only on the socket transport thread: + // (the exception being initialization/shutdown time) + //------------------------------------------------------------------------- + + // socket state vars: + uint32_t mState{STATE_CLOSED}; // STATE_??? flags + bool mAttached{false}; + + // this flag is used to determine if the results of a host lookup arrive + // recursively or not. this flag is not protected by any lock. + bool mResolving{false}; + + nsCOMPtr<nsICancelable> mDNSRequest; + nsCOMPtr<nsIDNSAddrRecord> mDNSRecord; + + nsCString mEchConfig; + bool mEchConfigUsed = false; + bool mResolvedByTRR{false}; + nsIRequest::TRRMode mEffectiveTRRMode{nsIRequest::TRR_DEFAULT_MODE}; + nsITRRSkipReason::value mTRRSkipReason{nsITRRSkipReason::TRR_UNSET}; + + nsCOMPtr<nsISupports> mInputCopyContext; + nsCOMPtr<nsISupports> mOutputCopyContext; + + // mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have + // reached STATE_TRANSFERRING. It must not change after that. + void SetSocketName(PRFileDesc* fd); + NetAddr mNetAddr; + NetAddr mSelfAddr; // getsockname() + Atomic<bool, Relaxed> mNetAddrIsSet{false}; + Atomic<bool, Relaxed> mSelfAddrIsSet{false}; + + UniquePtr<NetAddr> mBindAddr; + + // socket methods (these can only be called on the socket thread): + + void SendStatus(nsresult status); + nsresult ResolveHost(); + nsresult BuildSocket(PRFileDesc*&, bool&, bool&); + nsresult InitiateSocket(); + bool RecoverFromError(); + + void OnMsgInputPending() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mState == STATE_TRANSFERRING) { + mPollFlags |= (PR_POLL_READ | PR_POLL_EXCEPT); + } + } + void OnMsgOutputPending() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mState == STATE_TRANSFERRING) { + mPollFlags |= (PR_POLL_WRITE | PR_POLL_EXCEPT); + } + } + void OnMsgInputClosed(nsresult reason); + void OnMsgOutputClosed(nsresult reason); + + // called when the socket is connected + void OnSocketConnected(); + + //------------------------------------------------------------------------- + // socket input/output objects. these may be accessed on any thread with + // the exception of some specific methods (XXX). + + // protects members in this section. + Mutex mLock MOZ_UNANNOTATED{"nsSocketTransport.mLock"}; + LockedPRFileDesc mFD; + nsrefcnt mFDref{0}; // mFD is closed when mFDref goes to zero. + bool mFDconnected{false}; // mFD is available to consumer when TRUE. + + // A delete protector reference to gSocketTransportService held for lifetime + // of 'this'. Sometimes used interchangably with gSocketTransportService due + // to scoping. + RefPtr<nsSocketTransportService> mSocketTransportService; + + nsCOMPtr<nsIInterfaceRequestor> mCallbacks; + nsCOMPtr<nsITransportEventSink> mEventSink; + nsCOMPtr<nsITLSSocketControl> mTLSSocketControl; + + nsSocketInputStream mInput; + nsSocketOutputStream mOutput; + + friend class nsSocketInputStream; + friend class nsSocketOutputStream; + + // socket timeouts are protected by mLock. + uint16_t mTimeouts[2]{0}; + + // linger options to use when closing + bool mLingerPolarity{false}; + int16_t mLingerTimeout{0}; + + // QoS setting for socket + uint8_t mQoSBits{0x00}; + + // + // mFD access methods: called with mLock held. + // + PRFileDesc* GetFD_Locked(); + void ReleaseFD_Locked(PRFileDesc* fd); + + // + // stream state changes (called outside mLock): + // + void OnInputClosed(nsresult reason) { + // no need to post an event if called on the socket thread + if (OnSocketThread()) { + OnMsgInputClosed(reason); + } else { + PostEvent(MSG_INPUT_CLOSED, reason); + } + } + void OnInputPending() { + // no need to post an event if called on the socket thread + if (OnSocketThread()) { + OnMsgInputPending(); + } else { + PostEvent(MSG_INPUT_PENDING); + } + } + void OnOutputClosed(nsresult reason) { + // no need to post an event if called on the socket thread + if (OnSocketThread()) { + OnMsgOutputClosed(reason); // XXX need to not be inside lock! + } else { + PostEvent(MSG_OUTPUT_CLOSED, reason); + } + } + void OnOutputPending() { + // no need to post an event if called on the socket thread + if (OnSocketThread()) { + OnMsgOutputPending(); + } else { + PostEvent(MSG_OUTPUT_PENDING); + } + } + +#ifdef ENABLE_SOCKET_TRACING + void TraceInBuf(const char* buf, int32_t n); + void TraceOutBuf(const char* buf, int32_t n); +#endif + + // Reads prefs to get default keepalive config. + nsresult EnsureKeepaliveValsAreInitialized(); + + // Groups calls to fd.SetKeepaliveEnabled and fd.SetKeepaliveVals. + nsresult SetKeepaliveEnabledInternal(bool aEnable); + + // True if keepalive has been enabled by the socket owner. Note: Keepalive + // must also be enabled globally for it to be enabled in TCP. + bool mKeepaliveEnabled{false}; + + // Keepalive config (support varies by platform). + int32_t mKeepaliveIdleTimeS{-1}; + int32_t mKeepaliveRetryIntervalS{-1}; + int32_t mKeepaliveProbeCount{-1}; + + Atomic<bool> mDoNotRetryToConnect{false}; + + // Whether the port remapping has already been applied. We definitely want to + // prevent duplicate calls in case of chaining remapping. + bool mPortRemappingApplied = false; + + bool mExternalDNSResolution = false; + bool mRetryDnsIfPossible = false; +}; + +} // namespace net +} // namespace mozilla + +#endif // !nsSocketTransport_h__ |