/src/node/src/quic/session.h
Line | Count | Source |
1 | | #pragma once |
2 | | |
3 | | #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |
4 | | |
5 | | #include <async_wrap.h> |
6 | | #include <base_object.h> |
7 | | #include <env.h> |
8 | | #include <memory_tracker.h> |
9 | | #include <ngtcp2/ngtcp2.h> |
10 | | #include <node_http_common.h> |
11 | | #include <node_sockaddr.h> |
12 | | #include <timer_wrap.h> |
13 | | #include <util.h> |
14 | | #include <optional> |
15 | | #include "bindingdata.h" |
16 | | #include "cid.h" |
17 | | #include "data.h" |
18 | | #include "defs.h" |
19 | | #include "logstream.h" |
20 | | #include "packet.h" |
21 | | #include "preferredaddress.h" |
22 | | #include "sessionticket.h" |
23 | | #include "streams.h" |
24 | | #include "tlscontext.h" |
25 | | #include "transportparams.h" |
26 | | |
27 | | namespace node::quic { |
28 | | |
29 | | class Endpoint; |
30 | | |
31 | | // A Session represents one half of a persistent connection between two QUIC |
32 | | // peers. Every Session is established first by performing a TLS handshake in |
33 | | // which the client sends an initial packet to the server containing a TLS |
34 | | // client hello. Once the TLS handshake has been completed, the Session can be |
35 | | // used to open one or more Streams for the actual data flow back and forth. |
36 | | // |
37 | | // While client and server Sessions are created in slightly different ways, |
38 | | // their lifecycles are generally identical: |
39 | | // |
40 | | // A Session is either acting as a Client or as a Server. |
41 | | // |
42 | | // Client Sessions are always created using Endpoint::Connect() |
43 | | // |
44 | | // Server Sessions are always created by an Endpoint receiving a valid initial |
45 | | // request received from a remote client. |
46 | | // |
47 | | // As soon as Sessions of either type are created, they will immediately start |
48 | | // working through the TLS handshake to establish the crypographic keys used to |
49 | | // secure the communication. Once those keys are established, the Session can be |
50 | | // used to open Streams. Based on how the Session is configured, any number of |
51 | | // Streams can exist concurrently on a single Session. |
52 | | // |
53 | | // The Session wraps an ngtcp2_conn that is initialized when the session object |
54 | | // is created. This ngtcp2_conn is destroyed when the session object is freed. |
55 | | // However, the session can be in a closed/destroyed state and still have a |
56 | | // valid ngtcp2_conn pointer. This is important because the ngtcp2 still might |
57 | | // be processing data within the scope of an ngtcp2_conn after the session |
58 | | // object itself is closed/destroyed by user code. |
59 | | class Session final : public AsyncWrap, private SessionTicket::AppData::Source { |
60 | | public: |
61 | | // For simplicity, we use the same Application::Options struct for all |
62 | | // Application types. This may change in the future. Not all of the options |
63 | | // are going to be relevant for all Application types. |
64 | | struct Application_Options final : public MemoryRetainer { |
65 | | // The maximum number of header pairs permitted for a Stream. |
66 | | // Only relevant if the selected application supports headers. |
67 | | uint64_t max_header_pairs = DEFAULT_MAX_HEADER_LIST_PAIRS; |
68 | | |
69 | | // The maximum total number of header bytes (including header |
70 | | // name and value) permitted for a Stream. |
71 | | // Only relevant if the selected application supports headers. |
72 | | uint64_t max_header_length = DEFAULT_MAX_HEADER_LENGTH; |
73 | | |
74 | | // HTTP/3 specific options. |
75 | | uint64_t max_field_section_size = 0; |
76 | | uint64_t qpack_max_dtable_capacity = 0; |
77 | | uint64_t qpack_encoder_max_dtable_capacity = 0; |
78 | | uint64_t qpack_blocked_streams = 0; |
79 | | |
80 | | bool enable_connect_protocol = true; |
81 | | bool enable_datagrams = true; |
82 | | |
83 | | operator const nghttp3_settings() const; |
84 | | |
85 | | SET_NO_MEMORY_INFO() |
86 | | SET_MEMORY_INFO_NAME(Application::Options) |
87 | | SET_SELF_SIZE(Options) |
88 | | |
89 | | static v8::Maybe<Application_Options> From(Environment* env, |
90 | | v8::Local<v8::Value> value); |
91 | | |
92 | | std::string ToString() const; |
93 | | |
94 | | static const Application_Options kDefault; |
95 | | }; |
96 | | |
97 | | // An Application implements the ALPN-protocol specific semantics on behalf |
98 | | // of a QUIC Session. |
99 | | class Application; |
100 | | |
101 | | // The ApplicationProvider optionally supplies the underlying application |
102 | | // protocol handler used by a session. The ApplicationProvider is supplied |
103 | | // in the *internal* options (that is, it is not exposed as a public, user |
104 | | // facing API. If the ApplicationProvider is not specified, then the |
105 | | // DefaultApplication is used (see application.cc). |
106 | | class ApplicationProvider : public BaseObject { |
107 | | public: |
108 | | using BaseObject::BaseObject; |
109 | | virtual std::unique_ptr<Application> Create(Session* session) = 0; |
110 | | }; |
111 | | |
112 | | // The options used to configure a session. Most of these deal directly with |
113 | | // the transport parameters that are exchanged with the remote peer during |
114 | | // handshake. |
115 | | struct Options final : public MemoryRetainer { |
116 | | // The QUIC protocol version requested for the session. |
117 | | uint32_t version = NGTCP2_PROTO_VER_MAX; |
118 | | |
119 | | // Te minimum QUIC protocol version supported by this session. |
120 | | uint32_t min_version = NGTCP2_PROTO_VER_MIN; |
121 | | |
122 | | // By default a client session will ignore the preferred address |
123 | | // advertised by the the server. This option is only relevant for |
124 | | // client sessions. |
125 | | PreferredAddress::Policy preferred_address_strategy = |
126 | | PreferredAddress::Policy::IGNORE_PREFERRED; |
127 | | |
128 | | TransportParams::Options transport_params = |
129 | | TransportParams::Options::kDefault; |
130 | | TLSContext::Options tls_options = TLSContext::Options::kDefault; |
131 | | |
132 | | // A reference to the CID::Factory used to generate CID instances |
133 | | // for this session. |
134 | | const CID::Factory* cid_factory = &CID::Factory::random(); |
135 | | // If the CID::Factory is a base object, we keep a reference to it |
136 | | // so that it cannot be garbage collected. |
137 | | BaseObjectPtr<BaseObject> cid_factory_ref; |
138 | | |
139 | | // If the application provider is specified, it will be used to create |
140 | | // the underlying Application instance for the session. |
141 | | BaseObjectPtr<ApplicationProvider> application_provider; |
142 | | |
143 | | // When true, QLog output will be enabled for the session. |
144 | | bool qlog = false; |
145 | | |
146 | | // The amount of time (in milliseconds) that the endpoint will wait for the |
147 | | // completion of the tls handshake. |
148 | | uint64_t handshake_timeout = UINT64_MAX; |
149 | | |
150 | | // Maximum initial flow control window size for a stream. |
151 | | uint64_t max_stream_window = 0; |
152 | | |
153 | | // Maximum initial flow control window size for the connection. |
154 | | uint64_t max_window = 0; |
155 | | |
156 | | // The max_payload_size is the maximum size of a serialized QUIC packet. It |
157 | | // should always be set small enough to fit within a single MTU without |
158 | | // fragmentation. The default is set by the QUIC specification at 1200. This |
159 | | // value should not be changed unless you know for sure that the entire path |
160 | | // supports a given MTU without fragmenting at any point in the path. |
161 | | uint64_t max_payload_size = kDefaultMaxPacketLength; |
162 | | |
163 | | // The unacknowledged_packet_threshold is the maximum number of |
164 | | // unacknowledged packets that an ngtcp2 session will accumulate before |
165 | | // sending an acknowledgement. Setting this to 0 uses the ngtcp2 defaults, |
166 | | // which is what most will want. The value can be changed to fine tune some |
167 | | // of the performance characteristics of the session. This should only be |
168 | | // changed if you have a really good reason for doing so. |
169 | | uint64_t unacknowledged_packet_threshold = 0; |
170 | | |
171 | | // There are several common congestion control algorithms that ngtcp2 uses |
172 | | // to determine how it manages the flow control window: RENO, CUBIC, and |
173 | | // BBR. The details of how each works is not relevant here. The choice of |
174 | | // which to use by default is arbitrary and we can choose whichever we'd |
175 | | // like. Additional performance profiling will be needed to determine which |
176 | | // is the better of the two for our needs. |
177 | | ngtcp2_cc_algo cc_algorithm = CC_ALGO_CUBIC; |
178 | | |
179 | | void MemoryInfo(MemoryTracker* tracker) const override; |
180 | | SET_MEMORY_INFO_NAME(Session::Options) |
181 | | SET_SELF_SIZE(Options) |
182 | | |
183 | | static v8::Maybe<Options> From(Environment* env, |
184 | | v8::Local<v8::Value> value); |
185 | | |
186 | | std::string ToString() const; |
187 | | }; |
188 | | |
189 | | // The additional configuration settings used to create a specific session. |
190 | | // while the Options above can be used to configure multiple sessions, a |
191 | | // single Config is used to create a single session, which is why they are |
192 | | // kept separate. |
193 | | struct Config final : MemoryRetainer { |
194 | | // Is the Session acting as a client or a server? |
195 | | Side side; |
196 | | |
197 | | // The options to use for this session. |
198 | | Options options; |
199 | | |
200 | | // The actual QUIC version identified for this session. |
201 | | uint32_t version; |
202 | | |
203 | | SocketAddress local_address; |
204 | | SocketAddress remote_address; |
205 | | |
206 | | // The destination CID, identifying the remote peer. This value is always |
207 | | // provided by the remote peer. |
208 | | CID dcid = CID::kInvalid; |
209 | | |
210 | | // The source CID, identifying this session. This value is always created |
211 | | // locally. |
212 | | CID scid = CID::kInvalid; |
213 | | |
214 | | // Used only by client sessions to identify the original DCID |
215 | | // used to initiate the connection. |
216 | | CID ocid = CID::kInvalid; |
217 | | CID retry_scid = CID::kInvalid; |
218 | | CID preferred_address_cid = CID::kInvalid; |
219 | | |
220 | | ngtcp2_settings settings = {}; |
221 | 0 | operator ngtcp2_settings*() { return &settings; } |
222 | 0 | operator const ngtcp2_settings*() const { return &settings; } |
223 | | |
224 | | Config(Environment* env, |
225 | | Side side, |
226 | | const Options& options, |
227 | | uint32_t version, |
228 | | const SocketAddress& local_address, |
229 | | const SocketAddress& remote_address, |
230 | | const CID& dcid, |
231 | | const CID& scid, |
232 | | const CID& ocid = CID::kInvalid); |
233 | | |
234 | | Config(Environment* env, |
235 | | const Options& options, |
236 | | const SocketAddress& local_address, |
237 | | const SocketAddress& remote_address, |
238 | | const CID& ocid = CID::kInvalid); |
239 | | |
240 | | void set_token(const uint8_t* token, |
241 | | size_t len, |
242 | | ngtcp2_token_type type = NGTCP2_TOKEN_TYPE_UNKNOWN); |
243 | | void set_token(const RetryToken& token); |
244 | | void set_token(const RegularToken& token); |
245 | | |
246 | | void MemoryInfo(MemoryTracker* tracker) const override; |
247 | | SET_MEMORY_INFO_NAME(Session::Config) |
248 | | SET_SELF_SIZE(Config) |
249 | | |
250 | | std::string ToString() const; |
251 | | }; |
252 | | |
253 | | JS_CONSTRUCTOR(Session); |
254 | | JS_BINDING_INIT_BOILERPLATE(); |
255 | | |
256 | | static BaseObjectPtr<Session> Create( |
257 | | Endpoint* endpoint, |
258 | | const Config& config, |
259 | | TLSContext* tls_context, |
260 | | const std::optional<SessionTicket>& ticket); |
261 | | |
262 | | // Really should be private but MakeDetachedBaseObject needs visibility. |
263 | | Session(Endpoint* endpoint, |
264 | | v8::Local<v8::Object> object, |
265 | | const Config& config, |
266 | | TLSContext* tls_context, |
267 | | const std::optional<SessionTicket>& ticket); |
268 | | DISALLOW_COPY_AND_MOVE(Session) |
269 | | ~Session() override; |
270 | | |
271 | | bool is_destroyed() const; |
272 | | bool is_server() const; |
273 | | |
274 | | uint32_t version() const; |
275 | | Endpoint& endpoint() const; |
276 | | TLSSession& tls_session() const; |
277 | | Application& application() const; |
278 | | const Config& config() const; |
279 | | const Options& options() const; |
280 | | const SocketAddress& remote_address() const; |
281 | | const SocketAddress& local_address() const; |
282 | | |
283 | | std::string diagnostic_name() const override; |
284 | | |
285 | | void MemoryInfo(MemoryTracker* tracker) const override; |
286 | | SET_MEMORY_INFO_NAME(Session) |
287 | | SET_SELF_SIZE(Session) |
288 | | |
289 | | operator ngtcp2_conn*() const; |
290 | | |
291 | | // Ensures that the session/application sends pending data when the scope |
292 | | // exits. Scopes can be nested. When nested, pending data will be sent |
293 | | // only when the outermost scope is exited. |
294 | | struct SendPendingDataScope final { |
295 | | Session* session; |
296 | | explicit SendPendingDataScope(Session* session); |
297 | | explicit SendPendingDataScope(const BaseObjectPtr<Session>& session); |
298 | | ~SendPendingDataScope(); |
299 | | DISALLOW_COPY_AND_MOVE(SendPendingDataScope) |
300 | | }; |
301 | | |
302 | | struct State; |
303 | | struct Stats; |
304 | | |
305 | | void HandleQlog(uint32_t flags, const void* data, size_t len); |
306 | | |
307 | | private: |
308 | | struct Impl; |
309 | | |
310 | | using StreamsMap = std::unordered_map<stream_id, BaseObjectPtr<Stream>>; |
311 | | using QuicConnectionPointer = DeleteFnPtr<ngtcp2_conn, ngtcp2_conn_del>; |
312 | | |
313 | | struct PathValidationFlags final { |
314 | | bool preferredAddress = false; |
315 | | }; |
316 | | |
317 | | struct DatagramReceivedFlags final { |
318 | | bool early = false; |
319 | | }; |
320 | | |
321 | | bool Receive(Store&& store, |
322 | | const SocketAddress& local_address, |
323 | | const SocketAddress& remote_address); |
324 | | |
325 | | void Send(const BaseObjectPtr<Packet>& packet); |
326 | | void Send(const BaseObjectPtr<Packet>& packet, const PathStorage& path); |
327 | | datagram_id SendDatagram(Store&& data); |
328 | | |
329 | | // A non-const variation to allow certain modifications. |
330 | | Config& config(); |
331 | | |
332 | | enum class CreateStreamOption : uint8_t { |
333 | | NOTIFY, |
334 | | DO_NOT_NOTIFY, |
335 | | }; |
336 | | BaseObjectPtr<Stream> FindStream(stream_id id) const; |
337 | | BaseObjectPtr<Stream> CreateStream( |
338 | | stream_id id, |
339 | | CreateStreamOption option = CreateStreamOption::NOTIFY, |
340 | | std::shared_ptr<DataQueue> data_source = nullptr); |
341 | | void AddStream(BaseObjectPtr<Stream> stream, |
342 | | CreateStreamOption option = CreateStreamOption::NOTIFY); |
343 | | void RemoveStream(stream_id id); |
344 | | void ResumeStream(stream_id id); |
345 | | void StreamDataBlocked(stream_id id); |
346 | | void ShutdownStream(stream_id id, QuicError error = QuicError()); |
347 | | void ShutdownStreamWrite(stream_id id, QuicError code = QuicError()); |
348 | | |
349 | | // Use the configured CID::Factory to generate a new CID. |
350 | | CID new_cid(size_t len = CID::kMaxLength) const; |
351 | | |
352 | | const TransportParams local_transport_params() const; |
353 | | const TransportParams remote_transport_params() const; |
354 | | |
355 | | bool is_destroyed_or_closing() const; |
356 | | size_t max_packet_size() const; |
357 | | void set_priority_supported(bool on = true); |
358 | | |
359 | | // Open a new locally-initialized stream with the specified directionality. |
360 | | // If the session is not yet in a state where the stream can be openen -- |
361 | | // such as when the handshake is not yet sufficiently far along and ORTT |
362 | | // session resumption is not being used -- then the stream will be created |
363 | | // in a pending state where actually opening the stream will be deferred. |
364 | | v8::MaybeLocal<v8::Object> OpenStream( |
365 | | Direction direction, std::shared_ptr<DataQueue> data_source = nullptr); |
366 | | |
367 | | void ExtendStreamOffset(stream_id id, size_t amount); |
368 | | void ExtendOffset(size_t amount); |
369 | | void SetLastError(QuicError&& error); |
370 | | uint64_t max_data_left() const; |
371 | | |
372 | | PendingStream::PendingStreamQueue& pending_bidi_stream_queue() const; |
373 | | PendingStream::PendingStreamQueue& pending_uni_stream_queue() const; |
374 | | |
375 | | // Implementation of SessionTicket::AppData::Source |
376 | | void CollectSessionTicketAppData( |
377 | | SessionTicket::AppData* app_data) const override; |
378 | | SessionTicket::AppData::Status ExtractSessionTicketAppData( |
379 | | const SessionTicket::AppData& app_data, |
380 | | SessionTicket::AppData::Source::Flag flag) override; |
381 | | |
382 | | // Returns true if the Session has entered the closing period after sending a |
383 | | // CONNECTION_CLOSE. While true, the Session is only permitted to transmit |
384 | | // CONNECTION_CLOSE frames until either the idle timeout period elapses or |
385 | | // until the Session is explicitly destroyed. |
386 | | bool is_in_closing_period() const; |
387 | | |
388 | | // Returns true if the Session has received a CONNECTION_CLOSE frame from the |
389 | | // peer. Once in the draining period, the Session is not permitted to send any |
390 | | // frames to the peer. The Session will be silently closed after either the |
391 | | // idle timeout period elapses or until the Session is explicitly destroyed. |
392 | | bool is_in_draining_period() const; |
393 | | |
394 | | // Returns false if the Session is currently in a state where it is unable to |
395 | | // transmit any packets. |
396 | | bool can_send_packets() const; |
397 | | |
398 | | // Returns false if the Session is currently in a state where it cannot create |
399 | | // new streams. Specifically, a stream is not in a state to create streams if |
400 | | // it has been destroyed or is closing. |
401 | | bool can_create_streams() const; |
402 | | |
403 | | // Returns false if the Session is currently in a state where it cannot open |
404 | | // a new locally-initiated stream. When using 0RTT session resumption, this |
405 | | // will become true immediately after the session ticket and transport params |
406 | | // have been configured. Otherwise, it becomes true after the remote transport |
407 | | // params and tx keys have been installed. |
408 | | bool can_open_streams() const; |
409 | | |
410 | | uint64_t max_local_streams_uni() const; |
411 | | uint64_t max_local_streams_bidi() const; |
412 | | |
413 | | bool wants_session_ticket() const; |
414 | | void SetStreamOpenAllowed(); |
415 | | |
416 | | // It's a terrible name but "wrapped" here means that the Session has been |
417 | | // passed out to JavaScript and should be "wrapped" by whatever handler is |
418 | | // defined there to manage it. |
419 | | void set_wrapped(); |
420 | | |
421 | | enum class CloseMethod : uint8_t { |
422 | | // Immediate close with a roundtrip through JavaScript, causing all |
423 | | // currently opened streams to be closed. An attempt will be made to |
424 | | // send a CONNECTION_CLOSE frame to the peer. If closing while within |
425 | | // the ngtcp2 callback scope, sending the CONNECTION_CLOSE will be |
426 | | // deferred until the scope exits. |
427 | | DEFAULT, |
428 | | // Same as DEFAULT except that no attempt to notify the peer will be |
429 | | // made. |
430 | | SILENT, |
431 | | // Closing gracefully disables the ability to open or accept new streams |
432 | | // for this Session. Existing streams are allowed to close naturally on |
433 | | // their own. |
434 | | // Once called, the Session will be immediately closed once there are no |
435 | | // remaining streams. No notification is given to the connected peer that |
436 | | // we are in a graceful closing state. A CONNECTION_CLOSE will be sent |
437 | | // only once FinishClose() is called. |
438 | | GRACEFUL |
439 | | }; |
440 | | // Initiate closing of the session. |
441 | | void Close(CloseMethod method = CloseMethod::DEFAULT); |
442 | | |
443 | | void FinishClose(); |
444 | | void Destroy(); |
445 | | |
446 | | // Close the session and send a connection close packet to the peer. |
447 | | // If creating the packet fails the session will be silently closed. |
448 | | // The connection close packet will use the value of last_error_ as |
449 | | // the error code transmitted to the peer. |
450 | | void SendConnectionClose(); |
451 | | void OnTimeout(); |
452 | | |
453 | | void UpdateTimer(); |
454 | | // Has to be called after certain operations that generate packets. |
455 | | void UpdatePacketTxTime(); |
456 | | void UpdateDataStats(); |
457 | | void UpdatePath(const PathStorage& path); |
458 | | |
459 | | void ProcessPendingBidiStreams(); |
460 | | void ProcessPendingUniStreams(); |
461 | | |
462 | | // JavaScript callouts |
463 | | |
464 | | void EmitClose(const QuicError& error = QuicError()); |
465 | | void EmitDatagram(Store&& datagram, DatagramReceivedFlags flag); |
466 | | void EmitDatagramStatus(datagram_id id, DatagramStatus status); |
467 | | void EmitHandshakeComplete(); |
468 | | void EmitKeylog(const char* line); |
469 | | |
470 | | struct ValidatedPath { |
471 | | std::shared_ptr<SocketAddress> local; |
472 | | std::shared_ptr<SocketAddress> remote; |
473 | | }; |
474 | | |
475 | | void EmitPathValidation(PathValidationResult result, |
476 | | PathValidationFlags flags, |
477 | | const ValidatedPath& newPath, |
478 | | const std::optional<ValidatedPath>& oldPath); |
479 | | void EmitSessionTicket(Store&& ticket); |
480 | | void EmitStream(const BaseObjectWeakPtr<Stream>& stream); |
481 | | void EmitVersionNegotiation(const ngtcp2_pkt_hd& hd, |
482 | | const uint32_t* sv, |
483 | | size_t nsv); |
484 | | void DatagramStatus(datagram_id datagramId, DatagramStatus status); |
485 | | void DatagramReceived(const uint8_t* data, |
486 | | size_t datalen, |
487 | | DatagramReceivedFlags flag); |
488 | | void GenerateNewConnectionId(ngtcp2_cid* cid, size_t len, uint8_t* token); |
489 | | bool HandshakeCompleted(); |
490 | | void HandshakeConfirmed(); |
491 | | void SelectPreferredAddress(PreferredAddress* preferredAddress); |
492 | | |
493 | | static std::unique_ptr<Application> SelectApplication(Session* session, |
494 | | const Config& config); |
495 | | |
496 | | QuicConnectionPointer InitConnection(); |
497 | | |
498 | | Side side_; |
499 | | ngtcp2_mem allocator_; |
500 | | std::unique_ptr<Impl> impl_; |
501 | | QuicConnectionPointer connection_; |
502 | | std::unique_ptr<TLSSession> tls_session_; |
503 | | BaseObjectPtr<LogStream> qlog_stream_; |
504 | | BaseObjectPtr<LogStream> keylog_stream_; |
505 | | |
506 | | friend class Application; |
507 | | friend class DefaultApplication; |
508 | | friend class Http3ApplicationImpl; |
509 | | friend class Endpoint; |
510 | | friend class Stream; |
511 | | friend class PendingStream; |
512 | | friend class TLSContext; |
513 | | friend class TLSSession; |
514 | | friend class TransportParams; |
515 | | friend struct Impl; |
516 | | friend struct SendPendingDataScope; |
517 | | }; |
518 | | |
519 | | } // namespace node::quic |
520 | | |
521 | | #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |