1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
|
/* -*- 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/Array.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;
class Http3WebTransportSession;
class Http3WebTransportStream;
class nsHttpConnection;
// 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
};
// An abstract layer for testing.
class Http3SessionBase {
public:
NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
virtual nsresult TryActivating(const nsACString& aMethod,
const nsACString& aScheme,
const nsACString& aAuthorityHeader,
const nsACString& aPath,
const nsACString& aHeaders,
uint64_t* aStreamId,
Http3StreamBase* aStream) = 0;
virtual void CloseSendingSide(uint64_t aStreamId) = 0;
virtual void SendHTTPDatagram(uint64_t aStreamId, nsTArray<uint8_t>& aData,
uint64_t aTrackingId) = 0;
virtual nsresult SendPriorityUpdateFrame(uint64_t aStreamId,
uint8_t aPriorityUrgency,
bool aPriorityIncremental) = 0;
virtual void ConnectSlowConsumer(Http3StreamBase* stream) = 0;
virtual nsresult SendRequestBody(uint64_t aStreamId, const char* buf,
uint32_t count, uint32_t* countRead) = 0;
virtual nsresult ReadResponseData(uint64_t aStreamId, char* aBuf,
uint32_t aCount, uint32_t* aCountWritten,
bool* aFin) = 0;
virtual void FinishTunnelSetup(nsAHttpTransaction* aTransaction) = 0;
virtual void CloseStream(Http3StreamBase* aStream, nsresult aResult) {}
// For WebTransport
virtual void CloseWebTransportConn() = 0;
virtual void StreamHasDataToWrite(Http3StreamBase* aStream) = 0;
virtual nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError,
const nsACString& aMessage) = 0;
virtual void SendDatagram(Http3WebTransportSession* aSession,
nsTArray<uint8_t>& aData, uint64_t aTrackingId) = 0;
virtual uint64_t MaxDatagramSize(uint64_t aSessionId) = 0;
virtual nsresult TryActivatingWebTransportStream(
uint64_t* aStreamId, Http3StreamBase* aStream) = 0;
virtual void ResetWebTransportStream(Http3WebTransportStream* aStream,
uint64_t aErrorCode) = 0;
virtual void StreamStopSending(Http3WebTransportStream* aStream,
uint8_t aErrorCode) = 0;
virtual void SetSendOrder(Http3StreamBase* aStream,
Maybe<int64_t> aSendOrder) = 0;
};
class Http3Session final : public Http3SessionBase,
public nsAHttpTransaction,
public nsAHttpConnection {
public:
NS_INLINE_DECL_STATIC_IID(NS_HTTP3SESSION_IID)
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSAHTTPTRANSACTION
NS_DECL_NSAHTTPCONNECTION(mConnection)
class OnQuicTimeout final : public nsITimerCallback, public nsINamed {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSINAMED
explicit OnQuicTimeout(HttpConnectionUDP* aConnection);
private:
~OnQuicTimeout() = default;
RefPtr<HttpConnectionUDP> mConnection;
};
Http3Session();
nsresult Init(const nsHttpConnectionInfo* aConnInfo, nsINetAddr* selfAddr,
nsINetAddr* peerAddr, HttpConnectionUDP* udpConn,
uint32_t aProviderFlags, nsIInterfaceRequestor* callbacks,
nsIUDPSocket* socket, bool aIsTunnel = false);
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) override;
// The folowing functions are used by Http3Stream:
void CloseSendingSide(uint64_t aStreamId) override;
nsresult SendRequestBody(uint64_t aStreamId, const char* buf, uint32_t count,
uint32_t* countRead) override;
nsresult ReadResponseHeaders(uint64_t aStreamId,
nsTArray<uint8_t>& aResponseHeaders, bool* aFin);
nsresult ReadResponseData(uint64_t aStreamId, char* aBuf, uint32_t aCount,
uint32_t* aCountWritten, bool* aFin) override;
// The folowing functions are used by Http3WebTransportSession:
nsresult CloseWebTransport(uint64_t aSessionId, uint32_t aError,
const nsACString& aMessage) override;
nsresult CreateWebTransportStream(uint64_t aSessionId,
WebTransportStreamType aStreamType,
uint64_t* aStreamId);
void CloseStream(Http3StreamBase* aStream, nsresult aResult) override;
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, bool aServCertHashesSucceeded = false);
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) override;
void ConnectSlowConsumer(Http3StreamBase* stream) override;
nsresult TryActivatingWebTransportStream(uint64_t* aStreamId,
Http3StreamBase* aStream) override;
void CloseWebTransportStream(Http3WebTransportStream* aStream,
nsresult aResult);
void StreamHasDataToWrite(Http3StreamBase* aStream) override;
void ResetWebTransportStream(Http3WebTransportStream* aStream,
uint64_t aErrorCode) override;
void StreamStopSending(Http3WebTransportStream* aStream,
uint8_t aErrorCode) override;
void SendDatagram(Http3WebTransportSession* aSession,
nsTArray<uint8_t>& aData, uint64_t aTrackingId) override;
void SendHTTPDatagram(uint64_t aStreamId, nsTArray<uint8_t>& aData,
uint64_t aTrackingId) override;
uint64_t MaxDatagramSize(uint64_t aSessionId) override;
void SetSendOrder(Http3StreamBase* aStream,
Maybe<int64_t> aSendOrder) override;
void CloseWebTransportConn() override;
void FinishTunnelSetup(nsAHttpTransaction* aTransaction) override;
Http3Stats GetStats();
// For connect-udp
already_AddRefed<HttpConnectionUDP> CreateTunnelStream(
nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks);
// For HTTP CONNECT
already_AddRefed<nsHttpConnection> CreateTunnelStream(
nsAHttpTransaction* aHttpTransaction, nsIInterfaceRequestor* aCallbacks,
PRIntervalTime aRtt, bool aIsExtendedCONNECT);
void SetIsInTunnel() { mIsInTunnel = true; }
private:
~Http3Session();
void CloseInternal(bool aCallNeqoClose);
void Shutdown();
bool RealJoinConnection(const nsACString& hostname, int32_t port,
bool justKidding);
nsresult ProcessOutput(nsIUDPSocket* socket);
nsresult 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<nsCString> aEchPublicName);
void SetSecInfo();
#ifndef ANDROID
void EchOutcomeTelemetry();
#endif
void StreamReadyToWrite(Http3StreamBase* aStream);
void MaybeResumeSend();
void CloseConnectionTelemetry(CloseError& aError, bool aClosing);
void Finish0Rtt(bool aRestart);
#ifndef ANDROID
enum ZeroRttOutcome {
NOT_USED,
USED_SUCCEEDED,
USED_REJECTED,
USED_CONN_ERROR,
USED_CONN_CLOSED_BY_NECKO
};
void ZeroRttTelemetry(ZeroRttOutcome aOutcome);
#endif
RefPtr<NeqoHttp3Conn> mHttp3Connection;
RefPtr<nsAHttpConnection> 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<nsUint64HashKey, uint64_t> mWebTransportStreamToSessionMap;
nsRefPtrHashtable<nsUint64HashKey, Http3StreamBase> mStreamIdHash;
nsRefPtrHashtable<nsPtrHashKey<nsAHttpTransaction>, Http3StreamBase>
mStreamTransactionHash;
nsRefPtrDeque<Http3StreamBase> mReadyForWrite;
nsTArray<RefPtr<Http3StreamBase>> mSlowConsumersReadyForRead;
nsRefPtrDeque<Http3StreamBase> 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 this http3 session uses NSPR for UDP IO.
bool mUseNSPRForIO{true};
RefPtr<HttpConnectionUDP> mUdpConn;
nsCOMPtr<nsITimer> mTimer;
RefPtr<OnQuicTimeout> mTimerCallback;
nsTHashMap<nsCStringHashKey, bool> mJoinConnectionCache;
RefPtr<QuicSocketControl> mSocketControl;
uint64_t mTransactionCount = 0;
// The stream(s) that we are getting 0RTT data from.
nsTArray<WeakPtr<Http3StreamBase>> 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<WeakPtr<Http3StreamBase>> mCannotDo0RTTStreams;
// The following variables are needed for telemetry.
TimeStamp mConnectionIdleStart;
TimeStamp mConnectionIdleEnd;
Maybe<uint64_t> mFirstStreamIdReuseIdleConnection;
TimeStamp mTimerShouldTrigger;
TimeStamp mZeroRttStarted;
TimeStamp mLastTRRResponseTime; // Time of the last successful TRR response
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<nsHttpTransaction> mFirstHttpTransaction;
RefPtr<nsHttpConnectionInfo> mConnInfo;
int64_t mTotalBytesRead = 0; // total data read
int64_t mTotalBytesWritten = 0; // total data read
PRIntervalTime mLastWriteTime = 0;
nsCString mServer;
// 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<nsINetAddr> mNetAddr;
enum class ExtendedConnectKind : uint8_t {
WebTransport = 0,
ConnectUDP,
// add more extended CONNECT protocols here
};
enum ExtendedConnectNegotiation { DISABLED, NEGOTIATING, FAILED, SUCCEEDED };
struct ExtendedConnectState {
ExtendedConnectNegotiation mStatus = DISABLED;
nsTArray<WeakPtr<Http3StreamBase>> mWaiters;
};
Array<ExtendedConnectState, 2> mExtConnect{ExtendedConnectState{},
ExtendedConnectState{}};
// convenience accessors
ExtendedConnectState& ExtState(ExtendedConnectKind aKind) {
return mExtConnect[static_cast<size_t>(aKind)];
}
bool DeferIfNegotiating(ExtendedConnectKind aKind, Http3StreamBase* aStream);
// 1795854 implement the case when WebTransport is not supported.
// Also, implement the case when the HTTP/3 session fails before settings
// are exchanged.
void FinishNegotiation(ExtendedConnectKind aKind, bool aSuccess);
inline bool HasNoActiveStreams() const {
return mStreamTransactionHash.Count() == 0 &&
mWebTransportSessions.IsEmpty() && mWebTransportStreams.IsEmpty() &&
mTunnelStreams.IsEmpty();
}
nsTArray<RefPtr<Http3StreamBase>> mWebTransportSessions;
nsTArray<RefPtr<Http3StreamBase>> mWebTransportStreams;
nsTArray<RefPtr<Http3StreamBase>> mTunnelStreams;
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;
uint32_t mTrrStreams = 0;
bool mIsInTunnel = false;
};
} // namespace mozilla::net
#endif // Http3Session_H__
|