/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=4 sw=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef Http3Session_H__ #define Http3Session_H__ #include "HttpTrafficAnalyzer.h" #include "mozilla/UniquePtr.h" #include "mozilla/WeakPtr.h" #include "mozilla/net/NeqoHttp3Conn.h" #include "nsAHttpConnection.h" #include "nsDeque.h" #include "nsISupportsImpl.h" #include "nsITimer.h" #include "nsIUDPSocket.h" #include "nsRefPtrHashtable.h" #include "nsTHashMap.h" #include "nsWeakReference.h" /* * WebTransport * * Http3Session and the underlying neqo code support multiplexing of multiple * WebTransport and multiplexing WebTransport sessions with regular HTTP * traffic. Whether WebTransport sessions are polled, will be controlled by the * nsHttpConnectionMgr. * * WebTransport support is negotiated using HTTP/3 setting. Before the settings * are available all WebTransport transactions are queued in * mWaitingForWebTransportNegotiation. The information on whether WebTransport * is supported is received via an HTTP/3 event. The event is * Http3Event::Tag::WebTransport with the value * WebTransportEventExternal::Tag::Negotiated that can be true or false. If * the WebTransport feature has been negotiated, queued transactions will be * activated otherwise they will be canceled(i.e. WebTransportNegotiationDone). * * The lifetime of a WebTransport session * * A WebTransport lifetime consists of 2 parts: * - WebTransport session setup * - WebTransport session active time * * WebTransport session setup: * A WebTransport session uses a regular HTTP request for negotiation. * Therefore when a new WebTransport is started a nsHttpChannel and the * corresponding nsHttpTransaction are created. The nsHttpTransaction is * dispatched to a Http3Session and after the * WebTransportEventExternal::Tag::Negotiated event it behaves almost the same * as a regular transaction, e.g. it is added to mStreamTransactionHash and * mStreamIdHash. For activating the session NeqoHttp3Conn::CreateWebTransport * is called instead of NeqoHttp3Conn::Fetch(this is called for the regular * HTTP requests). In this phase, the WebTransport session is canceled in the * same way a regular request is canceled, by canceling the corresponding * nsHttpChannel. If HTTP/3 connection is closed in this phase the * corresponding nsHttpTransaction is canceled and this may cause the * transaction to be restarted (this is the existing restart logic) or the * error is propagated to the nsHttpChannel and its listener(via OnStartRequest * and OnStopRequest as the regular HTTP request). * The phase ends when a connection breaks or when the event * Http3Event::Tag::WebTransport with the value * WebTransportEventExternal::Tag::Session is received. The parameter * aData(from NeqoHttp3Conn::GetEvent) contain the HTTP head of the response. * The headers may be: * - failed code, i.e. anything except 200. In this case, the nsHttpTransaction * behaves the same as a normal HTTP request and the nsHttpChannel listener * will be informed via OnStartRequest and OnStopRequest calls. * - success code, i.e. 200. The code will be propagated to the * nsHttpTransaction. The transaction will parse the header and call * Http3Session::GetWebTransportSession. The function transfers WebTransport * session into the next phase: * - Removes nsHttpTransaction from mStreamTransactionHash. * - Adds the stream to mWebTransportSessions * - The nsHttpTransaction supplies Http3WebTransportSession to the * WebTransportSessionProxy and vice versa. * TODO remove this circular referencing. * * WebTransport session active time: * During this phase the following actions are possible: * - Cancelling a WebTransport session by the application: * The application calls Http3WebTransportSession::CloseSession. This * transfers Http3WebTransportSession into the CLOSE_PENDING state and calls * Http3Session::ConnectSlowConsumer to add itself to the “ready for reading * queue”. Consequently, the Http3Session will call * Http3WebTransportSession::WriteSegments and * Http3Session::CloseWebTransport will be called to send the closing signal * to the peer. After this, the Http3WebTransportSession is in the state DONE * and it will be removed from the Http3Session(the CloseStream function * takes care of this). * - The peer sending a session closing signal: * Http3Session will receive a Http3Event::Tag::WebTransport event with value * WebTransportEventExternal::Tag::SessionClosed. The * Http3WebTransportSession::OnSessionClosed function for the corresponding * stream wil be called. The function will inform the corresponding * WebTransportSessionProxy by calling OnSessionClosed function. The * Http3WebTransportSession is in the state DONE and will be removed from the * Http3Session(the CloseStream function takes care of this). */ namespace mozilla::net { class HttpConnectionUDP; class Http3StreamBase; class QuicSocketControl; // IID for the Http3Session interface #define NS_HTTP3SESSION_IID \ { \ 0x8fc82aaf, 0xc4ef, 0x46ed, { \ 0x89, 0x41, 0x93, 0x95, 0x8f, 0xac, 0x4f, 0x21 \ } \ } enum class EchExtensionStatus { kNotPresent, // No ECH Extension was sent kGREASE, // A GREASE ECH Extension was sent kReal // A 'real' ECH Extension was sent }; class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection { public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_HTTP3SESSION_IID) NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSAHTTPTRANSACTION NS_DECL_NSAHTTPCONNECTION(mConnection) Http3Session(); nsresult Init(const nsHttpConnectionInfo* aConnInfo, nsINetAddr* selfAddr, nsINetAddr* peerAddr, HttpConnectionUDP* udpConn, uint32_t controlFlags, nsIInterfaceRequestor* callbacks); bool IsConnected() const { return mState == CONNECTED; } bool CanSendData() const { return (mState == CONNECTED) || (mState == ZERORTT); } bool IsClosing() const { return (mState == CLOSING || mState == CLOSED); } bool IsClosed() const { return mState == CLOSED; } bool AddStream(nsAHttpTransaction* aHttpTransaction, int32_t aPriority, nsIInterfaceRequestor* aCallbacks); bool CanReuse(); // The following functions are used by Http3Stream and // Http3WebTransportSession: nsresult TryActivating(const nsACString& aMethod, const nsACString& aScheme, const nsACString& aAuthorityHeader, const nsACString& aPath, const nsACString& aHeaders, uint64_t* aStreamId, Http3StreamBase* aStream); // The folowing functions are used by Http3Stream: void CloseSendingSide(uint64_t aStreamId); nsresult SendRequestBody(uint64_t aStreamId, const char* buf, uint32_t count, uint32_t* countRead); nsresult ReadResponseHeaders(uint64_t aStreamId, nsTArray& aResponseHeaders, bool* aFin); nsresult ReadResponseData(uint64_t aStreamId, char* aBuf, uint32_t aCount, uint32_t* aCountWritten, bool* aFin); // The folowing functions are used by Http3WebTransportSession: nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError, const nsACString& aMessage); nsresult CreateWebTransportStream(uint64_t aSessionId, WebTransportStreamType aStreamType, uint64_t* aStreamId); void CloseStream(Http3StreamBase* aStream, nsresult aResult); void CloseStreamInternal(Http3StreamBase* aStream, nsresult aResult); void SetCleanShutdown(bool aCleanShutdown) { mCleanShutdown = aCleanShutdown; } bool TestJoinConnection(const nsACString& hostname, int32_t port); bool JoinConnection(const nsACString& hostname, int32_t port); void TransactionHasDataToWrite(nsAHttpTransaction* caller) override; void TransactionHasDataToRecv(nsAHttpTransaction* caller) override; [[nodiscard]] nsresult GetTransactionTLSSocketControl( nsITLSSocketControl**) override; // This function will be called by QuicSocketControl when the certificate // verification is done. void Authenticated(int32_t aError); nsresult ProcessOutputAndEvents(nsIUDPSocket* socket); void ReportHttp3Connection(); int64_t GetBytesWritten() { return mTotalBytesWritten; } int64_t BytesRead() { return mTotalBytesRead; } nsresult SendData(nsIUDPSocket* socket); nsresult RecvData(nsIUDPSocket* socket); void DoSetEchConfig(const nsACString& aEchConfig); nsresult SendPriorityUpdateFrame(uint64_t aStreamId, uint8_t aPriorityUrgency, bool aPriorityIncremental); void ConnectSlowConsumer(Http3StreamBase* stream); nsresult TryActivatingWebTransportStream(uint64_t* aStreamId, Http3StreamBase* aStream); void CloseWebTransportStream(Http3WebTransportStream* aStream, nsresult aResult); void StreamHasDataToWrite(Http3StreamBase* aStream); void ResetWebTransportStream(Http3WebTransportStream* aStream, uint64_t aErrorCode); void StreamStopSending(Http3WebTransportStream* aStream, uint8_t aErrorCode); void SendDatagram(Http3WebTransportSession* aSession, nsTArray& aData, uint64_t aTrackingId); uint64_t MaxDatagramSize(uint64_t aSessionId); void SetSendOrder(Http3StreamBase* aStream, Maybe aSendOrder); void CloseWebTransportConn(); private: ~Http3Session(); void CloseInternal(bool aCallNeqoClose); void Shutdown(); bool RealJoinConnection(const nsACString& hostname, int32_t port, bool justKidding); nsresult ProcessOutput(nsIUDPSocket* socket); void ProcessInput(nsIUDPSocket* socket); nsresult ProcessEvents(); nsresult ProcessTransactionRead(uint64_t stream_id); nsresult ProcessTransactionRead(Http3StreamBase* stream); nsresult ProcessSlowConsumers(); void SetupTimer(uint64_t aTimeout); enum ResetType { RESET, STOP_SENDING, }; void ResetOrStopSendingRecvd(uint64_t aStreamId, uint64_t aError, ResetType aType); void QueueStream(Http3StreamBase* stream); void RemoveStreamFromQueues(Http3StreamBase*); void ProcessPending(); void CallCertVerification(Maybe aEchPublicName); void SetSecInfo(); void EchOutcomeTelemetry(); void StreamReadyToWrite(Http3StreamBase* aStream); void MaybeResumeSend(); void CloseConnectionTelemetry(CloseError& aError, bool aClosing); void Finish0Rtt(bool aRestart); enum ZeroRttOutcome { NOT_USED, USED_SUCCEEDED, USED_REJECTED, USED_CONN_ERROR, USED_CONN_CLOSED_BY_NECKO }; void ZeroRttTelemetry(ZeroRttOutcome aOutcome); RefPtr mHttp3Connection; RefPtr mConnection; // We need an extra map to store the mapping of WebTransportSession and // WebTransportStreams to handle the case that a stream is already removed // from mStreamIdHash and we still need the WebTransportSession. nsTHashMap mWebTransportStreamToSessionMap; nsRefPtrHashtable mStreamIdHash; nsRefPtrHashtable, Http3StreamBase> mStreamTransactionHash; nsRefPtrDeque mReadyForWrite; nsTArray> mSlowConsumersReadyForRead; nsRefPtrDeque mQueuedStreams; enum State { INITIALIZING, ZERORTT, CONNECTED, CLOSING, CLOSED } mState{INITIALIZING}; bool mAuthenticationStarted{false}; bool mCleanShutdown{false}; bool mGoawayReceived{false}; bool mShouldClose{false}; bool mIsClosedByNeqo{false}; bool mHttp3ConnectionReported = false; // mError is neqo error (a protocol error) and that may mean that we will // send some packets after that. nsresult mError{NS_OK}; // This is a socket error, there is no poioint in sending anything on that // socket. nsresult mSocketError{NS_OK}; bool mBeforeConnectedError{false}; uint64_t mCurrentBrowserId; // True if the mTimer is inited and waiting for firing. bool mTimerActive{false}; RefPtr mUdpConn; // The underlying socket transport object is needed to propogate some events RefPtr mSocketTransport; nsCOMPtr mTimer; nsTHashMap mJoinConnectionCache; RefPtr mSocketControl; uint64_t mTransactionCount = 0; // The stream(s) that we are getting 0RTT data from. nsTArray> m0RTTStreams; // The stream(s) that are not able to send 0RTT data. We need to // remember them put them into mReadyForWrite queue when 0RTT finishes. nsTArray> mCannotDo0RTTStreams; // The following variables are needed for telemetry. TimeStamp mConnectionIdleStart; TimeStamp mConnectionIdleEnd; Maybe mFirstStreamIdReuseIdleConnection; TimeStamp mTimerShouldTrigger; TimeStamp mZeroRttStarted; uint64_t mBlockedByStreamLimitCount = 0; uint64_t mTransactionsBlockedByStreamLimitCount = 0; uint64_t mTransactionsSenderBlockedByFlowControlCount = 0; // NS_NET_STATUS_CONNECTED_TO event will be created by the Http3Session. // We want to propagate it to the first transaction. RefPtr mFirstHttpTransaction; RefPtr mConnInfo; bool mThroughCaptivePortal = false; int64_t mTotalBytesRead = 0; // total data read int64_t mTotalBytesWritten = 0; // total data read PRIntervalTime mLastWriteTime = 0; // Records whether we sent an ECH Extension and whether it was a GREASE Xtn EchExtensionStatus mEchExtensionStatus = EchExtensionStatus::kNotPresent; // Records whether the handshake finished successfully and we established a // a connection. bool mHandshakeSucceeded = false; nsCOMPtr mNetAddr; enum WebTransportNegotiation { DISABLED, NEGOTIATING, FAILED, SUCCEEDED }; WebTransportNegotiation mWebTransportNegotiationStatus{ WebTransportNegotiation::DISABLED}; nsTArray> mWaitingForWebTransportNegotiation; // 1795854 implement the case when WebTransport is not supported. // Also, implement the case when the HTTP/3 session fails before settings // are exchanged. void WebTransportNegotiationDone(); nsTArray> mWebTransportSessions; nsTArray> mWebTransportStreams; bool mHasWebTransportSession = false; // When true, we don't add this connection info into the Http/3 excluded list. bool mDontExclude = false; // The lifetime of the UDP socket is managed by the HttpConnectionUDP. This // is only used in Http3Session::ProcessOutput. Using raw pointer here to // improve performance. nsIUDPSocket* mSocket; }; NS_DEFINE_STATIC_IID_ACCESSOR(Http3Session, NS_HTTP3SESSION_IID); } // namespace mozilla::net #endif // Http3Session_H__