diff --git a/doc/api/quic.md b/doc/api/quic.md index c408e0145e..d8a65e7910 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -56,7 +56,191 @@ socket.on('listening', () => { }); ``` -## quic.createSocket([options]) +## QUIC Basics + +QUIC is a UDP-based network transport protocol that includes built-in security +via TLS 1.3, flow control, error correction, connection migration, +multiplexing, and more. + +Within the Node.js implementation of the QUIC protocol, there are three main +components: the `QuicSocket`, the `QuicSession` and the `QuicStream`. + +### QuicSocket + +A `QuicSocket` encapsulates a binding to a local UDP port. It is used to send +data to, and receive data from, remote endpoints. Once created, a `QuicSocket` +is associated with a local network address and IP port and can act as both a +QUIC client and server simultaneously. User code at the JavaScript level +interacts with the `QuicSocket` object to: + +* Query or modified the properties of the local UDP binding; +* Create client `QuicSession` instances; +* Wait for server `QuicSession` instances; or +* Query activity statistics + +Unlike the `net.Socket` and `tls.TLSSocket`, a `QuicSocket` instance cannot be +directly used by user code at the JavaScript level to send or receive data. + +### Client and Server QuicSessions + +A `QuicSession` represents a logical connection between two QUIC endpoints (a +client and a server). In the JavaScript API, each is represented by the +`QuicClientSession` and `QuicServerSession` specializations. + +At any given time, a `QuicSession` exists is one of four possible states: + +* `Initial` - Entered as soon as the `QuicSession` is created. +* `Handshake` - Entered as soon as the TLS 1.3 handshake between the client and + server begins. The handshake is always initiated by the client. +* `Ready` - Entered as soon as the TLS 1.3 handshake completes. Once the + `QuicSession` enters the `Ready` state, it may be used to exchange + application data using `QuicStream` instances. +* `Closed` - Entere as soon as the `QuicSession` connection has been + terminated. + +New instances of `QuicClientSession` are created using the `connect()` +function on a `QuicSocket` as in the example below: + +```js +const { createSocket } = require('quic'); + +// Create a QuicSocket associated with localhost and port 1234 +const socket = createSocket({ port: 1234 }); + +const client = client.connect({ + address: 'example.com', + port: 4567, + alpn: 'foo' +}); +``` + +As soon as the `QuicClientSession` is created, the `address` provided in +the connect options will be resolved to an IP address (if necessary), and +the TLS 1.3 handshake will begin. The `QuicClientSession` cannot be used +to exchange application data until after the `'secure'` event has been +emitted by the `QuicClientSession` object, signaling the completion of +the TLS 1.3 handshake. + +```js +client.on('secure', () => { + // The QuicClientSession can now be used for application data +}); +``` + +New instance of `QuicServerSession` are created internally by the +`QuicSocket` if it has been configured to listen for new connections +using the `listen()` method. + +``` +const key = getTLSKeySomehow(); +const cert = getTLSCertSomehow(); + +socket.listen({ + key, + cert, + alpn: 'foo' +}); + +socket.on('session', (session) => { + session.on('secure', () => { + // The QuicServerSession can now be used for application data + }); +}); +``` + +As with client `QuicSession` instances, the `QuicServerSession` cannot be +used to exhange application data until the `'secure'` event has been emitted. + +### QuicSession and ALPN + +QUIC uses the TLS 1.3 ALPN ("Application-Layer Protocol Negotiation") extension +to identify the application level protocol that is using the QUIC connection. +Every `QuicSession` instance has an ALPN identifier that *must* be specified +in either the `connect()` or `listen()` options. ALPN identifiers that are +known to Node.js (such as the ALPN identifier for HTTP/3) may alter how the +`QuicSession` and `QuicStream` objects operate internally, but the QUIC +implementation for Node.js has been designed to allow any ALPN to be specified +and used. + +### QuicStream + +Once a `QuicSession` transitions to the `Ready` state, `QuicStream` instances +may be created and used to exchange application data. On a general level, all +`QuicStream` instances are simply Node.js Duplex Streams that allow +bidirectional data flow between the QUIC client and server. However, the +application protocol negotiated for the `QuicSession` may alter the semantics +and operation of a `QuicStream` associated with the session. Specifically, +some features of the `QuicStream` (e.g. headers) are enabled only if the +application protocol selected is known by Node.js to support those features. + +Once the `QuicSession` is ready, a `QuicStream` may be created by either the +client or server, and may be unidirectional or bidirectional. + +The `openStream()` method is used to create a new `QuicStream`: + +```js +// Create a new bidirectional stream +const stream1 = session.openStream(); + +// Create a new unidirectional stream +const stream2 = session.openStream({ halfOpen: true }); +``` + +As suggested by the names, a bidirectional stream can send data to, and receive +data from, the QUIC peer; while a unidirectional stream can only be used to +send data to the peer. + +The `'stream'` event is emitted by the `QuicSession` when a new `QuicStream` +has been initated by the connected peer: + +```js +session.on('stream', (stream) => { + if (stream.bidirectional) { + stream.write('Hello World'); + stream.end(); + } + stream.on('data', console.log); + stream.on('end', () => {}); +}); +``` + +#### QuicStream Headers + +Some QUIC application protocols (like HTTP/3) make use of headers. + +There are three specific kinds of headers that the Node.js QUIC +implementation is capable of handling dependent entirely on known +application protocol support: + +* Informational Headers +* Initial Headers +* Trailing Headers + +These categories correlate exactly with the equivalent HTTP +concepts: + +* Informational Headers == Any response headers transmitted within + a block of headers using a `1xx` status code. +* Initial Headers == HTTP request or response headers +* Traiing Headers == A block of headers that follow the body of a + request or response. + +If headers are supported by the application protocol in use for +a given `QuicSession`, the `'initialHeaders'`, `'informationalHeaders'`, +and `'trailingHeaders'` events will be emitted by the `QuicStream` +object when headers are received; and the `submitInformationalHeaders()`, +`submitInitialHeaders()`, and `submitTrailingHeaders()` methods can be +used to send headers. + +## QUIC and HTTP/3 + +HTTP/3 is an application layer protocol that uses QUIC as the transport. + +TBD + +## QUIC JavaScript API + +### quic.createSocket([options]) @@ -69,7 +253,7 @@ added: REPLACEME using `quicsocket.connect()`. * `lookup` {Function} A custom DNS lookup function. Default `dns.lookup()`. * `maxConnectionsPerHost` {number} The maximum number of inbound connections - per remote host. Default: `100`. + allowed per remote host. Default: `100`. * `port` {number} The local port to bind to. * `qlog` {boolean} Whether to emit ['qlog'][] events for incoming sessions. (For outgoing client sessions, set `client.qlog`.) Default: `false`. @@ -88,9 +272,10 @@ added: REPLACEME `false`. * `ipv6Only` {boolean} -Creates a new `QuicSocket` instance. +The `quic.createSocket()` function is used to create new `QuicSocket` instances +associated with a local UDP address. -## Class: QuicSession exends EventEmitter +### Class: QuicSession exends EventEmitter @@ -101,14 +286,14 @@ properties that are shared by both `QuicClientSession` and `QuicServerSession`. Users will not create instances of `QuicSession` directly. -### Event: `'close'` +#### Event: `'close'` -Emiitted after the `QuicSession` has been destroyed. +Emiitted after the `QuicSession` has been destroyed and is no longer usable. -### Event: `'error'` +#### Event: `'error'`d @@ -116,7 +301,11 @@ added: REPLACEME Emitted immediately before the `'close'` event if the `QuicSession` was destroyed with an error. -### Event: `'keylog'` +The callback will be invoked with a single argument: + +* `error` {Object} An `Error` object. + +#### Event: `'keylog'` @@ -139,7 +328,7 @@ const log = fs.createWriteStream('/tmp/ssl-keys.log', { flags: 'a' }); session.on('keylog', (line) => log.write(line)); ``` -### Event: `'pathValidation'` +#### Event: `'pathValidation'` @@ -155,7 +344,7 @@ The callback will be invoked with three arguments: * `local` {Object} The local address component of the tested path. * `remote` {Object} The remote address component of the tested path. -### Event: `'qlog'` +#### Event: `'qlog'` @@ -167,7 +356,7 @@ Emitted if the `qlog: true` option was passed to `quicsocket.connect()` or The argument is a JSON fragment according to the [qlog standard][]. -### Event: `'secure'` +#### Event: `'secure'` @@ -185,14 +374,39 @@ The callback will be invoked with two arguments: These will also be available using the `quicsession.servername`, `quicsession.alpnProtocol`, and `quicsession.cipher` properties. -### Event: `'stream'` +#### Event: `'stream'` Emitted when a new `QuicStream` has been initiated by the connected peer. -### quicsession.alpnProtocol +#### quicsession.ackDelayRetransmitCount + + +* Type: {BigInt} + +A `BigInt` representing the number of retransmissions caused by delayed +acknowledgements. + +#### quicsession.address + + +* Type: {Object} + * `address` {string} The local IPv4 or IPv6 address to which the `QuicSession` + is bound. + * `family` {string} Either `'IPv4'` or `'IPv6'`. + * `port` {number} The local IP port to which the `QuicSocket` is bound. + +An object containing the local address information for the `QuicSocket` to which +the `QuicSession` is currently associated. + + +#### quicsession.alpnProtocol @@ -201,7 +415,61 @@ added: REPLACEME The ALPN protocol identifier negotiated for this session. -### quicsession.cipher +#### quicsession.authenticated + +* Type: {boolean} + +True if the certificate provided by the peer during the TLS 1.3 +handshake has been verified. + +#### quicsession.authenticationError + +* Type: {Object} An error object + +If `quicsession.authenticated` is false, returns an `Error` object +representing the reason the peer certificate verification failed. + +#### quicsession.bidiStreamCount + + +* Type: {BigInt} + +A `BigInt` representing the total number of bidirectional streams +created for this `QuicSession`. + +#### quicsession.bytesInFlight + + +* Type: {number} + +The total number of unacknowledged bytes this QUIC endpoint has transmitted +to the connected peer. + +#### quicsession.bytesReceived + + +* Type: {BigInt} + +A `BigInt` representing the total number of bytes received from the peer. + +#### quicsession.bytesSent + + +* Type: {BigInt} + +A `BigInt` representing the total number of bytes sent to the peer. + +#### quicsession.cipher @@ -212,7 +480,7 @@ added: REPLACEME Information about the cipher algorithm selected for the session. -### quicsession.close([callback]) +#### quicsession.close([callback]) @@ -224,7 +492,17 @@ will be permitted to close naturally. New `QuicStream` instances will not be permitted. Once all `QuicStream` instances have closed, the `QuicSession` instance will be destroyed. -### quicsession.closing +#### quicsession.closeCode + +* Type: {Object} + * `code` {number} The error code reported when the `QuicSession` closed. + * `family` {number} The type of error code reported (`0` indicates a QUIC + protocol level error, `1` indicates a TLS error, `2` represents an + application level error.) + +#### quicsession.closing @@ -233,7 +511,7 @@ added: REPLACEME Set to `true` if the `QuicSession` is in the process of a graceful shutdown. -### quicsession.destroy([error]) +#### quicsession.destroy([error]) @@ -246,7 +524,7 @@ before the `close` event. Any `QuicStream` instances that are still opened will be abruptly closed. -### quicsession.destroyed +#### quicsession.destroyed @@ -255,7 +533,16 @@ added: REPLACEME Set to `true` if the `QuicSession` has been destroyed. -### quicsession.getCertificate() +#### quicsession.duration + + +* Type: {BigInt} + +A `BigInt` representing the length of time the `QuicSession` was active. + +#### quicsession.getCertificate() @@ -268,7 +555,7 @@ some properties corresponding to the fields of the certificate. If there is no local certificate, or if the `QuicSession` has been destroyed, an empty object will be returned. -### quicsession.getPeerCertificate([detailed]) +#### quicsession.getPeerCertificate([detailed]) @@ -285,7 +572,21 @@ If the full certificate chain was requested (`details` equals `true`), each certificate will include an `issuerCertificate` property containing an object representing the issuer's certificate. -### quicsession.handshakeComplete +#### quicsession.handshakeAckHistogram + + +TBD + +#### quicsession.handshakeContinuationHistogram + + +TBD + +#### quicsession.handshakeComplete @@ -294,7 +595,65 @@ added: REPLACEME Set to `true` if the TLS handshake has completed. -### quicsession.maxStreams +#### quicsession.handshakeDuration + + +* Type: {BigInt} + +A `BigInt` representing the length of time taken to complete the TLS handshake. + +#### quicsession.keyUpdateCount + + +* Type: {BigInt} + +A `BigInt` representing the number of key update operations that have +occured. + +#### quicsession.latestRTT + + +* Type: {BigInt} + +The most recently recorded RTT for this `QuicSession`. + +#### quicsession.lossRetransmitCount + + +* Type: {BigInt} + +A `BigInt` representing the number of lost-packet retransmissions that have been +performed on this `QuicSession`. + +#### quicsession.maxDataLeft + + +* Type: {Number} + +The total number of bytes the `QuicSession` is *currently* allowed to +send to the connected peer. + +#### quicsession.maxInFlightBytes + + +* Type: {BigInt} + +A `BigInt` representing the maximum number of in-flight bytes recorded +for this `QuicSession`. + +#### quicsession.maxStreams @@ -308,7 +667,16 @@ that can currently be opened. The values are set initially by configuration parameters when the `QuicSession` is created, then updated over the lifespan of the `QuicSession` as the connected peer allows new streams to be created. -### quicsession.openStream([options]) +#### quicsession.minRTT + + +* Type: {BigInt} + +The minimum RTT recorded so far for this `QuicSession`. + +#### quicsession.openStream([options]) @@ -323,7 +691,7 @@ Returns a new `QuicStream`. An error will be thrown if the `QuicSession` has been destroyed or is in the process of a graceful shutdown. -### quicsession.ping() +#### quicsession.ping() @@ -336,7 +704,40 @@ that ignores any errors that may occur during the serialization and send operations. There is no return value and there is no way to monitor the status of the `ping()` operation. -### quicsession.servername +#### quicsession.peerInitiatedStreamCount + + +* Type: {BigInt} + +A `BigInt` representing the total number of `QuicStreams` initiated by the +connected peer. + +#### quicsession.remoteAddress + + +* Type: {Object} + * `address` {string} The local IPv4 or IPv6 address to which the `QuicSession` + is connected. + * `family` {string} Either `'IPv4'` or `'IPv6'`. + * `port` {number} The local IP port to which the `QuicSocket` is bound. + +An object containing the remote address information for the connected peer. + +#### quicsession.selfInitiatedStreamCount + + +* Type: {BigInt} + +A `BigInt` representing the total number of `QuicStream` instances initiated +by this `QuicSession`. + +#### quicsession.servername @@ -345,7 +746,16 @@ added: REPLACEME The SNI servername requested for this session by the client. -### quicsession.socket +#### quicsession.smoothedRTT + + +* Type: {BigInt} + +The modified RTT calculated for this `QuicSession`. + +#### quicsession.socket @@ -354,7 +764,26 @@ added: REPLACEME The `QuicSocket` the `QuicSession` is associated with. -### quicsession.updateKey() +#### quicsession.statelessReset + + +* Type: {Boolean} + +True if the `QuicSession` was closed due to QUIC stateless reset. + +#### quicsession.uniStreamCount + + +* Type: {BigInt} + +A `BigInt` representing the total number of unidirectional streams +created on this `QuicSession`. + +#### quicsession.updateKey() @@ -364,7 +793,7 @@ added: REPLACEME Initiates QuicSession key update. -## Class: QuicClientSession extends QuicSession +### Class: QuicClientSession extends QuicSession @@ -374,7 +803,7 @@ added: REPLACEME The `QuicClientSession` class implements the client side of a QUIC connection. Instances are created using the `quicsocket.connect()` method. -### Event: `'OCSPResponse'` +#### Event: `'OCSPResponse'` @@ -389,7 +818,7 @@ The callback is invoked with a single argument: Node.js does not perform any automatic validation or processing of the response. -### Event: `'sessionTicket'` +#### Event: `'sessionTicket'` @@ -406,7 +835,7 @@ three arguments: The `sessionTicket` and `remoteTransportParams` are useful when creating a new `QuicClientSession` to more quickly resume an existing session. -### quicclientsession.ephemeralKeyInfo +#### quicclientsession.ephemeralKeyInfo @@ -421,7 +850,7 @@ empty object when the key exchange is not ephemeral. The supported types are For example: `{ type: 'ECDH', name: 'prime256v1', size: 256 }`. -### quicclientsession.ready +#### quicclientsession.ready @@ -431,7 +860,7 @@ added: REPLACEME Set to `true` if the `QuicClientSession` is ready for use. False if the `QuicSocket` has not yet been bound. -### quicclientsession.setSocket(socket, callback]) +#### quicclientsession.setSocket(socket, callback]) @@ -446,7 +875,7 @@ to attempting the migration. If the `QuicClientSession` is not yet ready to migrate, the callback will be invoked with an `Error` using the code `ERR_QUICCLIENTSESSION_FAILED_SETSOCKET`. -## Class: QuicServerSession extends QuicSession +### Class: QuicServerSession extends QuicSession @@ -457,7 +886,7 @@ The `QuicServerSession` class implements the server side of a QUIC connection. Instances are created internally and are emitted using the `QuicSocket` `'session'` event. -### Event: `'clientHello'` +#### Event: `'clientHello'` @@ -477,7 +906,7 @@ The callback is invoked with four arguments: * `callback` {Function} A callback function that must be called in order for the TLS handshake to continue. -### Event: `'OCSPRequest'` +#### Event: `'OCSPRequest'` @@ -493,7 +922,17 @@ The callback is invoked with three arguments: The callback *must* be invoked in order for the TLS handshake to continue. -## Class: QuicSocket +#### quicserversession.addContext(servername[, context]) + + +* `servername` {String} A DNS name to associate with the given context. +* `context` {tls.SecureContext} A TLS SecureContext to associate with the `servername`. + +TBD + +### Class: QuicSocket @@ -504,14 +943,14 @@ method. Once created, a `QuicSocket` can be configured to work as both a client and a server. -### Event: `'close'` +#### Event: `'close'` Emitted after the `QuicSocket` has been destroyed and is no longer usable. -### Event: `'error'` +#### Event: `'error'` @@ -519,21 +958,21 @@ added: REPLACEME Emitted before the `'close'` event if the `QuicSocket` was destroyed with an `error`. -### Event: `'ready'` +#### Event: `'ready'` Emitted once the `QuicSocket` has been bound to a local UDP port. -### Event: `'session'` +#### Event: `'session'` Emitted when a new `QuicServerSession` has been created. -### quicsocket.addMembership(address, iface) +#### quicsocket.addMembership(address, iface) @@ -548,7 +987,7 @@ choose one interface and will add membership to it. To add membership to every available interface, call `quicsocket.addMembership()` multiple times, once per interface. -### quicsocket.address +#### quicsocket.address @@ -566,7 +1005,7 @@ The object will contain the properties: If the `QuicSocket` is not bound, `quicsocket.address` is an empty object. -### quicsocket.bound +#### quicsocket.bound @@ -576,7 +1015,7 @@ added: REPLACEME Will be `true` if the `QuicSocket` has been successfully bound to the local UDP port. -### quicsocket.close([callback]) +#### quicsocket.close([callback]) @@ -587,7 +1026,7 @@ Gracefully closes the `QuicSocket`. Existing `QuicSession` instances will be permitted to close naturally. New `QuicClientSession` and `QuicServerSession` instances will not be allowed. -### quicsocket.connect([options]) +#### quicsocket.connect([options]) @@ -713,7 +1152,7 @@ Create a new `QuicClientSession`. This function can be called multiple times to create sessions associated with different endpoints on the same client endpoint. -### quicsocket.destroy([error]) +#### quicsocket.destroy([error]) @@ -723,7 +1162,7 @@ added: REPLACEME Destroys the `QuicSocket` then emits the `'close'` event when done. The `'error'` event will be emitted after `'close'` if the `error` is not `undefined`. -### quicsocket.destroyed +#### quicsocket.destroyed @@ -732,7 +1171,7 @@ added: REPLACEME Will be `true` if the `QuicSocket` has been destroyed. -### quicsocket.dropMembership(address, iface) +#### quicsocket.dropMembership(address, iface) @@ -748,7 +1187,7 @@ never have reason to call this. If `multicastInterface` is not specified, the operating system will attempt to drop membership on all valid interfaces. -### quicsocket.fd +#### quicsocket.fd @@ -758,7 +1197,7 @@ added: REPLACEME The system file descriptor the `QuicSocket` is bound to. This property is not set on Windows. -### quicsocket.listen([options][, callback]) +#### quicsocket.listen([options][, callback]) @@ -869,7 +1308,7 @@ Listen for new peer-initiated sessions. If a `callback` is given, it is registered as a handler for the `'session'` event. -### quicsocket.pending +#### quicsocket.pending @@ -878,12 +1317,12 @@ added: REPLACEME Set to `true` if the socket is not yet bound to the local UDP port. -### quicsocket.ref() +#### quicsocket.ref() -### quicsocket.setBroadcast([on]) +#### quicsocket.setBroadcast([on]) @@ -893,7 +1332,7 @@ added: REPLACEME Sets or clears the `SO_BROADCAST` socket option. When set to `true`, UDP packets may be sent to a local interface's broadcast address. -### quicsocket.setMulticastLoopback([on]) +#### quicsocket.setMulticastLoopback([on]) @@ -903,7 +1342,7 @@ added: REPLACEME Sets or clears the `IP_MULTICAST_LOOP` socket option. When set to `true`, multicast packets will also be received on the local interface. -### quicsocket.setMulticastInterface(iface) +#### quicsocket.setMulticastInterface(iface) @@ -929,7 +1368,7 @@ also use explicit scope in addresses, so only packets sent to a multicast address without specifying an explicit scope are affected by the most recent successful use of this call. -#### Examples: IPv6 Outgoing Multicast Interface +##### Examples: IPv6 Outgoing Multicast Interface @@ -953,7 +1392,7 @@ socket.on('ready', () => { }); ``` -#### Example: IPv4 Outgoing Multicast Interface +##### Example: IPv4 Outgoing Multicast Interface @@ -967,7 +1406,7 @@ socket.on('ready', () => { }); ``` -#### Call Results +##### Call Results A call on a socket that is not ready to send or no longer open may throw a Not running Error. @@ -987,7 +1426,7 @@ A socket's address family's ANY address (IPv4 `'0.0.0.0'` or IPv6 `'::'`) can be used to return control of the sockets default outgoing interface to the system for future multicast packets. -### quicsocket.setMulticastTTL(ttl) +#### quicsocket.setMulticastTTL(ttl) @@ -1003,7 +1442,7 @@ decremented to `0` by a router, it will not be forwarded. The argument passed to `socket.setMulticastTTL()` is a number of hops between `0` and `255`. The default on most systems is `1` but can vary. -### quicsocket.setServerBusy([on]) +#### quicsocket.setServerBusy([on]) @@ -1016,7 +1455,7 @@ to reject all new incoming connection requests using the `SERVER_BUSY` QUIC error code. To begin receiving connections again, disable busy mode by calling `setServerBusy(false)`. -### quicsocket.setTTL(ttl) +#### quicsocket.setTTL(ttl) @@ -1032,19 +1471,19 @@ Changing TTL values is typically done for network probes or when multicasting. The argument to `socket.setTTL()` is a number of hops between `1` and `255`. The default on most systems is `64` but can vary. -### quicsocket.unref(); +#### quicsocket.unref(); -## Class: QuicStream extends stream.Duplex +### Class: QuicStream extends stream.Duplex * Extends: {stream.Duplex} -### Event: `'abort'` +#### Event: `'abort'` @@ -1058,7 +1497,7 @@ The callback is invoked with two arguments: * `finalSize` {number} The total number of bytes received by the `QuicStream` as of the moment the stream was closed. -### Event: `'close'` +#### Event: `'close'` @@ -1066,22 +1505,22 @@ added: REPLACEME Emitted when the `QuicStream` has is completely closed and the underlying resources have been freed. -### Event: `'data'` +#### Event: `'data'` -### Event: `'end'` +#### Event: `'end'` -### Event: `'error'` +#### Event: `'error'` -### Event: `'informationalHeaders'` +#### Event: `'informationalHeaders'` @@ -1101,7 +1540,7 @@ stream('informationalHeaders', (headers) => { }); ``` -### Event: `'initialHeaders'` +#### Event: `'initialHeaders'` @@ -1123,7 +1562,7 @@ stream('initialHeaders', (headers) => { }); ``` -### Event: `'ready'` +#### Event: `'ready'` @@ -1132,7 +1571,7 @@ Emitted when the underlying `QuicSession` has emitted its `secure` event this stream has received its id, which is accessible as `stream.id` once this event is emitted. -### Event: `'trailingHeaders'` +#### Event: `'trailingHeaders'` @@ -1154,12 +1593,20 @@ stream('trailingHeaders', (headers) => { }); ``` -### Event: `'readable'` +#### Event: `'readable'` -### quicstream.bidirectional +#### quicstream.aborted + +* Type: {boolean} + +True if dataflow on the `QuicStream` was prematurely terminated. + +#### quicstream.bidirectional @@ -1168,7 +1615,7 @@ added: REPLACEME Set to `true` if the `QuicStream` is bidirectional. -### quicstream.clientInitiated +#### quicstream.clientInitiated @@ -1178,7 +1625,36 @@ added: REPLACEME Set to `true` if the `QuicStream` was initiated by a `QuicClientSession` instance. -### quicstream.id +#### quicstream.close(code) + + +* `code` {number} + +Closes the `QuicStream`. + +#### quicstream.dataAckHistogram + + +TBD + +#### quicstream.dataRateHistogram + + +TBD + +#### quicstream.dataSizeHistogram + +TBD + +#### quicstream.id @@ -1187,7 +1663,7 @@ added: REPLACEME The numeric identifier of the `QuicStream`. -### quicstream.pending +#### quicstream.pending @@ -1197,7 +1673,7 @@ added: REPLACEME This property is `true` if the underlying session is not finished yet, i.e. before the `'ready'` event is emitted. -### quicstream.serverInitiated +#### quicstream.serverInitiated @@ -1207,7 +1683,7 @@ added: REPLACEME Set to `true` if the `QuicStream` was initiated by a `QuicServerSession` instance. -### quicstream.session +#### quicstream.session @@ -1216,7 +1692,7 @@ added: REPLACEME The `QuicServerSession` or `QuicClientSession`. -### quicstream.sendFD(fd[, options]) +#### quicstream.sendFD(fd[, options]) @@ -1242,7 +1718,7 @@ Using the same file descriptor concurrently for multiple streams is not supported and may result in data loss. Re-using a file descriptor after a stream has finished is supported. -### quicstream.sendFile(path[, options]) +#### quicstream.sendFile(path[, options]) @@ -1264,7 +1740,31 @@ If `offset` is set to a non-negative number, reading starts from that position. If `length` is set to a non-negative number, it gives the maximum number of bytes that are read from the file. -### quicstream.unidirectional +#### quicstream.submitInformationalHeaders(headers) + +* {headers} {Object} + +TBD + +#### quicstream.submitInitialHeaders(headers) + +* {headers} {Object} + +TBD + +#### quicstream.submitTrailingHeaders(headers) + +* {headers} {Object} + +TBD + +#### quicstream.unidirectional diff --git a/lib/internal/quic/core.js b/lib/internal/quic/core.js index 088ea20ce0..6f566af4ca 100644 --- a/lib/internal/quic/core.js +++ b/lib/internal/quic/core.js @@ -139,6 +139,20 @@ const { IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED, IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI, IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI, + IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT, + IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT, + IDX_QUIC_SESSION_STATS_CREATED_AT, + IDX_QUIC_SESSION_STATS_HANDSHAKE_START_AT, + IDX_QUIC_SESSION_STATS_BYTES_RECEIVED, + IDX_QUIC_SESSION_STATS_BYTES_SENT, + IDX_QUIC_SESSION_STATS_BIDI_STREAM_COUNT, + IDX_QUIC_SESSION_STATS_UNI_STREAM_COUNT, + IDX_QUIC_SESSION_STATS_STREAMS_IN_COUNT, + IDX_QUIC_SESSION_STATS_STREAMS_OUT_COUNT, + IDX_QUIC_SESSION_STATS_KEYUPDATE_COUNT, + IDX_QUIC_SESSION_STATS_LOSS_RETRANSMIT_COUNT, + IDX_QUIC_SESSION_STATS_ACK_DELAY_RETRANSMIT_COUNT, + IDX_QUIC_SESSION_STATS_MAX_BYTES_IN_FLIGHT, ERR_INVALID_REMOTE_TRANSPORT_PARAMS, ERR_INVALID_TLS_SESSION_TICKET, NGTCP2_PATH_VALIDATION_RESULT_FAILURE, @@ -534,8 +548,8 @@ function onStreamError(streamHandle, error) { // received for the stream. Not all QuicStreams // will support headers. The headers argument // here is an Array of name-value pairs. -function onStreamHeaders(headers, kind) { - this[owner_symbol][kHeaders](headers, kind); +function onStreamHeaders(id, headers, kind) { + this[owner_symbol][kHeaders](id, headers, kind); } function onSessionSilentClose(statelessReset, code, family) { @@ -1491,6 +1505,14 @@ class QuicSession extends EventEmitter { stream.destroy(); } + [kHeaders](id, headers, kind) { + const stream = this.#streams.get(id); + if (stream === undefined) + return; + + stream[kHeaders](headers, kind); + } + [kStreamReset](id, code, finalSize) { const stream = this.#streams.get(id); if (stream === undefined) @@ -1670,6 +1692,16 @@ class QuicSession extends EventEmitter { return this.#socket ? this.#socket.address : {}; } + get maxDataLeft() { + return this[kHandle] ? + this[kHandle].state[IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT] : 0; + } + + get bytesInFlight() { + return this[kHandle] ? + this[kHandle].state[IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT] : 0; + } + get authenticated() { // Specifically check for null. Undefined means the check has not // been performed yet, another other value other than null means @@ -1745,6 +1777,7 @@ class QuicSession extends EventEmitter { }; } + get socket() { return this.#socket; } @@ -1797,7 +1830,7 @@ class QuicSession extends EventEmitter { get duration() { const now = process.hrtime.bigint(); const stats = this.#stats || this[kHandle].stats; - return now - stats[0]; + return now - stats[IDX_QUIC_SESSION_STATS_CREATED_AT]; } get handshakeDuration() { @@ -1805,42 +1838,57 @@ class QuicSession extends EventEmitter { const end = this.handshakeComplete ? stats[4] : process.hrtime.bigint(); - return end - stats[1]; + return end - stats[IDX_QUIC_SESSION_STATS_HANDSHAKE_START_AT]; } get bytesReceived() { const stats = this.#stats || this[kHandle].stats; - return stats[8]; + return stats[IDX_QUIC_SESSION_STATS_BYTES_RECEIVED]; } get bytesSent() { const stats = this.#stats || this[kHandle].stats; - return stats[9]; + return stats[IDX_QUIC_SESSION_STATS_BYTES_SENT]; } get bidiStreamCount() { const stats = this.#stats || this[kHandle].stats; - return stats[10]; + return stats[IDX_QUIC_SESSION_STATS_BIDI_STREAM_COUNT]; } get uniStreamCount() { const stats = this.#stats || this[kHandle].stats; - return stats[11]; + return stats[IDX_QUIC_SESSION_STATS_UNI_STREAM_COUNT]; + } + + get maxInFlightBytes() { + const stats = this.#stats || this[kHandle].stats; + return stats[IDX_QUIC_SESSION_STATS_MAX_BYTES_IN_FLIGHT]; + } + + get lossRetransmitCount() { + const stats = this.#stats || this[kHandle].stats; + return stats[IDX_QUIC_SESSION_STATS_LOSS_RETRANSMIT_COUNT]; + } + + get ackDelayRetransmitCount() { + const stats = this.#stats || this[kHandle].stats; + return stats[IDX_QUIC_SESSION_STATS_ACK_DELAY_RETRANSMIT_COUNT]; } get peerInitiatedStreamCount() { const stats = this.#stats || this[kHandle].stats; - return stats[12]; + return stats[IDX_QUIC_SESSION_STATS_STREAMS_IN_COUNT]; } get selfInitiatedStreamCount() { const stats = this.#stats || this[kHandle].stats; - return stats[13]; + return stats[IDX_QUIC_SESSION_STATS_STREAMS_OUT_COUNT]; } get keyUpdateCount() { const stats = this.#stats || this[kHandle].stats; - return stats[14]; + return stats[IDX_QUIC_SESSION_STATS_KEYUPDATE_COUNT]; } get minRTT() { diff --git a/node.gyp b/node.gyp index 62b393b7d2..528dff08dd 100644 --- a/node.gyp +++ b/node.gyp @@ -841,6 +841,7 @@ 'src/node_quic_socket.h', 'src/node_quic_stream.h', 'src/node_quic_util.h', + 'src/node_quic_util-inl.h', 'src/node_quic_state.h', 'src/node_quic_default_application.h', 'src/node_quic_http3_application.h', @@ -848,7 +849,6 @@ 'src/node_quic_session.cc', 'src/node_quic_socket.cc', 'src/node_quic_stream.cc', - 'src/node_quic_util.cc', 'src/node_quic.cc', 'src/node_quic_default_application.cc', 'src/node_quic_http3_application.cc' diff --git a/src/js_udp_wrap.cc b/src/js_udp_wrap.cc index 76d9eb39b8..acd956bf00 100644 --- a/src/js_udp_wrap.cc +++ b/src/js_udp_wrap.cc @@ -1,6 +1,7 @@ #include "udp_wrap.h" #include "async_wrap-inl.h" #include "node_errors.h" +#include "node_sockaddr-inl.h" namespace node { @@ -27,6 +28,8 @@ class JSUDPWrap final : public UDPWrapBase, public AsyncWrap { const sockaddr* addr) override; int GetPeerName(sockaddr* name, int* namelen) override; int GetSockName(sockaddr* name, int* namelen) override; + SocketAddress* GetPeerName(SocketAddress* addr = nullptr) override; + SocketAddress* GetSockName(SocketAddress* addr = nullptr) override; int GetSockaddr(sockaddr* name, int* namelen, bool peer); AsyncWrap* GetAsyncWrap() override { return this; } @@ -120,6 +123,16 @@ int JSUDPWrap::GetSockName(sockaddr* name, int* namelen) { return GetSockaddr(name, namelen, false); } +SocketAddress* JSUDPWrap::GetPeerName(SocketAddress* addr) { + // TODO(jasnell): Maybe turn this into a real JS-based method. + return SocketAddress::New("127.0.0.1", 1337, AF_INET, addr); +} + +SocketAddress* JSUDPWrap::GetSockName(SocketAddress* addr) { + // TODO(jasnell): Maybe turn this into a real JS-based method. + return SocketAddress::New("127.0.0.1", 1337, AF_INET, addr); +} + int JSUDPWrap::GetSockaddr(sockaddr* name, int* namelen, bool peer) { // TODO(addaleax): Maybe turn this into a real JS-based method. sockaddr_in addr_in; diff --git a/src/node_quic.cc b/src/node_quic.cc index 2237d7232b..8f894f89ba 100755 --- a/src/node_quic.cc +++ b/src/node_quic.cc @@ -10,7 +10,8 @@ #include "node_quic_socket.h" #include "node_quic_stream.h" #include "node_quic_state.h" -#include "node_quic_util.h" +#include "node_quic_util-inl.h" +#include "node_sockaddr-inl.h" #include #include @@ -147,6 +148,8 @@ void Initialize(Local target, NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED); NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI); NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT); NODE_DEFINE_CONSTANT(constants, MAX_RETRYTOKEN_EXPIRATION); NODE_DEFINE_CONSTANT(constants, MIN_RETRYTOKEN_EXPIRATION); NODE_DEFINE_CONSTANT(constants, NGTCP2_MAX_CIDLEN); @@ -184,6 +187,33 @@ void Initialize(Local target, NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_MAX_CRYPTO_BUFFER); NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_CONFIG_COUNT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_CREATED_AT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_HANDSHAKE_START_AT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_HANDSHAKE_SEND_AT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_HANDSHAKE_CONTINUE_AT); + NODE_DEFINE_CONSTANT(constants, + IDX_QUIC_SESSION_STATS_HANDSHAKE_COMPLETED_AT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_HANDSHAKE_ACKED_AT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_SENT_AT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_RECEIVED_AT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_CLOSING_AT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_BYTES_RECEIVED); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_BYTES_SENT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_BIDI_STREAM_COUNT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_UNI_STREAM_COUNT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_STREAMS_IN_COUNT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_STREAMS_OUT_COUNT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_KEYUPDATE_COUNT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_RETRY_COUNT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_LOSS_RETRANSMIT_COUNT); + NODE_DEFINE_CONSTANT(constants, + IDX_QUIC_SESSION_STATS_ACK_DELAY_RETRANSMIT_COUNT); + NODE_DEFINE_CONSTANT(constants, + IDX_QUIC_SESSION_STATS_PATH_VALIDATION_SUCCESS_COUNT); + NODE_DEFINE_CONSTANT(constants, + IDX_QUIC_SESSION_STATS_PATH_VALIDATION_FAILURE_COUNT); + NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_MAX_BYTES_IN_FLIGHT); + NODE_DEFINE_CONSTANT(constants, IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY); NODE_DEFINE_CONSTANT(constants, IDX_HTTP3_QPACK_BLOCKED_STREAMS); NODE_DEFINE_CONSTANT(constants, IDX_HTTP3_MAX_HEADER_LIST_SIZE); diff --git a/src/node_quic_crypto.cc b/src/node_quic_crypto.cc index c75cdc14fe..a9e5e27533 100644 --- a/src/node_quic_crypto.cc +++ b/src/node_quic_crypto.cc @@ -3,7 +3,8 @@ #include "node_crypto.h" #include "node_crypto_common.h" #include "node_quic_session-inl.h" -#include "node_quic_util.h" +#include "node_quic_util-inl.h" +#include "node_sockaddr-inl.h" #include "node_url.h" #include "string_bytes.h" #include "v8.h" @@ -144,7 +145,7 @@ bool GenerateRetryToken( ngtcp2_crypto_ctx ctx; ngtcp2_crypto_ctx_initial(&ctx); - const size_t addrlen = SocketAddress::GetAddressLen(addr); + const size_t addrlen = SocketAddress::GetLength(addr); size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(&ctx.aead); uint64_t now = uv_hrtime(); @@ -203,7 +204,7 @@ bool InvalidRetryToken( ngtcp2_crypto_ctx_initial(&ctx); size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(&ctx.aead); - const size_t addrlen = SocketAddress::GetAddressLen(addr); + const size_t addrlen = SocketAddress::GetLength(addr); if (hd->tokenlen < TOKEN_RAND_DATALEN) return true; @@ -420,7 +421,7 @@ int VerifyHostnameIdentity( // check. It's possible that the X509_check_ip_asc covers this. If so, // we can remove this check. - if (SocketAddress::numeric_host(hostname)) { + if (SocketAddress::is_numeric_host(hostname)) { auto ips = altnames.equal_range("ip"); for (auto ip = ips.first; ip != ips.second; ++ip) { if (ip->second.compare(hostname) == 0) { @@ -638,7 +639,7 @@ void SetHostname(SSL* ssl, const std::string& hostname) { // TODO(@jasnell): Need to determine if setting localhost // here is the right thing to do. if (hostname.length() == 0 || - SocketAddress::numeric_host(hostname.c_str())) { + SocketAddress::is_numeric_host(hostname.c_str())) { SSL_set_tlsext_host_name(ssl, "localhost"); } else { SSL_set_tlsext_host_name(ssl, hostname.c_str()); diff --git a/src/node_quic_default_application.cc b/src/node_quic_default_application.cc index cc4413c0ae..ba7af66294 100644 --- a/src/node_quic_default_application.cc +++ b/src/node_quic_default_application.cc @@ -2,6 +2,8 @@ #include "node_quic_default_application.h" #include "node_quic_session-inl.h" #include "node_quic_stream.h" +#include "node_quic_util-inl.h" +#include "node_sockaddr-inl.h" #include namespace node { diff --git a/src/node_quic_http3_application.cc b/src/node_quic_http3_application.cc index ee80487edb..4e8d027406 100644 --- a/src/node_quic_http3_application.cc +++ b/src/node_quic_http3_application.cc @@ -4,7 +4,8 @@ #include "node_quic_http3_application.h" #include "node_quic_session-inl.h" #include "node_quic_stream.h" -#include "node_quic_util.h" +#include "node_quic_util-inl.h" +#include "node_sockaddr-inl.h" #include "node_http_common-inl.h" #include @@ -536,7 +537,9 @@ ssize_t Http3Application::H3ReadData( // available to send but there might be later, so return WOULDBLOCK // to tell nghttp3 to hold off attempting to serialize any more // data for this stream until it is resumed. - return count == 0 ? NGHTTP3_ERR_WOULDBLOCK : count; + if (count == 0) + return NGHTTP3_ERR_WOULDBLOCK; + return count; } // Outgoing data is retained in memory until it is acknowledged. @@ -553,19 +556,7 @@ void Http3Application::H3AckedStreamData( void Http3Application::H3StreamClose( int64_t stream_id, uint64_t app_error_code) { - Environment* env = Session()->env(); - Local argv[] = { - Number::New(env->isolate(), static_cast(stream_id)), - Number::New(env->isolate(), static_cast(app_error_code)) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(Session()); - Session()->MakeCallback( - env->quic_on_stream_close_function(), - arraysize(argv), - argv); + Session()->Listener()->OnStreamClose(stream_id, app_error_code); } QuicStream* Http3Application::FindOrCreateStream(int64_t stream_id) { diff --git a/src/node_quic_http3_application.h b/src/node_quic_http3_application.h index 265900d041..5c234cd8b4 100644 --- a/src/node_quic_http3_application.h +++ b/src/node_quic_http3_application.h @@ -49,7 +49,7 @@ constexpr size_t DEFAULT_MAX_PUSHES = 65535; using Http3ConnectionPointer = DeleteFnPtr; -class Http3Header : public QuicStream::Header { +class Http3Header : public QuicHeader { public: Http3Header(int32_t token, nghttp3_rcbuf* name, nghttp3_rcbuf* value); Http3Header(Http3Header&& other) noexcept : diff --git a/src/node_quic_session-inl.h b/src/node_quic_session-inl.h index b8c02798c2..c103f7802f 100644 --- a/src/node_quic_session-inl.h +++ b/src/node_quic_session-inl.h @@ -39,12 +39,12 @@ void QuicSession::SetLastError(QuicError error) { last_error_ = error; } -void QuicSession::SetLastError(QuicErrorFamily family, uint64_t code) { +void QuicSession::SetLastError(int32_t family, uint64_t code) { SetLastError({ family, code }); } -void QuicSession::SetLastError(QuicErrorFamily family, int code) { - SetLastError(family, ngtcp2_err_infer_quic_transport_error_code(code)); +void QuicSession::SetLastError(int32_t family, int code) { + SetLastError({ family, code }); } bool QuicSession::IsInClosingPeriod() { diff --git a/src/node_quic_session.cc b/src/node_quic_session.cc index 4f485f6ebf..dc899bb71d 100644 --- a/src/node_quic_session.cc +++ b/src/node_quic_session.cc @@ -10,15 +10,17 @@ #include "node_crypto.h" #include "node_internals.h" #include "node_mem-inl.h" +#include "node_quic_buffer.h" #include "node_quic_crypto.h" #include "node_quic_session.h" // NOLINT(build/include_inline) #include "node_quic_session-inl.h" #include "node_quic_socket.h" #include "node_quic_stream.h" #include "node_quic_state.h" -#include "node_quic_util.h" +#include "node_quic_util-inl.h" #include "node_quic_default_application.h" #include "node_quic_http3_application.h" +#include "node_sockaddr-inl.h" #include "v8.h" #include "uv.h" @@ -191,6 +193,491 @@ void QuicSessionConfig::SetQlog(const ngtcp2_qlog_settings& qlog) { settings_.qlog = qlog; } + +QuicSessionListener::~QuicSessionListener() { + if (session_ != nullptr) + session_->RemoveListener(this); +} + +void QuicSessionListener::OnKeylog(const char* line, size_t len) { + if (previous_listener_ != nullptr) + previous_listener_->OnKeylog(line, len); +} + +void QuicSessionListener::OnClientHello( + const char* alpn, + const char* server_name) { + if (previous_listener_ != nullptr) + previous_listener_->OnClientHello(alpn, server_name); +} + +void QuicSessionListener::OnCert(const char* server_name) { + if (previous_listener_ != nullptr) + previous_listener_->OnCert(server_name); +} + +void QuicSessionListener::OnOCSP(const std::string& ocsp) { + if (previous_listener_ != nullptr) + previous_listener_->OnOCSP(ocsp); +} + +void QuicSessionListener::OnStreamHeaders( + int64_t stream_id, + int kind, + const std::vector>& headers) { + if (previous_listener_ != nullptr) + previous_listener_->OnStreamHeaders(stream_id, kind, headers); +} + +void QuicSessionListener::OnStreamClose( + int64_t stream_id, + uint64_t app_error_code) { + if (previous_listener_ != nullptr) + previous_listener_->OnStreamClose(stream_id, app_error_code); +} + +void QuicSessionListener::OnStreamReset( + int64_t stream_id, + uint64_t final_size, + uint64_t app_error_code) { + if (previous_listener_ != nullptr) + previous_listener_->OnStreamReset(stream_id, final_size, app_error_code); +} + +void QuicSessionListener::OnSessionDestroyed() { + if (previous_listener_ != nullptr) + previous_listener_->OnSessionDestroyed(); +} + +void QuicSessionListener::OnSessionClose(QuicError error) { + if (previous_listener_ != nullptr) + previous_listener_->OnSessionClose(error); +} + +void QuicSessionListener::OnStreamReady(BaseObjectPtr stream) { + if (previous_listener_ != nullptr) + previous_listener_->OnStreamReady(stream); +} + +void QuicSessionListener::OnHandshakeCompleted() { + if (previous_listener_ != nullptr) + previous_listener_->OnHandshakeCompleted(); +} + +void QuicSessionListener::OnPathValidation( + ngtcp2_path_validation_result res, + const sockaddr* local, + const sockaddr* remote) { + if (previous_listener_ != nullptr) + previous_listener_->OnPathValidation(res, local, remote); +} + +void QuicSessionListener::OnSessionTicket(int size, SSL_SESSION* session) { + if (previous_listener_ != nullptr) { + previous_listener_->OnSessionTicket(size, session); + } +} + +void QuicSessionListener::OnSessionSilentClose( + bool stateless_reset, + QuicError error) { + if (previous_listener_ != nullptr) + previous_listener_->OnSessionSilentClose(stateless_reset, error); +} + +void QuicSessionListener::OnVersionNegotiation( + uint32_t supported_version, + const uint32_t* versions, + size_t vcnt) { + if (previous_listener_ != nullptr) + previous_listener_->OnVersionNegotiation(supported_version, versions, vcnt); +} + +void QuicSessionListener::OnQLog(const uint8_t* data, size_t len) { + if (previous_listener_ != nullptr) + previous_listener_->OnQLog(data, len); +} + +void JSQuicSessionListener::OnKeylog(const char* line, size_t len) { + Environment* env = Session()->env(); + + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + Local line_bf = Buffer::Copy(env, line, 1 + len).ToLocalChecked(); + char* data = Buffer::Data(line_bf); + data[len] = '\n'; + + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback(env->quic_on_session_keylog_function(), 1, &line_bf); +} + +void JSQuicSessionListener::OnClientHello( + const char* alpn, + const char* server_name) { + + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local argv[] = { + Undefined(env->isolate()), + Undefined(env->isolate()), + crypto::GetClientHelloCiphers(env, Session()->CryptoContext()->ssl()) + }; + + if (alpn != nullptr) { + argv[0] = String::NewFromUtf8( + env->isolate(), + alpn, + v8::NewStringType::kNormal).ToLocalChecked(); + } + if (server_name != nullptr) { + argv[1] = String::NewFromUtf8( + env->isolate(), + server_name, + v8::NewStringType::kNormal).ToLocalChecked(); + } + + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_session_client_hello_function(), + arraysize(argv), argv); +} + +void JSQuicSessionListener::OnCert(const char* server_name) { + Environment* env = Session()->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local servername_str; + + Local argv[] = { + server_name == nullptr ? + String::Empty(env->isolate()) : + OneByteString( + env->isolate(), + server_name, + strlen(server_name)) + }; + + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_session_cert_function(), + arraysize(argv), + argv); +} + +void JSQuicSessionListener::OnStreamHeaders( + int64_t stream_id, + int kind, + const std::vector>& headers) { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + MaybeStackBuffer, 16> head(headers.size()); + size_t n = 0; + for (const auto& header : headers) { + // name and value should never be empty here, and if + // they are, there's an actual bug so go ahead and crash + Local pair[] = { + header->GetName(Session()->Application()).ToLocalChecked(), + header->GetValue(Session()->Application()).ToLocalChecked() + }; + head[n++] = Array::New(env->isolate(), pair, arraysize(pair)); + } + Local argv[] = { + Number::New(env->isolate(), static_cast(stream_id)), + Array::New(env->isolate(), head.out(), n), + Integer::New(env->isolate(), kind) + }; + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_stream_headers_function(), + arraysize(argv), argv); +} + +void JSQuicSessionListener::OnOCSP(const std::string& ocsp) { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + Local arg = Undefined(env->isolate()); + if (ocsp.length() > 0) + arg = Buffer::Copy(env, ocsp.c_str(), ocsp.length()).ToLocalChecked(); + BaseObjectPtr ptr(Session()); + Session()->MakeCallback(env->quic_on_session_status_function(), 1, & arg); +} + +void JSQuicSessionListener::OnStreamClose( + int64_t stream_id, + uint64_t app_error_code) { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local argv[] = { + Number::New(env->isolate(), static_cast(stream_id)), + Number::New(env->isolate(), static_cast(app_error_code)) + }; + + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_stream_close_function(), + arraysize(argv), + argv); +} + +void JSQuicSessionListener::OnStreamReset( + int64_t stream_id, + uint64_t final_size, + uint64_t app_error_code) { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local argv[] = { + Number::New(env->isolate(), static_cast(stream_id)), + Number::New(env->isolate(), static_cast(app_error_code)), + Number::New(env->isolate(), static_cast(final_size)) + }; + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_stream_reset_function(), + arraysize(argv), + argv); +} + +void JSQuicSessionListener::OnSessionDestroyed() { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + // Emit the 'close' event in JS. This needs to happen after destroying the + // connection, because doing so also releases the last qlog data. + Session()->MakeCallback( + env->quic_on_session_destroyed_function(), 0, nullptr); +} + +void JSQuicSessionListener::OnSessionClose(QuicError error) { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local argv[] = { + Number::New(env->isolate(), static_cast(error.code)), + Integer::New(env->isolate(), error.family) + }; + + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_session_close_function(), + arraysize(argv), argv); +} + +void JSQuicSessionListener::OnStreamReady(BaseObjectPtr stream) { + Environment* env = Session()->env(); + Local argv[] = { + stream->object(), + Number::New(env->isolate(), static_cast(stream->GetID())) + }; + + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_stream_ready_function(), + arraysize(argv), argv); +} + +void JSQuicSessionListener::OnHandshakeCompleted() { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local servername = Undefined(env->isolate()); + const char* hostname = + crypto::GetServerName(Session()->CryptoContext()->ssl()); + if (hostname != nullptr) { + servername = + String::NewFromUtf8( + env->isolate(), + hostname, + v8::NewStringType::kNormal).ToLocalChecked(); + } + + int err = Session()->CryptoContext()->VerifyPeerIdentity( + hostname != nullptr ? + hostname : + Session()->GetHostname().c_str()); + + Local argv[] = { + servername, + GetALPNProtocol(Session()), + crypto::GetCipherName(env, Session()->CryptoContext()->ssl()), + crypto::GetCipherVersion(env, Session()->CryptoContext()->ssl()), + Integer::New(env->isolate(), Session()->max_pktlen_), + err != 0 ? + crypto::GetValidationErrorReason(env, err) : + v8::Undefined(env->isolate()).As(), + err != 0 ? + crypto::GetValidationErrorCode(env, err) : + v8::Undefined(env->isolate()).As() + }; + + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_session_handshake_function(), + arraysize(argv), + argv); +} + +void JSQuicSessionListener::OnPathValidation( + ngtcp2_path_validation_result res, + const sockaddr* local, + const sockaddr* remote) { + // This is a fairly expensive operation because both the local and + // remote addresses have to converted into JavaScript objects. We + // only do this if a pathValidation handler is registered. + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Local context = env->context(); + Context::Scope context_scope(context); + Local argv[] = { + Integer::New(env->isolate(), res), + AddressToJS(env, local), + AddressToJS(env, remote) + }; + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_session_path_validation_function(), + arraysize(argv), + argv); +} + +void JSQuicSessionListener::OnSessionTicket(int size, SSL_SESSION* session) { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + + unsigned int session_id_length; + const unsigned char* session_id_data = + SSL_SESSION_get_id(session, &session_id_length); + + Local argv[] = { + Buffer::Copy( + env, + reinterpret_cast(session_id_data), + session_id_length).ToLocalChecked(), + v8::Undefined(env->isolate()), + v8::Undefined(env->isolate()) + }; + + AllocatedBuffer session_ticket = env->AllocateManaged(size); + unsigned char* session_data = + reinterpret_cast(session_ticket.data()); + memset(session_data, 0, size); + i2d_SSL_SESSION(session, &session_data); + if (!session_ticket.empty()) + argv[1] = session_ticket.ToBuffer().ToLocalChecked(); + + if (Session()->IsFlagSet( + QuicSession::QUICSESSION_FLAG_HAS_TRANSPORT_PARAMS)) { + argv[2] = Buffer::Copy( + env, + reinterpret_cast(&Session()->transport_params_), + sizeof(Session()->transport_params_)).ToLocalChecked(); + } + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_session_ticket_function(), + arraysize(argv), argv); + +} + +void JSQuicSessionListener::OnSessionSilentClose( + bool stateless_reset, + QuicError error) { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local argv[] = { + stateless_reset ? v8::True(env->isolate()) : v8::False(env->isolate()), + Number::New(env->isolate(), static_cast(error.code)), + Integer::New(env->isolate(), error.family) + }; + + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_session_silent_close_function(), arraysize(argv), argv); +} + +void JSQuicSessionListener::OnVersionNegotiation( + uint32_t supported_version, + const uint32_t* vers, + size_t vcnt) { + Environment* env = Session()->env(); + HandleScope scope(env->isolate()); + Local context = env->context(); + Context::Scope context_scope(context); + + MaybeStackBuffer, 4> versions(vcnt); + for (size_t n = 0; n < vcnt; n++) + versions[n] = Integer::New(env->isolate(), vers[n]); + + + Local supported = + Integer::New(env->isolate(), supported_version); + + Local argv[] = { + Integer::New(env->isolate(), NGTCP2_PROTO_VER), + Array::New(env->isolate(), versions.out(), vcnt), + Array::New(env->isolate(), &supported, 1) + }; + + // Grab a shared pointer to this to prevent the QuicSession + // from being freed while the MakeCallback is running. + BaseObjectPtr ptr(Session()); + Session()->MakeCallback( + env->quic_on_session_version_negotiation_function(), + arraysize(argv), argv); +} + +void JSQuicSessionListener::OnQLog(const uint8_t* data, size_t len) { + Environment* env = Session()->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local str = + String::NewFromOneByte(env->isolate(), + data, + v8::NewStringType::kNormal, + len).ToLocalChecked(); + + Session()->MakeCallback( + env->quic_on_session_qlog_function(), + 1, + &str); +} + // Check required capabilities were not excluded from the OpenSSL build: // - OPENSSL_NO_SSL_TRACE excludes SSL_trace() // - OPENSSL_NO_STDIO excludes BIO_new_fp() @@ -299,20 +786,7 @@ ngtcp2_crypto_level QuicCryptoContext::GetWriteCryptoLevel() { void QuicCryptoContext::Keylog(const char* line) { if (LIKELY(session_->state_[IDX_QUIC_SESSION_STATE_KEYLOG_ENABLED] == 0)) return; - - Environment* env = session_->env(); - - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - const size_t size = strlen(line); - Local line_bf = Buffer::Copy(env, line, 1 + size).ToLocalChecked(); - char* data = Buffer::Data(line_bf); - data[size] = '\n'; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(session_); - session_->MakeCallback(env->quic_on_session_keylog_function(), 1, &line_bf); + session_->Listener()->OnKeylog(line, strlen(line)); } // If a 'clientHello' event listener is registered on the JavaScript @@ -349,44 +823,11 @@ int QuicCryptoContext::OnClientHello() { return -1; in_client_hello_ = true; - Environment* env = session_->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - const char* alpn = - crypto::GetClientHelloALPN( - session_->CryptoContext()->ssl()); + crypto::GetClientHelloALPN(session_->CryptoContext()->ssl()); const char* server_name = - crypto::GetClientHelloServerName( - session_->CryptoContext()->ssl()); - - Local argv[] = { - Undefined(env->isolate()), - Undefined(env->isolate()), - crypto::GetClientHelloCiphers( - session_->env(), - session_->CryptoContext()->ssl()) - }; - - if (alpn != nullptr) { - argv[0] = String::NewFromUtf8( - env->isolate(), - alpn, - v8::NewStringType::kNormal).ToLocalChecked(); - } - if (server_name != nullptr) { - argv[1] = String::NewFromUtf8( - env->isolate(), - server_name, - v8::NewStringType::kNormal).ToLocalChecked(); - } - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(session_); - session_->MakeCallback( - env->quic_on_session_client_hello_function(), - arraysize(argv), argv); + crypto::GetClientHelloServerName(session_->CryptoContext()->ssl()); + session_->Listener()->OnClientHello(alpn, server_name); return in_client_hello_ ? -1 : 0; } @@ -423,31 +864,8 @@ int QuicCryptoContext::OnOCSP() { return -1; in_ocsp_request_ = true; - Environment* env = session_->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - Local servername_str; - const char* servername = - crypto::GetServerName( - session_->CryptoContext()->ssl()); - - Local argv[] = { - servername == nullptr ? - String::Empty(env->isolate()) : - OneByteString( - env->isolate(), - servername, - strlen(servername)) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(session_); - session_->MakeCallback( - env->quic_on_session_cert_function(), - arraysize(argv), - argv); + session_->Listener()->OnCert( + crypto::GetServerName(session_->CryptoContext()->ssl())); return in_ocsp_request_ ? -1 : 1; } @@ -546,16 +964,10 @@ int QuicCryptoContext::OnTLSStatus() { return SSL_TLSEXT_ERR_OK; } case NGTCP2_CRYPTO_SIDE_CLIENT: { - Local arg = Undefined(env->isolate()); std::string resp = GetOCSPResponse(); - if (resp.length() > 0) { + if (resp.length() > 0) Debug(session_, "An OCSP Response has been received"); - arg = Buffer::Copy(env, resp.c_str(), resp.length()).ToLocalChecked(); - } - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(session_); - session_->MakeCallback(env->quic_on_session_status_function(), 1, &arg); + session_->Listener()->OnOCSP(resp); return 1; } default: @@ -612,7 +1024,7 @@ bool QuicCryptoContext::SetSession(const unsigned char* data, size_t length) { void QuicCryptoContext::SetTLSAlert(int err) { Debug(session_, "TLS Alert [%d]: %s", err, SSL_alert_type_string_long(err)); - session_->SetLastError(InitQuicError(QUIC_ERROR_CRYPTO, err)); + session_->SetLastError(QuicError(QUIC_ERROR_CRYPTO, err)); } bool QuicCryptoContext::SetupInitialKey(const ngtcp2_cid* dcid) { @@ -697,45 +1109,24 @@ void QuicCryptoContext::WriteHandshake( handshake_[level].Push(std::move(buffer)); } +void QuicApplication::StreamHeaders( + int64_t stream_id, + int kind, + const std::vector>& headers) { + Session()->Listener()->OnStreamHeaders(stream_id, kind, headers); +} + void QuicApplication::StreamClose( int64_t stream_id, uint64_t app_error_code) { - - Environment* env = Session()->env(); - Local argv[] = { - Number::New(env->isolate(), static_cast(stream_id)), - Number::New(env->isolate(), static_cast(app_error_code)) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(Session()); - Session()->MakeCallback( - env->quic_on_stream_close_function(), - arraysize(argv), - argv); + Session()->Listener()->OnStreamClose(stream_id, app_error_code); } void QuicApplication::StreamReset( int64_t stream_id, uint64_t final_size, uint64_t app_error_code) { - Environment* env = Session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - - Local argv[] = { - Number::New(env->isolate(), static_cast(stream_id)), - Number::New(env->isolate(), static_cast(app_error_code)), - Number::New(env->isolate(), static_cast(final_size)) - }; - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(Session()); - Session()->MakeCallback( - env->quic_on_stream_reset_function(), - arraysize(argv), - argv); + Session()->Listener()->OnStreamReset(stream_id, final_size, app_error_code); } QuicApplication::QuicApplication(QuicSession* session) : session_(session) {} @@ -851,6 +1242,7 @@ QuicSession::QuicSession( socket->env()->isolate(), sizeof(recovery_stats_) / sizeof(double), reinterpret_cast(&recovery_stats_)) { + PushListener(&default_listener_); crypto_context_.reset(new QuicCryptoContext(this, ctx, side, options)); application_.reset(SelectApplication(this)); if (rcid != nullptr) @@ -912,7 +1304,8 @@ QuicSession::~QuicSession() { " Streams Out Count: %" PRIu64 "\n" " Remaining sendbuf_: %" PRIu64 "\n" " Remaining handshake_: %" PRIu64 "\n" - " Remaining txbuf_: %" PRIu64 "\n", + " Remaining txbuf_: %" PRIu64 "\n" + " Max In Flight Bytes: %" PRIu64 "\n", uv_hrtime() - session_stats_.created_at, session_stats_.handshake_start_at, session_stats_.handshake_completed_at, @@ -924,14 +1317,48 @@ QuicSession::~QuicSession() { session_stats_.streams_out_count, sendbuf_length, handshake_length, - txbuf_length); + txbuf_length, + session_stats_.max_bytes_in_flight); connection_.reset(); - // Emit the 'close' event in JS. This needs to happen after destroying the - // connection, because doing so also releases the last qlog data. - HandleScope scope(env()->isolate()); - MakeCallback(env()->quic_on_session_destroyed_function(), 0, nullptr); + QuicSessionListener* listener = Listener(); + listener->OnSessionDestroyed(); + if (listener == Listener()) + RemoveListener(listener); +} + +void QuicSession::PushListener(QuicSessionListener* listener) { + CHECK_NOT_NULL(listener); + CHECK_NULL(listener->session_); + + listener->previous_listener_ = listener_; + listener->session_ = this; + + listener_ = listener; +} + +void QuicSession::RemoveListener(QuicSessionListener* listener) { + CHECK_NOT_NULL(listener); + + QuicSessionListener* previous; + QuicSessionListener* current; + + for (current = listener_, previous = nullptr; + /* No loop condition because we want a crash if listener is not found */ + ; previous = current, current = current->previous_listener_) { + CHECK_NOT_NULL(current); + if (current == listener) { + if (previous != nullptr) + previous->previous_listener_ = current->previous_listener_; + else + listener_ = listener->previous_listener_; + break; + } + } + + listener->session_ = nullptr; + listener->previous_listener_ = nullptr; } std::string QuicSession::diagnostic_name() const { @@ -1048,20 +1475,9 @@ void QuicSession::ImmediateClose() { QuicError last_error = GetLastError(); Debug(this, "Immediate close with code %" PRIu64 " (%s)", last_error.code, - ErrorFamilyName(last_error.family)); - - HandleScope scope(env()->isolate()); - Context::Scope context_scope(env()->context()); - - Local argv[] = { - Number::New(env()->isolate(), static_cast(last_error.code)), - Integer::New(env()->isolate(), last_error.family) - }; + last_error.GetFamilyName()); - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(this); - MakeCallback(env()->quic_on_session_close_function(), arraysize(argv), argv); + Listener()->OnSessionClose(last_error); } void QuicSession::InitApplication() { @@ -1079,15 +1495,7 @@ QuicStream* QuicSession::CreateStream(int64_t stream_id) { BaseObjectPtr stream = QuicStream::New(this, stream_id); CHECK(stream); - Local argv[] = { - stream->object(), - Number::New(env()->isolate(), static_cast(stream_id)) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(this); - MakeCallback(env()->quic_on_stream_ready_function(), arraysize(argv), argv); + Listener()->OnStreamReady(stream); return stream.get(); } @@ -1233,55 +1641,8 @@ void QuicSession::HandleError() { // need to do at this point is let the javascript side know. void QuicSession::HandshakeCompleted() { Debug(this, "Handshake is completed"); - - // TODO(@jasnell): We should determine if the ALPN - // protocol identifier was selected by this point. - // If no protocol identifier was selected, then we - // should stop here here and close the QuicSession - // with a protocol error. - session_stats_.handshake_completed_at = uv_hrtime(); - - HandleScope scope(env()->isolate()); - Context::Scope context_scope(env()->context()); - - Local servername = Undefined(env()->isolate()); - const char* hostname = - crypto::GetServerName( - this->CryptoContext()->ssl()); - if (hostname != nullptr) { - servername = - String::NewFromUtf8( - env()->isolate(), - hostname, - v8::NewStringType::kNormal).ToLocalChecked(); - } - - int err = crypto_context_->VerifyPeerIdentity( - hostname != nullptr ? - hostname : - hostname_.c_str()); - - Local argv[] = { - servername, - GetALPNProtocol(this), - crypto::GetCipherName(this->env(), this->CryptoContext()->ssl()), - crypto::GetCipherVersion(this->env(), this->CryptoContext()->ssl()), - Integer::New(env()->isolate(), max_pktlen_), - err != 0 ? - crypto::GetValidationErrorReason(env(), err) : - v8::Undefined(env()->isolate()).As(), - err != 0 ? - crypto::GetValidationErrorCode(env(), err) : - v8::Undefined(env()->isolate()).As() - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(this); - MakeCallback(env()->quic_on_session_handshake_function(), - arraysize(argv), - argv); + Listener()->OnHandshakeCompleted(); } bool QuicSession::IsHandshakeCompleted() { @@ -1347,7 +1708,7 @@ void QuicSession::PathValidation( Debug(this, "Path validation succeeded. Updating local and remote addresses"); SetLocalAddress(&path->local); - remote_address_.Update(&path->remote); + remote_address_.Update(path->remote.addr, path->remote.addrlen); IncrementStat( 1, &session_stats_, &session_stats::path_validation_success_count); @@ -1362,24 +1723,10 @@ void QuicSession::PathValidation( if (LIKELY(state_[IDX_QUIC_SESSION_STATE_PATH_VALIDATED_ENABLED] == 0)) return; - // This is a fairly expensive operation because both the local and - // remote addresses have to converted into JavaScript objects. We - // only do this if a pathValidation handler is registered. - HandleScope scope(env()->isolate()); - Local context = env()->context(); - Context::Scope context_scope(context); - Local argv[] = { - Integer::New(env()->isolate(), res), - AddressToJS(env(), reinterpret_cast(path->local.addr)), - AddressToJS(env(), reinterpret_cast(path->remote.addr)) - }; - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(this); - MakeCallback( - env()->quic_on_session_path_validation_function(), - arraysize(argv), - argv); + Listener()->OnPathValidation( + res, + reinterpret_cast(path->local.addr), + reinterpret_cast(path->remote.addr)); } // Calling Ping will trigger the ngtcp2_conn to serialize any @@ -1465,7 +1812,7 @@ bool QuicSession::Receive( // It's possible for the remote address to change from one // packet to the next so we have to look at the addr on // every packet. - remote_address_.Copy(addr); + remote_address_ = addr; QuicPath path(Socket()->GetLocalAddress(), &remote_address_); { @@ -1473,6 +1820,9 @@ bool QuicSession::Receive( // and HandleScope are both exited before continuing on with the // function. This allows any nextTicks and queued tasks to be processed // before we continue. + OnScopeLeave update_stats([&](){ + UpdateDataStats(); + }); Debug(this, "Processing received packet"); HandleScope handle_scope(env()->isolate()); InternalCallbackScope callback_scope(this); @@ -1517,6 +1867,7 @@ bool QuicSession::Receive( // If processing the packet puts us into draining period, there's // absolutely nothing left for us to do except silently close // and destroy this QuicSession. + GetConnectionCloseInfo(); SilentClose(); return true; } else { @@ -1542,8 +1893,16 @@ bool QuicSession::ReceiveClientInitial(const ngtcp2_cid* dcid) { initial_connection_close_ == NGTCP2_NO_ERROR; } +// Captures the error code and family information from a received +// connection close frame. +void QuicSession::GetConnectionCloseInfo() { + ngtcp2_connection_close_error_code close_code; + ngtcp2_conn_get_connection_close_error_code(Connection(), &close_code); + SetLastError(QuicError(close_code)); +} + bool QuicSession::ReceivePacket( - QuicPath* path, + ngtcp2_path* path, const uint8_t* data, ssize_t nread) { DCHECK(!Ngtcp2CallbackScope::InNgtcp2CallbackScope(this)); @@ -1555,7 +1914,7 @@ bool QuicSession::ReceivePacket( uint64_t now = uv_hrtime(); session_stats_.session_received_at = now; - int err = ngtcp2_conn_read_pkt(Connection(), **path, data, nread, now); + int err = ngtcp2_conn_read_pkt(Connection(), path, data, nread, now); if (err < 0) { switch (err) { case NGTCP2_ERR_DRAINING: @@ -1764,7 +2123,7 @@ bool QuicSession::SelectPreferredAddress( SocketAddress* local_address = Socket()->GetLocalAddress(); uv_getaddrinfo_t req; - if (!SocketAddress::ResolvePreferredAddress( + if (!ResolvePreferredAddress( env(), local_address->GetFamily(), paddr, &req)) { return false; @@ -1787,10 +2146,10 @@ bool QuicSession::SelectPreferredAddress( bool QuicSession::SendPacket( MallocedBuffer buf, - QuicPathStorage* path) { + ngtcp2_path_storage* path) { sendbuf_.Push(std::move(buf)); // TODO(@jasnell): Update the local endpoint also? - remote_address_.Update(&path->path.remote); + remote_address_.Update(path->path.remote.addr, path->path.remote.addrlen); return SendPacket("stream data"); } @@ -1914,45 +2273,11 @@ int QuicSession::SetRemoteTransportParams(ngtcp2_transport_params* params) { int QuicSession::SetSession(SSL_SESSION* session) { CHECK(!IsServer()); CHECK(!IsFlagSet(QUICSESSION_FLAG_DESTROYED)); + int size = i2d_SSL_SESSION(session, nullptr); if (size > SecureContext::kMaxSessionSize) return 0; - - HandleScope scope(env()->isolate()); - Context::Scope context_scope(env()->context()); - - unsigned int session_id_length; - const unsigned char* session_id_data = - SSL_SESSION_get_id(session, &session_id_length); - - Local argv[] = { - Buffer::Copy( - env(), - reinterpret_cast(session_id_data), - session_id_length).ToLocalChecked(), - v8::Undefined(env()->isolate()), - v8::Undefined(env()->isolate()) - }; - - AllocatedBuffer session_ticket = env()->AllocateManaged(size); - unsigned char* session_data = - reinterpret_cast(session_ticket.data()); - memset(session_data, 0, size); - i2d_SSL_SESSION(session, &session_data); - if (!session_ticket.empty()) - argv[1] = session_ticket.ToBuffer().ToLocalChecked(); - - if (IsFlagSet(QUICSESSION_FLAG_HAS_TRANSPORT_PARAMS)) { - argv[2] = Buffer::Copy( - env(), - reinterpret_cast(&transport_params_), - sizeof(transport_params_)).ToLocalChecked(); - } - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(this); - MakeCallback(env()->quic_on_session_ticket_function(), arraysize(argv), argv); - + Listener()->OnSessionTicket(size, session); return 1; } @@ -1985,13 +2310,14 @@ bool QuicSession::SetSocket(QuicSocket* socket, bool nat_rebinding) { // Step 4: Update ngtcp2 SocketAddress* local_address = socket->GetLocalAddress(); if (nat_rebinding) { - ngtcp2_addr addr = local_address->ToAddr(); + ngtcp2_addr addr; + ToNgtcp2Addr(local_address, &addr); ngtcp2_conn_set_local_addr(Connection(), &addr); } else { QuicPath path(local_address, &remote_address_); if (ngtcp2_conn_initiate_migration( Connection(), - *path, + &path, uv_hrtime()) != 0) { return false; } @@ -2040,24 +2366,11 @@ void QuicSession::SilentClose(bool stateless_reset) { QuicError last_error = GetLastError(); Debug(this, "Silent close with %s code %" PRIu64 " (stateless reset? %s)", - ErrorFamilyName(last_error.family), + last_error.GetFamilyName(), last_error.code, stateless_reset ? "yes" : "no"); - HandleScope scope(env()->isolate()); - Context::Scope context_scope(env()->context()); - - Local argv[] = { - stateless_reset ? v8::True(env()->isolate()) : v8::False(env()->isolate()), - Number::New(env()->isolate(), static_cast(last_error.code)), - Integer::New(env()->isolate(), last_error.family) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(this); - MakeCallback( - env()->quic_on_session_silent_close_function(), arraysize(argv), argv); + Listener()->OnSessionSilentClose(stateless_reset, last_error); } bool QuicSession::StartClosingPeriod() { @@ -2086,10 +2399,12 @@ bool QuicSession::StartClosingPeriod() { error.code, uv_hrtime()); if (nwrite < 0) { - if (nwrite == NGTCP2_ERR_PKT_NUM_EXHAUSTED) + if (nwrite == NGTCP2_ERR_PKT_NUM_EXHAUSTED) { + SetLastError(QUIC_ERROR_SESSION, NGTCP2_ERR_PKT_NUM_EXHAUSTED); SilentClose(); - else + } else { SetLastError(QUIC_ERROR_SESSION, static_cast(nwrite)); + } return false; } conn_closebuf_.Realloc(nwrite); @@ -2190,29 +2505,7 @@ void QuicSession::VersionNegotiation( CHECK(!IsServer()); if (IsFlagSet(QUICSESSION_FLAG_DESTROYED)) return; - HandleScope scope(env()->isolate()); - Local context = env()->context(); - Context::Scope context_scope(context); - - std::vector> versions; - for (size_t n = 0; n < nsv; n++) - versions.push_back(Integer::New(env()->isolate(), sv[n])); - - Local supported_version = - Integer::New(env()->isolate(), NGTCP2_PROTO_VER); - - Local argv[] = { - Integer::New(env()->isolate(), NGTCP2_PROTO_VER), - Array::New(env()->isolate(), versions.data(), nsv), - Array::New(env()->isolate(), &supported_version, 1) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr ptr(this); - MakeCallback( - env()->quic_on_session_version_negotiation_function(), - arraysize(argv), argv); + Listener()->OnVersionNegotiation(NGTCP2_PROTO_VER, sv, nsv); } // Write any packets current pending for the ngtcp2 connection based on @@ -2273,8 +2566,9 @@ bool QuicSession::WritePackets(const char* diagnostic_label) { } data.Realloc(nwrite); - remote_address_.Update(&path.path.remote); + remote_address_.Update(path.path.remote.addr, path.path.remote.addrlen); sendbuf_.Push(std::move(data)); + UpdateDataStats(); if (!SendPacket(diagnostic_label)) return false; } @@ -2399,8 +2693,8 @@ void QuicSession::InitServer( ExtendMaxStreamsBidi(DEFAULT_MAX_STREAMS_BIDI); ExtendMaxStreamsUni(DEFAULT_MAX_STREAMS_UNI); - remote_address_.Copy(addr); - max_pktlen_ = SocketAddress::GetMaxPktLen(addr); + remote_address_ = addr; + max_pktlen_ = GetMaxPktLen(addr); config->SetOriginalConnectionID(ocid); config->GenerateStatelessResetToken(); @@ -2421,7 +2715,7 @@ void QuicSession::InitServer( &conn, dcid, &scid_, - *path, + &path, version, &callbacks[crypto_context_->Side()], **config, @@ -2431,6 +2725,7 @@ void QuicSession::InitServer( connection_.reset(conn); InitializeTLS(this); + UpdateDataStats(); UpdateIdleTimer(); } @@ -2465,6 +2760,16 @@ void QuicSession::UpdateRecoveryStats() { recovery_stats_.smoothed_rtt = static_cast(stat->smoothed_rtt); } +void QuicSession::UpdateDataStats() { + state_[IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT] = + static_cast(ngtcp2_conn_get_max_data_left(Connection())); + size_t bytes_in_flight = ngtcp2_conn_get_bytes_in_flight(Connection()); + state_[IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT] = + static_cast(bytes_in_flight); + if (bytes_in_flight > session_stats_.max_bytes_in_flight) + session_stats_.max_bytes_in_flight = bytes_in_flight; +} + BaseObjectPtr QuicSession::CreateClient( QuicSocket* socket, const struct sockaddr* addr, @@ -2511,8 +2816,8 @@ bool QuicSession::InitClient( QlogMode qlog) { CHECK_NULL(connection_); - remote_address_.Copy(addr); - max_pktlen_ = SocketAddress::GetMaxPktLen(addr); + remote_address_ = addr; + max_pktlen_ = GetMaxPktLen(addr); QuicSessionConfig config(env()); max_crypto_buffer_ = config.GetMaxCryptoBuffer(); @@ -2545,7 +2850,7 @@ bool QuicSession::InitClient( &conn, &dcid, &scid_, - *path, + &path, NGTCP2_PROTO_VER, &callbacks[crypto_context_->Side()], *config, @@ -2580,6 +2885,7 @@ bool QuicSession::InitClient( } UpdateIdleTimer(); + UpdateDataStats(); return true; } @@ -2917,20 +3223,7 @@ int QuicSession::OnStatelessReset( void QuicSession::OnQlogWrite(void* user_data, const void* data, size_t len) { QuicSession* session = static_cast(user_data); - - HandleScope handle_scope(session->env()->isolate()); - Context::Scope context_scope(session->env()->context()); - - Local str = - String::NewFromOneByte(session->env()->isolate(), - static_cast(data), - v8::NewStringType::kNormal, - len).ToLocalChecked(); - - session->MakeCallback( - session->env()->quic_on_session_qlog_function(), - 1, - &str); + session->Listener()->OnQLog(reinterpret_cast(data), len); } const ngtcp2_conn_callbacks QuicSession::callbacks[2] = { @@ -3021,10 +3314,7 @@ void QuicSessionClose(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); QuicSession* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - int family = QUIC_ERROR_SESSION; - uint64_t code = ExtractErrorCode(env, args[0]); - if (!args[1]->Int32Value(env->context()).To(&family)) return; - session->SetLastError(static_cast(family), code); + session->SetLastError(QuicError(env, args[0], args[1])); session->SendConnectionClose(); } @@ -3044,11 +3334,7 @@ void QuicSessionDestroy(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); QuicSession* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - int code = 0; - int family = QUIC_ERROR_SESSION; - if (!args[0]->Int32Value(env->context()).To(&code)) return; - if (!args[1]->Int32Value(env->context()).To(&family)) return; - session->SetLastError(static_cast(family), code); + session->SetLastError(QuicError(env, args[0], args[1])); session->Destroy(); } @@ -3079,7 +3365,7 @@ void QuicSessionGetRemoteAddress( Environment* env = session->env(); CHECK(args[0]->IsObject()); args.GetReturnValue().Set( - AddressToJS(env, **session->GetRemoteAddress(), args[0].As())); + session->GetRemoteAddress()->ToJS(env, args[0].As())); } void QuicSessionGetCertificate( @@ -3137,9 +3423,8 @@ void NewQuicClientSession(const FunctionCallbackInfo& args) { std::string hostname(*servername); sockaddr_storage addr; - int err = SocketAddress::ToSockAddr(family, *address, port, &addr); - if (err != 0) - return args.GetReturnValue().Set(err); + if (SocketAddress::ToSockAddr(family, *address, port, &addr) == nullptr) + return args.GetReturnValue().Set(-1); int select_preferred_address_policy = QUIC_PREFERRED_ADDRESS_IGNORE; if (!args[10]->Int32Value(env->context()) diff --git a/src/node_quic_session.h b/src/node_quic_session.h index ea23918331..8efc3ec152 100644 --- a/src/node_quic_session.h +++ b/src/node_quic_session.h @@ -11,8 +11,10 @@ #include "node.h" #include "node_crypto.h" #include "node_mem.h" +#include "node_quic_buffer.h" #include "node_quic_crypto.h" #include "node_quic_util.h" +#include "node_sockaddr.h" #include "v8.h" #include "uv.h" @@ -30,6 +32,7 @@ using ConnectionPointer = DeleteFnPtr; class QuicSocket; class QuicStream; +class QuicHeader; enum class QlogMode { kDisabled, @@ -159,11 +162,128 @@ enum QuicSessionState : int { IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI, IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI, + // Communicates the current maxinum number of bytes that + // the local endpoint can send in this connection + // (updated immediately after processing sent/received packets) + IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT, + + // Communicates the current total number of bytes in flight + IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT, + // Just the number of session state enums for use when // creating the AliasedBuffer. IDX_QUIC_SESSION_STATE_COUNT }; +enum QuicSessionStatsIdx : int { + IDX_QUIC_SESSION_STATS_CREATED_AT, + IDX_QUIC_SESSION_STATS_HANDSHAKE_START_AT, + IDX_QUIC_SESSION_STATS_HANDSHAKE_SEND_AT, + IDX_QUIC_SESSION_STATS_HANDSHAKE_CONTINUE_AT, + IDX_QUIC_SESSION_STATS_HANDSHAKE_COMPLETED_AT, + IDX_QUIC_SESSION_STATS_HANDSHAKE_ACKED_AT, + IDX_QUIC_SESSION_STATS_SENT_AT, + IDX_QUIC_SESSION_STATS_RECEIVED_AT, + IDX_QUIC_SESSION_STATS_CLOSING_AT, + IDX_QUIC_SESSION_STATS_BYTES_RECEIVED, + IDX_QUIC_SESSION_STATS_BYTES_SENT, + IDX_QUIC_SESSION_STATS_BIDI_STREAM_COUNT, + IDX_QUIC_SESSION_STATS_UNI_STREAM_COUNT, + IDX_QUIC_SESSION_STATS_STREAMS_IN_COUNT, + IDX_QUIC_SESSION_STATS_STREAMS_OUT_COUNT, + IDX_QUIC_SESSION_STATS_KEYUPDATE_COUNT, + IDX_QUIC_SESSION_STATS_RETRY_COUNT, + IDX_QUIC_SESSION_STATS_LOSS_RETRANSMIT_COUNT, + IDX_QUIC_SESSION_STATS_ACK_DELAY_RETRANSMIT_COUNT, + IDX_QUIC_SESSION_STATS_PATH_VALIDATION_SUCCESS_COUNT, + IDX_QUIC_SESSION_STATS_PATH_VALIDATION_FAILURE_COUNT, + IDX_QUIC_SESSION_STATS_MAX_BYTES_IN_FLIGHT +}; + +class QuicSessionListener { + public: + virtual ~QuicSessionListener(); + + virtual void OnKeylog(const char* str, size_t size); + virtual void OnClientHello( + const char* alpn, + const char* server_name); + virtual void OnCert(const char* server_name); + virtual void OnOCSP(const std::string& ocsp); + virtual void OnStreamHeaders( + int64_t stream_id, + int kind, + const std::vector>& headers); + virtual void OnStreamClose( + int64_t stream_id, + uint64_t app_error_code); + virtual void OnStreamReset( + int64_t stream_id, + uint64_t final_size, + uint64_t app_error_code); + virtual void OnSessionDestroyed(); + virtual void OnSessionClose(QuicError error); + virtual void OnStreamReady(BaseObjectPtr stream); + virtual void OnHandshakeCompleted(); + virtual void OnPathValidation( + ngtcp2_path_validation_result res, + const sockaddr* local, + const sockaddr* remote); + virtual void OnSessionTicket(int size, SSL_SESSION* session); + virtual void OnSessionSilentClose(bool stateless_reset, QuicError error); + virtual void OnVersionNegotiation( + uint32_t supported_version, + const uint32_t* versions, + size_t vcnt); + virtual void OnQLog(const uint8_t* data, size_t len); + + QuicSession* Session() { return session_; } + + private: + QuicSession* session_ = nullptr; + QuicSessionListener* previous_listener_ = nullptr; + friend class QuicSession; +}; + +class JSQuicSessionListener : public QuicSessionListener { + public: + void OnKeylog(const char* str, size_t size) override; + void OnClientHello( + const char* alpn, + const char* server_name) override; + void OnCert(const char* server_name) override; + void OnOCSP(const std::string& ocsp) override; + void OnStreamHeaders( + int64_t stream_id, + int kind, + const std::vector>& headers) override; + void OnStreamClose( + int64_t stream_id, + uint64_t app_error_code) override; + void OnStreamReset( + int64_t stream_id, + uint64_t final_size, + uint64_t app_error_code) override; + void OnSessionDestroyed() override; + void OnSessionClose(QuicError error) override; + void OnStreamReady(BaseObjectPtr stream) override; + void OnHandshakeCompleted() override; + void OnPathValidation( + ngtcp2_path_validation_result res, + const sockaddr* local, + const sockaddr* remote) override; + void OnSessionTicket(int size, SSL_SESSION* session) override; + void OnSessionSilentClose(bool stateless_reset, QuicError error) override; + void OnVersionNegotiation( + uint32_t supported_version, + const uint32_t* versions, + size_t vcnt) override; + void OnQLog(const uint8_t* data, size_t len) override; + + private: + friend class QuicSession; +}; + // The QuicCryptoContext class encapsulates all of the crypto/TLS // handshake details on behalf of a QuicSession. class QuicCryptoContext : public MemoryRetainer { @@ -359,6 +479,10 @@ class QuicApplication : public MemoryRetainer { virtual void ExtendMaxStreamData(int64_t stream_id, uint64_t max_data) {} virtual bool SendPendingData() = 0; virtual bool SendStreamData(QuicStream* stream) = 0; + virtual void StreamHeaders( + int64_t stream_id, + int kind, + const std::vector>& headers); virtual void StreamClose( int64_t stream_id, uint64_t app_error_code); @@ -519,9 +643,11 @@ class QuicSession : public AsyncWrap, ssize_t nread); QuicCryptoContext* CryptoContext() { return crypto_context_.get(); } + QuicSessionListener* Listener() { return listener_; } QuicStream* CreateStream(int64_t id); QuicStream* FindStream(int64_t id); + inline bool HasStream(int64_t id); inline QuicError GetLastError() const; @@ -659,17 +785,17 @@ class QuicSession : public AsyncWrap, // Causes pending QuicStream data to be serialized and sent bool SendStreamData(QuicStream* stream); - bool SendPacket(MallocedBuffer buf, QuicPathStorage* path); + bool SendPacket(MallocedBuffer buf, ngtcp2_path_storage* path); inline uint64_t GetMaxDataLeft(); inline void SetLastError( QuicError error = { - QUIC_ERROR_SESSION, - NGTCP2_NO_ERROR + uint32_t{QUIC_ERROR_SESSION}, + uint64_t{NGTCP2_NO_ERROR} }); - inline void SetLastError(QuicErrorFamily family, uint64_t error_code); - inline void SetLastError(QuicErrorFamily family, int error_code); + inline void SetLastError(int32_t family, uint64_t error_code); + inline void SetLastError(int32_t family, int error_code); inline uint64_t GetMaxLocalStreamsUni(); @@ -782,6 +908,9 @@ class QuicSession : public AsyncWrap, // peer. void SilentClose(bool stateless_reset = false); + void PushListener(QuicSessionListener* listener); + void RemoveListener(QuicSessionListener* listener); + // Tracks whether or not we are currently within an ngtcp2 callback // function. Certain ngtcp2 APIs are not supposed to be called when // within a callback. We use this as a gate to check. @@ -842,12 +971,13 @@ class QuicSession : public AsyncWrap, void ExtendMaxStreamsRemoteUni(uint64_t max_streams); void ExtendMaxStreamsRemoteBidi(uint64_t max_streams); int GetNewConnectionID(ngtcp2_cid* cid, uint8_t* token, size_t cidlen); + void GetConnectionCloseInfo(); void HandshakeCompleted(); void PathValidation( const ngtcp2_path* path, ngtcp2_path_validation_result res); bool ReceiveClientInitial(const ngtcp2_cid* dcid); - bool ReceivePacket(QuicPath* path, const uint8_t* data, ssize_t nread); + bool ReceivePacket(ngtcp2_path* path, const uint8_t* data, ssize_t nread); bool ReceiveRetry(); void RemoveConnectionID(const ngtcp2_cid* cid); void ScheduleRetransmit(); @@ -864,6 +994,7 @@ class QuicSession : public AsyncWrap, uint64_t app_error_code); bool WritePackets(const char* diagnostic_label = nullptr); void UpdateRecoveryStats(); + void UpdateDataStats(); void VersionNegotiation( const ngtcp2_pkt_hd* hd, @@ -1107,7 +1238,7 @@ class QuicSession : public AsyncWrap, uint64_t error_code, ngtcp2_tstamp ts); - static inline ngtcp2_close_fn SelectCloseFn(QuicErrorFamily family) { + static inline ngtcp2_close_fn SelectCloseFn(uint32_t family) { if (family == QUIC_ERROR_APPLICATION) return ngtcp2_conn_write_application_close; return ngtcp2_conn_write_connection_close; @@ -1122,7 +1253,10 @@ class QuicSession : public AsyncWrap, BaseObjectWeakPtr socket_; std::string alpn_; std::string hostname_; - QuicError last_error_ = { QUIC_ERROR_SESSION, NGTCP2_NO_ERROR }; + QuicError last_error_ = { + uint32_t{QUIC_ERROR_SESSION}, + uint64_t{NGTCP2_NO_ERROR} + }; ConnectionPointer connection_; SocketAddress remote_address_; uint32_t flags_ = 0; @@ -1133,6 +1267,9 @@ class QuicSession : public AsyncWrap, size_t connection_close_attempts_ = 0; size_t connection_close_limit_ = 1; + QuicSessionListener* listener_; + JSQuicSessionListener default_listener_; + TimerPointer idle_; TimerPointer retransmit_; @@ -1206,6 +1343,8 @@ class QuicSession : public AsyncWrap, uint64_t path_validation_success_count; // The total number of failed path validations uint64_t path_validation_failure_count; + // The max number of in flight bytes recorded + uint64_t max_bytes_in_flight; }; session_stats session_stats_{}; @@ -1233,6 +1372,8 @@ class QuicSession : public AsyncWrap, static const ngtcp2_conn_callbacks callbacks[2]; friend class QuicCryptoContext; + friend class QuicSessionListener; + friend class JSQuicSessionListener; }; } // namespace quic diff --git a/src/node_quic_socket.cc b/src/node_quic_socket.cc index 28477912ba..10d3275ee0 100644 --- a/src/node_quic_socket.cc +++ b/src/node_quic_socket.cc @@ -10,7 +10,8 @@ #include "node_quic_crypto.h" #include "node_quic_session-inl.h" #include "node_quic_socket.h" -#include "node_quic_util.h" +#include "node_quic_util-inl.h" +#include "node_sockaddr-inl.h" #include "req_wrap-inl.h" #include "util.h" #include "uv.h" @@ -44,7 +45,7 @@ namespace { inline uint32_t GenerateReservedVersion( const sockaddr* addr, uint32_t version) { - socklen_t addrlen = SocketAddress::GetAddressLen(addr); + socklen_t addrlen = SocketAddress::GetLength(addr); uint32_t h = 0x811C9DC5u; const uint8_t* p = reinterpret_cast(addr); const uint8_t* ep = p + addrlen; @@ -65,6 +66,83 @@ inline uint32_t GenerateReservedVersion( } } // namespace +QuicSocketListener::~QuicSocketListener() { + if (socket_ != nullptr) + socket_->RemoveListener(this); +} + +void QuicSocketListener::OnError(ssize_t code) { + if (previous_listener_ != nullptr) + previous_listener_->OnError(code); +} + +void QuicSocketListener::OnError(int code) { + if (previous_listener_ != nullptr) + previous_listener_->OnError(code); +} + +void QuicSocketListener::OnSessionReady(BaseObjectPtr session) { + if (previous_listener_ != nullptr) + previous_listener_->OnSessionReady(session); +} + +void QuicSocketListener::OnServerBusy(bool busy) { + if (previous_listener_ != nullptr) + previous_listener_->OnServerBusy(busy); +} + +void QuicSocketListener::OnDone() { + if (previous_listener_ != nullptr) + previous_listener_->OnDone(); +} + +void QuicSocketListener::OnDestroy() { + if (previous_listener_ != nullptr) + previous_listener_->OnDestroy(); +} + +void JSQuicSocketListener::OnError(ssize_t code) { + Environment* env = Socket()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + Local arg = Number::New(env->isolate(), static_cast(code)); + Socket()->MakeCallback(env->quic_on_socket_error_function(), 1, &arg); +} + +void JSQuicSocketListener::OnError(int code) { + Environment* env = Socket()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + Local arg = Integer::New(env->isolate(), code); + Socket()->MakeCallback(env->quic_on_socket_error_function(), 1, &arg); +} + +void JSQuicSocketListener::OnSessionReady(BaseObjectPtr session) { + Environment* env = Socket()->env(); + Local arg = session->object(); + Context::Scope context_scope(env->context()); + Socket()->MakeCallback(env->quic_on_session_ready_function(), 1, &arg); +} + +void JSQuicSocketListener::OnServerBusy(bool busy) { + Environment* env = Socket()->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + Local arg = Boolean::New(env->isolate(), busy); + Socket()->MakeCallback(env->quic_on_socket_server_busy_function(), 1, &arg); +} + +void JSQuicSocketListener::OnDone() { + Environment* env = Socket()->env(); + HandleScope scope(env->isolate()); + Context::Scope context_scope(env->context()); + Socket()->MakeCallback(env->ondone_string(), 0, nullptr); +} + +void JSQuicSocketListener::OnDestroy() { + // Do nothing here. +} + QuicSocket::QuicSocket( Environment* env, Local wrap, @@ -85,6 +163,7 @@ QuicSocket::QuicSocket( sizeof(socket_stats_) / sizeof(uint64_t), reinterpret_cast(&socket_stats_)) { MakeWeak(); + PushListener(&default_listener_); udp_ = static_cast( udp_base_wrap->GetAlignedPointerFromInternalField( @@ -129,6 +208,11 @@ QuicSocket::~QuicSocket() { socket_stats_.packets_ignored, socket_stats_.server_sessions, socket_stats_.client_sessions); + QuicSocketListener* listener = listener_; + listener_->OnDestroy(); + // Remove the listener if it didn't remove itself already. + if (listener == listener_) + RemoveListener(listener_); } void QuicSocket::MemoryInfo(MemoryTracker* tracker) const { @@ -160,14 +244,8 @@ void QuicSocket::AssociateCID( } void QuicSocket::OnAfterBind() { - sockaddr_storage addr_buf; - sockaddr* addr = reinterpret_cast(&addr_buf); - int addrlen = sizeof(addr_buf); - - CHECK_EQ(udp_->GetSockName(addr, &addrlen), 0); - local_address_.Copy(addr); + udp_->GetSockName(&local_address_); Debug(this, "Socket bound"); - socket_stats_.bound_at = uv_hrtime(); } @@ -209,7 +287,7 @@ void QuicSocket::StopListening() { void QuicSocket::WaitForPendingCallbacks() { if (!HasPendingCallbacks()) { Debug(this, "No pending callbacks, calling ondone immediately"); - MakeCallback(env()->ondone_string(), 0, nullptr); + listener_->OnDone(); return; } SetFlag(QUICSOCKET_FLAGS_WAITING_FOR_CALLBACKS); @@ -232,11 +310,7 @@ void QuicSocket::OnRecv( if (nread < 0) { Debug(this, "Reading data from UDP socket failed. Error %d", nread); - Environment* env = this->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - Local arg = Number::New(env->isolate(), static_cast(nread)); - MakeCallback(env->quic_on_socket_error_function(), 1, &arg); + listener_->OnError(nread); return; } @@ -381,11 +455,7 @@ void QuicSocket::RemoveSession(const QuicCID& cid, const sockaddr* addr) { } void QuicSocket::ReportSendError(int error) { - HandleScope scope(env()->isolate()); - Context::Scope context_scope(env()->context()); - Local arg = Integer::New(env()->isolate(), error); - MakeCallback(env()->quic_on_socket_error_function(), 1, &arg); - return; + listener_->OnError(error); } void QuicSocket::SendInitialConnectionClose( @@ -403,8 +473,7 @@ void QuicSocket::SendInitialConnectionClose( EntropySource(scid.data, NGTCP2_SV_SCIDLEN); scid.datalen = NGTCP2_SV_SCIDLEN; - SocketAddress remote_address; - remote_address.Copy(addr); + SocketAddress remote_address(addr); QuicPath path(GetLocalAddress(), &remote_address); ngtcp2_conn_callbacks callbacks; @@ -417,7 +486,7 @@ void QuicSocket::SendInitialConnectionClose( &conn, *dcid, &scid, - *path, + &path, version, &callbacks, &settings, @@ -429,7 +498,7 @@ void QuicSocket::SendInitialConnectionClose( ssize_t nwrite = ngtcp2_conn_write_connection_close( conn, - *path, + &path, reinterpret_cast(buf.data), NGTCP2_MAX_PKTLEN_IPV6, error_code, @@ -643,13 +712,8 @@ BaseObjectPtr QuicSocket::AcceptInitialPacket( server_options_, initial_connection_close, qlog_); - Local arg = session->object(); - MakeCallback(env()->quic_on_session_ready_function(), 1, &arg); - // The above MakeCallback will notify the JavaScript side that a new - // server QuicSession has been created in an event emitted on nextTick. - // The user may destroy() the server QuicSession in that event but that - // won't impact the code here. + listener_->OnSessionReady(session); return session; } @@ -678,11 +742,7 @@ size_t QuicSocket::GetCurrentSocketAddressCounter(const sockaddr* addr) { void QuicSocket::SetServerBusy(bool on) { Debug(this, "Turning Server Busy Response %s", on ? "on" : "off"); SetFlag(QUICSOCKET_FLAGS_SERVER_BUSY, on); - - HandleScope handle_scope(env()->isolate()); - Context::Scope context_scope(env()->context()); - Local arg = Boolean::New(env()->isolate(), on); - MakeCallback(env()->quic_on_socket_server_busy_function(), 1, &arg); + listener_->OnServerBusy(on); } QuicSocket::SendWrap::SendWrap( @@ -723,11 +783,9 @@ int QuicSocket::SendPacket( if (buffer->Length() == 0 || buffer->RemainingLength() == 0) return 0; - { - char host[INET6_ADDRSTRLEN]; - SocketAddress::GetAddress(dest, host, sizeof(host)); - Debug(this, "Sending to %s at port %d", host, SocketAddress::GetPort(dest)); - } + Debug(this, "Sending to %s at port %d", + SocketAddress::GetAddress(dest).c_str(), + SocketAddress::GetPort(dest)); // Remaining Length should never be zero at this point CHECK_GT(buffer->RemainingLength(), 0); @@ -838,9 +896,7 @@ void QuicSocket::OnSend( if (!HasPendingCallbacks() && IsFlagSet(QUICSOCKET_FLAGS_WAITING_FOR_CALLBACKS)) { - HandleScope handle_scope(env()->isolate()); - Context::Scope context_scope(env()->context()); - MakeCallback(env()->ondone_string(), 0, nullptr); + listener_->OnDone(); } } @@ -877,6 +933,39 @@ void QuicSocket::DecreaseAllocatedSize(size_t size) { current_ngtcp2_memory_ -= size; } +void QuicSocket::PushListener(QuicSocketListener* listener) { + CHECK_NOT_NULL(listener); + CHECK_NULL(listener->socket_); + + listener->previous_listener_ = listener_; + listener->socket_ = this; + + listener_ = listener; +} + +void QuicSocket::RemoveListener(QuicSocketListener* listener) { + CHECK_NOT_NULL(listener); + + QuicSocketListener* previous; + QuicSocketListener* current; + + for (current = listener_, previous = nullptr; + /* No loop condition because we want a crash if listener is not found */ + ; previous = current, current = current->previous_listener_) { + CHECK_NOT_NULL(current); + if (current == listener) { + if (previous != nullptr) + previous->previous_listener_ = current->previous_listener_; + else + listener_ = listener->previous_listener_; + break; + } + } + + listener->socket_ = nullptr; + listener->previous_listener_ = nullptr; +} + // JavaScript API namespace { void NewQuicSocket(const FunctionCallbackInfo& args) { @@ -961,7 +1050,7 @@ void QuicSocketListen(const FunctionCallbackInfo& args) { preferred_address_family, *preferred_address_host, preferred_address_port, - &preferred_address_storage) == 0) { + &preferred_address_storage) != nullptr) { preferred_address = reinterpret_cast(&preferred_address_storage); } diff --git a/src/node_quic_socket.h b/src/node_quic_socket.h index ddc6328be6..ad40174c3c 100644 --- a/src/node_quic_socket.h +++ b/src/node_quic_socket.h @@ -9,6 +9,7 @@ #include "ngtcp2/ngtcp2.h" #include "node_quic_session.h" #include "node_quic_util.h" +#include "node_sockaddr.h" #include "env.h" #include "udp_wrap.h" #include "v8.h" @@ -43,6 +44,40 @@ enum QuicSocketOptions : uint32_t { QUICSOCKET_OPTIONS_VALIDATE_ADDRESS_LRU = 0x2, }; +class QuicSocket; + +// This is the generic interface for objects that control QuicSocket +// instances. The default `JSQuicSocketListener` emits events to +// JavaScript +class QuicSocketListener { + public: + virtual ~QuicSocketListener(); + + virtual void OnError(ssize_t code); + virtual void OnError(int code); + virtual void OnSessionReady(BaseObjectPtr session); + virtual void OnServerBusy(bool busy); + virtual void OnDone(); + virtual void OnDestroy(); + + QuicSocket* Socket() { return socket_; } + + private: + QuicSocket* socket_ = nullptr; + QuicSocketListener* previous_listener_ = nullptr; + friend class QuicSocket; +}; + +class JSQuicSocketListener : public QuicSocketListener { + public: + void OnError(ssize_t code) override; + void OnError(int code) override; + void OnSessionReady(BaseObjectPtr session) override; + void OnServerBusy(bool busy) override; + void OnDone() override; + void OnDestroy() override; +}; + class QuicSocket : public AsyncWrap, public UDPListener, public mem::NgLibMemoryManager { @@ -125,6 +160,9 @@ class QuicSocket : public AsyncWrap, const QuicCID& scid, const sockaddr* addr); + void PushListener(QuicSocketListener* listener); + void RemoveListener(QuicSocketListener* listener); + private: static void OnAlloc( uv_handle_t* handle, @@ -236,6 +274,8 @@ class QuicSocket : public AsyncWrap, double rx_loss_ = 0.0; double tx_loss_ = 0.0; + QuicSocketListener* listener_; + JSQuicSocketListener default_listener_; SocketAddress local_address_; QuicSessionConfig server_session_config_; QlogMode qlog_ = QlogMode::kDisabled; @@ -345,6 +385,8 @@ class QuicSocket : public AsyncWrap, int Send(const sockaddr* addr, MallocedBuffer&& data, const char* diagnostic_label = "unspecified"); + + friend class QuicSocketListener; }; } // namespace quic diff --git a/src/node_quic_stream.cc b/src/node_quic_stream.cc index c67fbd4a62..4208350175 100644 --- a/src/node_quic_stream.cc +++ b/src/node_quic_stream.cc @@ -8,7 +8,8 @@ #include "node_quic_session-inl.h" #include "node_quic_stream.h" #include "node_quic_socket.h" -#include "node_quic_util.h" +#include "node_quic_util-inl.h" +#include "node_sockaddr-inl.h" #include "v8.h" #include "uv.h" @@ -387,7 +388,7 @@ void QuicStream::SetHeadersKind(QuicStreamHeadersKind kind) { headers_kind_ = kind; } -bool QuicStream::AddHeader(std::unique_ptr
header) { +bool QuicStream::AddHeader(std::unique_ptr header) { Debug(this, "Header Added"); headers_.emplace_back(std::move(header)); // TODO(@jasnell): We need to limit the maximum number of headers. @@ -400,28 +401,8 @@ void QuicStream::EndHeaders() { // Upon completion of a block of headers, convert the // vector of Header objects into an array of name+value // pairs, then call the on_stream_headers function. - std::vector> headers; - for (const auto& header : headers_) { - // name and value should never be empty here, and if - // they are, there's an actual bug so go ahead and crash - Local pair[] = { - header->GetName(Session()->Application()).ToLocalChecked(), - header->GetValue(Session()->Application()).ToLocalChecked() - }; - headers.push_back(Array::New(env()->isolate(), pair, arraysize(pair))); - } - Local argv[] = { - Array::New( - env()->isolate(), - headers.data(), - headers.size()), - Integer::New( - env()->isolate(), - headers_kind_) - }; + Session()->Application()->StreamHeaders(GetID(), headers_kind_, headers_); headers_.clear(); - BaseObjectPtr ptr(this); - MakeCallback(env()->quic_on_stream_headers_function(), arraysize(argv), argv); } void QuicStream::MemoryInfo(MemoryTracker* tracker) const { @@ -491,13 +472,11 @@ void QuicStreamReset(const FunctionCallbackInfo& args) { QuicStream* stream; ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); - uint32_t family = QUIC_ERROR_APPLICATION; - uint64_t code = ExtractErrorCode(env, args[0]); - if (!args[1]->Uint32Value(env->context()).To(&family)) return; + QuicError error(env, args[0], args[1], QUIC_ERROR_APPLICATION); stream->ResetStream( - family == QUIC_ERROR_APPLICATION ? - code : static_cast(NGTCP2_NO_ERROR)); + error.family == QUIC_ERROR_APPLICATION ? + error.code : static_cast(NGTCP2_NO_ERROR)); } // Requests transmission of a block of informational headers. Not all diff --git a/src/node_quic_stream.h b/src/node_quic_stream.h index a9e07b0e58..80df7862c3 100644 --- a/src/node_quic_stream.h +++ b/src/node_quic_stream.h @@ -37,6 +37,20 @@ enum QuicStreamHeadersKind : int { QUICSTREAM_HEADERS_KIND_TRAILING }; +// QuicHeader is a base class for implementing QUIC application +// specific headers. Each type of QUIC application may have +// different internal representations for a header name+value +// pair. QuicApplication implementations that support headers +// per stream must create a specialization of the Header class. +class QuicHeader { + public: + QuicHeader() {} + + virtual ~QuicHeader() {} + virtual v8::MaybeLocal GetName(QuicApplication* app) const = 0; + virtual v8::MaybeLocal GetValue(QuicApplication* app) const = 0; +}; + // QuicStream's are simple data flows that, fortunately, do not // require much. They may be: // @@ -105,20 +119,6 @@ enum QuicStreamHeadersKind : int { // ngtcp2 level. class QuicStream : public AsyncWrap, public StreamBase { public: - // Header is a base class for implementing QUIC application - // specific headers. Each type of QUIC application may have - // different internal representations for a header name+value - // pair. QuicApplication implementations that support headers - // per stream must create a specialization of the Header class. - class Header { - public: - Header() {} - - virtual ~Header() {} - virtual v8::MaybeLocal GetName(QuicApplication* app) const = 0; - virtual v8::MaybeLocal GetValue(QuicApplication* app) const = 0; - }; - enum QuicStreamStates : uint32_t { // QuicStream is fully open. Readable and Writable QUICSTREAM_FLAG_INITIAL = 0x0, @@ -361,7 +361,7 @@ class QuicStream : public AsyncWrap, public StreamBase { // Returns false if the header cannot be added. This will // typically only happen if a maximimum number of headers // has been reached. - bool AddHeader(std::unique_ptr
header); + bool AddHeader(std::unique_ptr header); void EndHeaders(); // Sets the kind of headers currently being processed. @@ -428,7 +428,7 @@ class QuicStream : public AsyncWrap, public StreamBase { size_t available_outbound_length_ = 0; size_t inbound_consumed_data_while_paused_ = 0; - std::vector> headers_; + std::vector> headers_; QuicStreamHeadersKind headers_kind_; struct stream_stats { diff --git a/src/node_quic_util-inl.h b/src/node_quic_util-inl.h new file mode 100644 index 0000000000..d18575e8a2 --- /dev/null +++ b/src/node_quic_util-inl.h @@ -0,0 +1,240 @@ +#ifndef SRC_NODE_QUIC_UTIL_INL_H_ +#define SRC_NODE_QUIC_UTIL_INL_H_ + +#include "node_internals.h" +#include "node_quic_util.h" +#include "env-inl.h" +#include "string_bytes.h" +#include "util-inl.h" +#include "uv.h" + +#include + +namespace node { + +namespace quic { + +std::string QuicCID::ToStr() const { + return std::string(cid_.data, cid_.data + cid_.datalen); +} + +std::string QuicCID::ToHex() const { + MaybeStackBuffer dest; + dest.AllocateSufficientStorage(cid_.datalen * 2); + dest.SetLengthAndZeroTerminate(cid_.datalen * 2); + size_t written = StringBytes::hex_encode( + reinterpret_cast(cid_.data), + cid_.datalen, + *dest, + dest.length()); + return std::string(*dest, written); +} + +ngtcp2_addr* ToNgtcp2Addr(SocketAddress* addr, ngtcp2_addr* dest) { + if (dest == nullptr) + dest = new ngtcp2_addr(); + return ngtcp2_addr_init(dest, **addr, addr->GetLength(), nullptr); +} + +size_t GetMaxPktLen(const sockaddr* addr) { + return addr->sa_family == AF_INET6 ? + NGTCP2_MAX_PKTLEN_IPV6 : + NGTCP2_MAX_PKTLEN_IPV4; +} + +bool ResolvePreferredAddress( + Environment* env, + int local_address_family, + const ngtcp2_preferred_addr* paddr, + uv_getaddrinfo_t* req) { + int af; + const uint8_t* binaddr; + uint16_t port; + constexpr uint8_t empty_addr[] = {0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + + if (local_address_family == AF_INET && + memcmp(empty_addr, paddr->ipv4_addr, sizeof(paddr->ipv4_addr)) != 0) { + af = AF_INET; + binaddr = paddr->ipv4_addr; + port = paddr->ipv4_port; + } else if (local_address_family == AF_INET6 && + memcmp(empty_addr, + paddr->ipv6_addr, + sizeof(paddr->ipv6_addr)) != 0) { + af = AF_INET6; + binaddr = paddr->ipv6_addr; + port = paddr->ipv6_port; + } else { + return false; + } + + char host[NI_MAXHOST]; + if (uv_inet_ntop(af, binaddr, host, sizeof(host)) != 0) + return false; + + addrinfo hints{}; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + hints.ai_family = af; + hints.ai_socktype = SOCK_DGRAM; + + return + uv_getaddrinfo( + env->event_loop(), + req, + nullptr, + host, + std::to_string(port).c_str(), + &hints) == 0; +} + +Timer::Timer(Environment* env, std::function fn) + : env_(env), + fn_(fn) { + uv_timer_init(env_->event_loop(), &timer_); + timer_.data = this; +} + +void Timer::Stop() { + if (stopped_) + return; + stopped_ = true; + + if (timer_.data == this) { + uv_timer_stop(&timer_); + timer_.data = nullptr; + } +} + +// If the timer is not currently active, interval must be either 0 or greater. +// If the timer is already active, interval is ignored. +void Timer::Update(uint64_t interval) { + if (stopped_) + return; + uv_timer_start(&timer_, OnTimeout, interval, interval); + uv_unref(reinterpret_cast(&timer_)); +} + +void Timer::Free(Timer* timer) { + timer->env_->CloseHandle( + reinterpret_cast(&timer->timer_), + [&](uv_handle_t* timer) { + Timer* t = ContainerOf( + &Timer::timer_, + reinterpret_cast(timer)); + delete t; + }); +} + +void Timer::OnTimeout(uv_timer_t* timer) { + Timer* t = ContainerOf(&Timer::timer_, timer); + t->fn_(); +} + +ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) { + switch (ossl_level) { + case ssl_encryption_initial: + return NGTCP2_CRYPTO_LEVEL_INITIAL; + case ssl_encryption_early_data: + return NGTCP2_CRYPTO_LEVEL_EARLY; + case ssl_encryption_handshake: + return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + case ssl_encryption_application: + return NGTCP2_CRYPTO_LEVEL_APP; + default: + UNREACHABLE(); + } +} + +const char* crypto_level_name(ngtcp2_crypto_level level) { + switch (level) { + case NGTCP2_CRYPTO_LEVEL_INITIAL: + return "initial"; + case NGTCP2_CRYPTO_LEVEL_EARLY: + return "early"; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + return "handshake"; + case NGTCP2_CRYPTO_LEVEL_APP: + return "app"; + default: + UNREACHABLE(); + } +} + +QuicError::QuicError( + int32_t family_, + uint64_t code_) : + family(family_), + code(code_) {} + +QuicError::QuicError( + int32_t family_, + int code_) : + family(family_) { + switch (family) { + case QUIC_ERROR_CRYPTO: + code_ |= NGTCP2_CRYPTO_ERROR; + // Fall-through... + case QUIC_ERROR_SESSION: + code = ngtcp2_err_infer_quic_transport_error_code(code_); + break; + case QUIC_ERROR_APPLICATION: + code = code_; + break; + default: + UNREACHABLE(); + } +} + +QuicError::QuicError(ngtcp2_connection_close_error_code ccec) : + family(QUIC_ERROR_SESSION), + code(ccec.error_code) { + switch (ccec.type) { + case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION: + family = QUIC_ERROR_APPLICATION; + break; + case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT: + if (code & NGTCP2_CRYPTO_ERROR) + family = QUIC_ERROR_CRYPTO; + break; + default: + UNREACHABLE(); + } +} + +QuicError::QuicError( + Environment* env, + v8::Local codeArg, + v8::Local familyArg, + int32_t family_) : + family(family_), + code(NGTCP2_NO_ERROR) { + if (codeArg->IsBigInt()) { + code = codeArg.As()->Int64Value(); + } else if (codeArg->IsNumber()) { + double num = 0; + CHECK(codeArg->NumberValue(env->context()).To(&num)); + code = static_cast(num); + } + if (familyArg->IsNumber()) { + CHECK(familyArg->Int32Value(env->context()).To(&family)); + } +} + +const char* QuicError::GetFamilyName() { + switch (family) { + case QUIC_ERROR_SESSION: + return "Session"; + case QUIC_ERROR_APPLICATION: + return "Application"; + case QUIC_ERROR_CRYPTO: + return "Crypto"; + default: + UNREACHABLE(); + } +} + +} // namespace quic +} // namespace node + +#endif // SRC_NODE_QUIC_UTIL_INL_H_ diff --git a/src/node_quic_util.cc b/src/node_quic_util.cc deleted file mode 100644 index 29f190cb2d..0000000000 --- a/src/node_quic_util.cc +++ /dev/null @@ -1,57 +0,0 @@ -#include "node_quic_util.h" -#include "env-inl.h" -#include "util-inl.h" -#include "uv.h" - -namespace node { - -namespace quic { - -void Timer::Free(Timer* timer) { - timer->env_->CloseHandle( - reinterpret_cast(&timer->timer_), - [&](uv_handle_t* timer) { - Timer* t = ContainerOf( - &Timer::timer_, - reinterpret_cast(timer)); - delete t; - }); -} - -void Timer::OnTimeout(uv_timer_t* timer) { - Timer* t = ContainerOf(&Timer::timer_, timer); - t->fn_(); -} - -ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) { - switch (ossl_level) { - case ssl_encryption_initial: - return NGTCP2_CRYPTO_LEVEL_INITIAL; - case ssl_encryption_early_data: - return NGTCP2_CRYPTO_LEVEL_EARLY; - case ssl_encryption_handshake: - return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; - case ssl_encryption_application: - return NGTCP2_CRYPTO_LEVEL_APP; - default: - UNREACHABLE(); - } -} - -const char* crypto_level_name(ngtcp2_crypto_level level) { - switch (level) { - case NGTCP2_CRYPTO_LEVEL_INITIAL: - return "initial"; - case NGTCP2_CRYPTO_LEVEL_EARLY: - return "early"; - case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: - return "handshake"; - case NGTCP2_CRYPTO_LEVEL_APP: - return "app"; - default: - UNREACHABLE(); - } -} - -} // namespace quic -} // namespace node diff --git a/src/node_quic_util.h b/src/node_quic_util.h index 2b61bd5091..a0585f56c5 100644 --- a/src/node_quic_util.h +++ b/src/node_quic_util.h @@ -4,8 +4,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "node.h" -#include "node_quic_buffer.h" -#include "string_bytes.h" +#include "node_sockaddr.h" #include "uv.h" #include "v8.h" @@ -71,259 +70,53 @@ inline void hash_combine(size_t* seed, const T& value, Args... rest) { // look at the ALPN identifier to determine exactly what it // means. Connection (Session) and Crypto errors, on the other // hand, share the same meaning regardless of the ALPN. -enum QuicErrorFamily : int { +enum QuicErrorFamily : int32_t { QUIC_ERROR_SESSION, QUIC_ERROR_CRYPTO, QUIC_ERROR_APPLICATION }; struct QuicError { - QuicErrorFamily family; + int32_t family; uint64_t code; inline QuicError( - QuicErrorFamily family_ = QUIC_ERROR_SESSION, - uint64_t code_ = NGTCP2_NO_ERROR) : - family(family_), code(code_) {} -}; - -inline QuicError InitQuicError( - QuicErrorFamily family = QUIC_ERROR_SESSION, - int code_ = NGTCP2_NO_ERROR) { - QuicError error; - error.family = family; - switch (family) { - case QUIC_ERROR_CRYPTO: - code_ |= NGTCP2_CRYPTO_ERROR; - // Fall-through... - case QUIC_ERROR_SESSION: - error.code = ngtcp2_err_infer_quic_transport_error_code(code_); - break; - case QUIC_ERROR_APPLICATION: - error.code = code_; - } - return error; -} - -inline uint64_t ExtractErrorCode(Environment* env, v8::Local arg) { - uint64_t code = NGTCP2_APP_NOERROR; - if (arg->IsBigInt()) { - code = arg.As()->Int64Value(); - } else if (arg->IsNumber()) { - double num = 0; - USE(arg->NumberValue(env->context()).To(&num)); - code = static_cast(num); - } - return code; -} - -inline const char* ErrorFamilyName(QuicErrorFamily family) { - switch (family) { - case QUIC_ERROR_SESSION: - return "Session"; - case QUIC_ERROR_APPLICATION: - return "Application"; - case QUIC_ERROR_CRYPTO: - return "Crypto"; - default: - return ""; - } -} - -class SocketAddress { - public: - // std::hash specialization for sockaddr instances (ipv4 or ipv6) used - // for tracking the number of connections per client. - struct Hash { - size_t operator()(const sockaddr* addr) const { - size_t hash = 0; - switch (addr->sa_family) { - case AF_INET: { - const sockaddr_in* ipv4 = - reinterpret_cast(addr); - hash_combine(&hash, ipv4->sin_port, ipv4->sin_addr.s_addr); - break; - } - case AF_INET6: { - const sockaddr_in6* ipv6 = - reinterpret_cast(addr); - const uint64_t* a = - reinterpret_cast(&ipv6->sin6_addr); - hash_combine(&hash, ipv6->sin6_port, a[0], a[1]); - break; - } - default: - UNREACHABLE(); - } - return hash; - } - }; - - // std::equal_to specialization for sockaddr instances (ipv4 or ipv6). - struct Compare { - bool operator()(const sockaddr* laddr, const sockaddr* raddr) const { - CHECK(laddr->sa_family == AF_INET || laddr->sa_family == AF_INET6); - return memcmp(laddr, raddr, GetAddressLen(laddr)) == 0; - } - }; - - static bool numeric_host(const char* hostname) { - return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6); - } - - static bool numeric_host(const char* hostname, int family) { - std::array dst; - return inet_pton(family, hostname, dst.data()) == 1; - } - - static size_t GetMaxPktLen(const sockaddr* addr) { - return addr->sa_family == AF_INET6 ? - NGTCP2_MAX_PKTLEN_IPV6 : - NGTCP2_MAX_PKTLEN_IPV4; - } - - static bool ResolvePreferredAddress( + int32_t family_ = QUIC_ERROR_SESSION, + int code_ = NGTCP2_NO_ERROR); + inline QuicError( + int32_t family_ = QUIC_ERROR_SESSION, + uint64_t code_ = NGTCP2_NO_ERROR); + inline QuicError(ngtcp2_connection_close_error_code code); + inline QuicError( Environment* env, - int local_address_family, - const ngtcp2_preferred_addr* paddr, - uv_getaddrinfo_t* req) { - int af; - const uint8_t* binaddr; - uint16_t port; - constexpr uint8_t empty_addr[] = {0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0}; - - if (local_address_family == AF_INET && - memcmp(empty_addr, paddr->ipv4_addr, sizeof(paddr->ipv4_addr)) != 0) { - af = AF_INET; - binaddr = paddr->ipv4_addr; - port = paddr->ipv4_port; - } else if (local_address_family == AF_INET6 && - memcmp(empty_addr, - paddr->ipv6_addr, - sizeof(paddr->ipv6_addr)) != 0) { - af = AF_INET6; - binaddr = paddr->ipv6_addr; - port = paddr->ipv6_port; - } else { - return false; - } - - char host[NI_MAXHOST]; - if (uv_inet_ntop(af, binaddr, host, sizeof(host)) != 0) - return false; - - addrinfo hints{}; - hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; - hints.ai_family = af; - hints.ai_socktype = SOCK_DGRAM; - - return - uv_getaddrinfo( - env->event_loop(), - req, - nullptr, - host, - std::to_string(port).c_str(), - &hints) == 0; - } - - static int ToSockAddr( - int32_t family, - const char* host, - uint32_t port, - sockaddr_storage* addr) { - CHECK(family == AF_INET || family == AF_INET6); - switch (family) { - case AF_INET: - return uv_ip4_addr(host, port, reinterpret_cast(addr)); - case AF_INET6: - return uv_ip6_addr(host, port, reinterpret_cast(addr)); - default: - CHECK(0 && "unexpected address family"); - } - } - - static int GetPort(const sockaddr* addr) { - return ntohs(addr->sa_family == AF_INET ? - reinterpret_cast(addr)->sin_port : - reinterpret_cast(addr)->sin6_port); - } - - static void GetAddress(const sockaddr* addr, char* host, size_t host_len) { - const void* src = addr->sa_family == AF_INET ? - static_cast( - &(reinterpret_cast(addr)->sin_addr)) : - static_cast( - &(reinterpret_cast(addr)->sin6_addr)); - uv_inet_ntop(addr->sa_family, src, host, host_len); - } - - static size_t GetAddressLen(const sockaddr* addr) { - return - addr->sa_family == AF_INET6 ? - sizeof(sockaddr_in6) : - sizeof(sockaddr_in); - } - - static size_t GetAddressLen(const sockaddr_storage* addr) { - return - addr->ss_family == AF_INET6 ? - sizeof(sockaddr_in6) : - sizeof(sockaddr_in); - } - - void Copy(SocketAddress* addr) { - Copy(**addr); - } - - void Copy(const sockaddr* source) { - memcpy(&address_, source, GetAddressLen(source)); - } - - void Update(const ngtcp2_addr* addr) { - memcpy(&address_, addr->addr, addr->addrlen); - } - - const sockaddr* operator*() const { - return reinterpret_cast(&address_); - } - - ngtcp2_addr ToAddr() { - return ngtcp2_addr{Size(), reinterpret_cast(&address_), nullptr}; - } + v8::Local codeArg, + v8::Local familyArg = v8::Local(), + int32_t family_ = QUIC_ERROR_SESSION); + inline const char* GetFamilyName(); +}; - size_t Size() { - return GetAddressLen(&address_); - } +inline size_t GetMaxPktLen(const sockaddr* addr); - int GetFamily() { return address_.ss_family; } +inline bool ResolvePreferredAddress( + Environment* env, + int local_address_family, + const ngtcp2_preferred_addr* paddr, + uv_getaddrinfo_t* req); - private: - sockaddr_storage address_; -}; +inline ngtcp2_addr* ToNgtcp2Addr(SocketAddress* addr, ngtcp2_addr* dest); -class QuicPath { - public: +struct QuicPath : public ngtcp2_path { QuicPath( - SocketAddress* local, - SocketAddress* remote) : - path_({ local->ToAddr(), remote->ToAddr() }) {} - - ngtcp2_path* operator*() { return &path_; } - - private: - ngtcp2_path path_; + SocketAddress* local, + SocketAddress* remote) { + ngtcp2_addr_init(&this->local, **local, local->GetLength(), nullptr); + ngtcp2_addr_init(&this->remote, **remote, remote->GetLength(), nullptr); + } }; -struct QuicPathStorage { +struct QuicPathStorage : public ngtcp2_path_storage { QuicPathStorage() { - path.local.addr = local_addrbuf.data(); - path.remote.addr = remote_addrbuf.data(); + ngtcp2_path_storage_zero(this); } - - ngtcp2_path path; - std::array local_addrbuf; - std::array remote_addrbuf; }; // Simple wrapper for ngtcp2_cid that handles hex encoding @@ -344,23 +137,9 @@ class QuicCID { str_ = std::string(cid_.data, cid_.data + cid_.datalen); } - std::string ToStr() const { return str_; } - - std::string ToHex() const { - if (hex_.empty() && cid_.datalen > 0) { - size_t len = cid_.datalen * 2; - MaybeStackBuffer dest; - dest.AllocateSufficientStorage(len); - dest.SetLengthAndZeroTerminate(len); - size_t written = StringBytes::hex_encode( - reinterpret_cast(cid_.data), - cid_.datalen, - *dest, - dest.length()); - hex_ = std::string(*dest, written); - } - return hex_; - } + inline std::string ToStr() const; + + inline std::string ToHex() const; const ngtcp2_cid* operator*() const { return &cid_; } @@ -400,43 +179,24 @@ void IncrementStat( // reset the timer; Stop to halt the timer. class Timer final : public MemoryRetainer { public: - explicit Timer(Environment* env, std::function fn) - : env_(env), - fn_(fn) { - uv_timer_init(env_->event_loop(), &timer_); - timer_.data = this; - } + inline explicit Timer(Environment* env, std::function fn); // Stops the timer with the side effect of the timer no longer being usable. // It will be cleaned up and the Timer object will be destroyed. - void Stop() { - if (stopped_) - return; - stopped_ = true; - - if (timer_.data == this) { - uv_timer_stop(&timer_); - timer_.data = nullptr; - } - } + inline void Stop(); // If the timer is not currently active, interval must be either 0 or greater. // If the timer is already active, interval is ignored. - void Update(uint64_t interval) { - if (stopped_) - return; - uv_timer_start(&timer_, OnTimeout, interval, interval); - uv_unref(reinterpret_cast(&timer_)); - } + inline void Update(uint64_t interval); - static void Free(Timer* timer); + static inline void Free(Timer* timer); SET_NO_MEMORY_INFO() SET_MEMORY_INFO_NAME(Timer) SET_SELF_SIZE(Timer) private: - static void OnTimeout(uv_timer_t* timer); + static inline void OnTimeout(uv_timer_t* timer); bool stopped_ = false; Environment* env_; @@ -446,8 +206,8 @@ class Timer final : public MemoryRetainer { using TimerPointer = DeleteFnPtr; -ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level); -const char* crypto_level_name(ngtcp2_crypto_level level); +inline ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level); +inline const char* crypto_level_name(ngtcp2_crypto_level level); } // namespace quic } // namespace node diff --git a/src/node_sockaddr-inl.h b/src/node_sockaddr-inl.h new file mode 100644 index 0000000000..24dd9a9e9a --- /dev/null +++ b/src/node_sockaddr-inl.h @@ -0,0 +1,239 @@ +#ifndef SRC_NODE_SOCKADDR_INL_H_ +#define SRC_NODE_SOCKADDR_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "node.h" +#include "node_internals.h" +#include "node_sockaddr.h" +#include "util-inl.h" + +#include + +namespace node { + +namespace { +// Fun hash combine trick based on a variadic template that +// I came across a while back but can't remember where. Will add an attribution +// if I can find the source. +inline void hash_combine(size_t* seed) { } + +template +inline void hash_combine(size_t* seed, const T& value, Args... rest) { + *seed ^= std::hash{}(value) + 0x9e3779b9 + (*seed << 6) + (*seed >> 2); + hash_combine(seed, rest...); +} +} // namespace + +size_t SocketAddress::Hash::operator()(const sockaddr* addr) const { + size_t hash = 0; + switch (addr->sa_family) { + case AF_INET: { + const sockaddr_in* ipv4 = + reinterpret_cast(addr); + hash_combine(&hash, ipv4->sin_port, ipv4->sin_addr.s_addr); + break; + } + case AF_INET6: { + const sockaddr_in6* ipv6 = + reinterpret_cast(addr); + const uint64_t* a = + reinterpret_cast(&ipv6->sin6_addr); + hash_combine(&hash, ipv6->sin6_port, a[0], a[1]); + break; + } + default: + UNREACHABLE(); + } + return hash; +} + +bool SocketAddress::Compare::operator()( + const sockaddr* laddr, + const sockaddr* raddr) const { + CHECK(laddr->sa_family == AF_INET || laddr->sa_family == AF_INET6); + return memcmp(laddr, raddr, GetLength(laddr)) == 0; +} + +bool SocketAddress::is_numeric_host(const char* hostname) { + return is_numeric_host(hostname, AF_INET) || + is_numeric_host(hostname, AF_INET6); +} + +bool SocketAddress::is_numeric_host(const char* hostname, int family) { + std::array dst; + return inet_pton(family, hostname, dst.data()) == 1; +} + +sockaddr_storage* SocketAddress::ToSockAddr( + int32_t family, + const char* host, + uint32_t port, + sockaddr_storage* addr) { + switch (family) { + case AF_INET: + return uv_ip4_addr( + host, + port, + reinterpret_cast(addr)) == 0 ? + addr : nullptr; + case AF_INET6: + return uv_ip6_addr( + host, + port, + reinterpret_cast(addr)) == 0 ? + addr : nullptr; + default: + UNREACHABLE(); + } +} + +int SocketAddress::GetPort(const sockaddr* addr) { + return ntohs(addr->sa_family == AF_INET ? + reinterpret_cast(addr)->sin_port : + reinterpret_cast(addr)->sin6_port); +} + +int SocketAddress::GetPort(const sockaddr_storage* addr) { + return GetPort(reinterpret_cast(addr)); +} + +std::string SocketAddress::GetAddress(const sockaddr* addr) { + char host[INET6_ADDRSTRLEN]; + const void* src = addr->sa_family == AF_INET ? + static_cast( + &(reinterpret_cast(addr)->sin_addr)) : + static_cast( + &(reinterpret_cast(addr)->sin6_addr)); + uv_inet_ntop(addr->sa_family, src, host, INET6_ADDRSTRLEN); + return std::string(host); +} + +std::string SocketAddress::GetAddress(const sockaddr_storage* addr) { + return GetAddress(reinterpret_cast(addr)); +} + +size_t SocketAddress::GetLength(const sockaddr* addr) { + return + addr->sa_family == AF_INET6 ? + sizeof(sockaddr_in6) : + sizeof(sockaddr_in); +} + +size_t SocketAddress::GetLength(const sockaddr_storage* addr) { + return GetLength(reinterpret_cast(addr)); +} + +SocketAddress::SocketAddress(const sockaddr* addr) { + memcpy(&address_, addr, GetLength(addr)); +} + +SocketAddress::SocketAddress(const SocketAddress& addr) { + memcpy(&address_, &addr.address_, addr.GetLength()); +} + +SocketAddress& SocketAddress::operator=(const sockaddr* addr) { + memcpy(&address_, addr, GetLength(addr)); + return *this; +} + +SocketAddress& SocketAddress::operator=(const SocketAddress& addr) { + memcpy(&address_, &addr.address_, addr.GetLength()); + return *this; +} + +const sockaddr* SocketAddress::operator*() const { + return reinterpret_cast(&address_); +} + +size_t SocketAddress::GetLength() const { + return GetLength(&address_); +} + +int SocketAddress::GetFamily() const { + return address_.ss_family; +} + +std::string SocketAddress::GetAddress() const { + return GetAddress(&address_); +} + +int SocketAddress::GetPort() const { + return GetPort(&address_); +} + +void SocketAddress::Update(uint8_t* data, size_t len) { + memcpy(&address_, data, len); +} + +template +SocketAddress* SocketAddress::FromUVHandle( + F fn, + T* handle, + SocketAddress* addr) { + if (addr == nullptr) + addr = new SocketAddress(); + int len = sizeof(sockaddr_storage); + fn(handle, reinterpret_cast(&addr->address_), &len); + return addr; +} + +SocketAddress* SocketAddress::FromSockName( + uv_tcp_t* handle, + SocketAddress* addr) { + return FromUVHandle(uv_tcp_getsockname, handle, addr); +} + +SocketAddress* SocketAddress::FromSockName( + uv_udp_t* handle, + SocketAddress* addr) { + return FromUVHandle(uv_udp_getsockname, handle, addr); +} + +SocketAddress* SocketAddress::FromPeerName( + uv_tcp_t* handle, + SocketAddress* addr) { + return FromUVHandle(uv_tcp_getpeername, handle, addr); +} + +SocketAddress* SocketAddress::FromPeerName( + uv_udp_t* handle, + SocketAddress* addr) { + return FromUVHandle(uv_udp_getpeername, handle, addr); +} + +SocketAddress* SocketAddress::New( + const char* host, + uint32_t port, + int32_t family, + SocketAddress* addr) { + if (addr == nullptr) + addr = new SocketAddress(); + switch (family) { + case AF_INET: + return uv_ip4_addr( + host, + port, + reinterpret_cast(&addr->address_)) == 0 ? + addr : nullptr; + case AF_INET6: + return uv_ip6_addr( + host, + port, + reinterpret_cast(&addr->address_)) == 0 ? + addr : nullptr; + default: + UNREACHABLE(); + } +} + +v8::Local SocketAddress::ToJS( + Environment* env, + v8::Local info) const { + return AddressToJS(env, this->operator*(), info); +} + +} // namespace node + +#endif // NODE_WANT_INTERNALS +#endif // SRC_NODE_SOCKADDR_INL_H_ diff --git a/src/node_sockaddr.h b/src/node_sockaddr.h new file mode 100644 index 0000000000..1ac5cbbdd4 --- /dev/null +++ b/src/node_sockaddr.h @@ -0,0 +1,109 @@ +#ifndef SRC_NODE_SOCKADDR_H_ +#define SRC_NODE_SOCKADDR_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "env.h" +#include "node.h" +#include "uv.h" +#include "v8.h" + +#include + +namespace node { + +class SocketAddress { + public: + struct Hash { + inline size_t operator()(const sockaddr* addr) const; + }; + + struct Compare { + inline bool operator()(const sockaddr* laddr, const sockaddr* raddr) const; + }; + + inline static bool is_numeric_host(const char* hostname); + + inline static bool is_numeric_host(const char* hostname, int family); + + inline static sockaddr_storage* ToSockAddr( + int32_t family, + const char* host, + uint32_t port, + sockaddr_storage* addr); + + inline static int GetPort(const sockaddr* addr); + + inline static int GetPort(const sockaddr_storage* addr); + + inline static std::string GetAddress(const sockaddr* addr); + + inline static std::string GetAddress(const sockaddr_storage* addr); + + inline static size_t GetLength(const sockaddr* addr); + + inline static size_t GetLength(const sockaddr_storage* addr); + + SocketAddress() {} + + inline explicit SocketAddress(const sockaddr* addr); + + inline explicit SocketAddress(const SocketAddress& addr); + + inline SocketAddress& operator=(const sockaddr* other); + + inline SocketAddress& operator=(const SocketAddress& other); + + inline const sockaddr* operator*() const; + + inline size_t GetLength() const; + + inline int GetFamily() const; + + inline std::string GetAddress() const; + + inline int GetPort() const; + + inline void Update(uint8_t* data, size_t len); + + inline static SocketAddress* FromSockName( + uv_tcp_t* handle, + SocketAddress* addr = nullptr); + + inline static SocketAddress* FromSockName( + uv_udp_t* handle, + SocketAddress* addr = nullptr); + + inline static SocketAddress* FromPeerName( + uv_tcp_t* handle, + SocketAddress* addr = nullptr); + + inline static SocketAddress* FromPeerName( + uv_udp_t* handle, + SocketAddress* addr = nullptr); + + inline static SocketAddress* New( + const char* host, + uint32_t port, + int32_t family = AF_INET, + SocketAddress* addr = nullptr); + + inline v8::Local ToJS( + Environment* env, + v8::Local obj = v8::Local()) const; + + private: + template + static SocketAddress* FromUVHandle( + F fn, + T* handle, + SocketAddress* addr = nullptr); + + sockaddr_storage address_; +}; + +} // namespace node + +#endif // NOE_WANT_INTERNALS + +#endif // SRC_NODE_SOCKADDR_H_ diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index f76e277dec..853e3fd71d 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -22,6 +22,7 @@ #include "udp_wrap.h" #include "env-inl.h" #include "node_buffer.h" +#include "node_sockaddr-inl.h" #include "handle_wrap.h" #include "req_wrap-inl.h" #include "util-inl.h" @@ -592,6 +593,14 @@ int UDPWrap::GetSockName(sockaddr* name, int* namelen) { return uv_udp_getsockname(&handle_, name, namelen); } +SocketAddress* UDPWrap::GetPeerName(SocketAddress* addr) { + return SocketAddress::FromPeerName(&handle_, addr); +} + +SocketAddress* UDPWrap::GetSockName(SocketAddress* addr) { + return SocketAddress::FromSockName(&handle_, addr); +} + void UDPWrapBase::RecvStart(const FunctionCallbackInfo& args) { UDPWrapBase* wrap = UDPWrapBase::FromObject(args.Holder()); args.GetReturnValue().Set(wrap == nullptr ? UV_EBADF : wrap->RecvStart()); diff --git a/src/udp_wrap.h b/src/udp_wrap.h index 5a6cb605d5..4ccabcad3c 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -29,6 +29,7 @@ #include "handle_wrap.h" #include "uv.h" #include "v8.h" +#include "node_sockaddr.h" namespace node { @@ -98,6 +99,10 @@ class UDPWrapBase { // Stores the sockaddr for the local socket in `name`. virtual int GetSockName(sockaddr* name, int* namelen) = 0; + virtual SocketAddress* GetPeerName(SocketAddress* addr = nullptr) = 0; + + virtual SocketAddress* GetSockName(SocketAddress* addr = nullptr) = 0; + // Returns an AsyncWrap object with the same lifetime as this object. virtual AsyncWrap* GetAsyncWrap() = 0; @@ -163,6 +168,9 @@ class UDPWrap final : public HandleWrap, const sockaddr* addr) override; int GetPeerName(sockaddr* name, int* namelen) override; int GetSockName(sockaddr* name, int* namelen) override; + SocketAddress* GetPeerName(SocketAddress* addr = nullptr) override; + SocketAddress* GetSockName(SocketAddress* addr = nullptr) override; + AsyncWrap* GetAsyncWrap() override; static v8::MaybeLocal Instantiate(Environment* env, diff --git a/test/cctest/test_quic_verifyhostnameidentity.cc b/test/cctest/test_quic_verifyhostnameidentity.cc index 2c7d1e232a..01df440d39 100644 --- a/test/cctest/test_quic_verifyhostnameidentity.cc +++ b/test/cctest/test_quic_verifyhostnameidentity.cc @@ -1,6 +1,8 @@ #include "base_object-inl.h" #include "node_quic_crypto.h" +#include "node_quic_util-inl.h" +#include "node_sockaddr-inl.h" #include "util.h" #include "gtest/gtest.h"