diff --git a/doc/api/quic.md b/doc/api/quic.md index e6505b8654586a..42a8b756909d1e 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -1539,9 +1539,11 @@ added: REPLACEME `object.passphrase` if provided, or `options.passphrase` if it is not. * `activeConnectionIdLimit` {number} Must be a value between `2` and `8` (inclusive). Default: `2`. + * `congestionAlgorithm` {string} Must be either `'reno'` or `'cubic'`. + **Default**: `'reno'`. * `maxAckDelay` {number} * `maxData` {number} - * `maxPacketSize` {number} + * `maxUdpPayloadSize` {number} * `maxStreamDataBidiLocal` {number} * `maxStreamDataBidiRemote` {number} * `maxStreamDataUni` {number} @@ -1706,9 +1708,11 @@ added: REPLACEME `object.passphrase` is optional. Encrypted keys will be decrypted with `object.passphrase` if provided, or `options.passphrase` if it is not. * `activeConnectionIdLimit` {number} + * `congestionAlgorithm` {string} Must be either `'reno'` or `'cubic'`. + **Default**: `'reno'`. * `maxAckDelay` {number} * `maxData` {number} - * `maxPacketSize` {number} + * `maxUdpPayloadSize` {number} * `maxStreamsBidi` {number} * `maxStreamsUni` {number} * `maxStreamDataBidiLocal` {number} diff --git a/lib/internal/quic/core.js b/lib/internal/quic/core.js index 6e3eab03b7f6f9..7e874ff34d58de 100644 --- a/lib/internal/quic/core.js +++ b/lib/internal/quic/core.js @@ -130,7 +130,7 @@ const { constants: { AF_INET, AF_INET6, - IDX_QUIC_SESSION_MAX_PACKET_SIZE_DEFAULT, + NGTCP2_DEFAULT_MAX_PKTLEN, IDX_QUIC_SESSION_STATE_MAX_STREAMS_BIDI, IDX_QUIC_SESSION_STATE_MAX_STREAMS_UNI, IDX_QUIC_SESSION_STATE_MAX_DATA_LEFT, @@ -1633,7 +1633,7 @@ class QuicSession extends EventEmitter { #earlyData = false; #handshakeComplete = false; #idleTimeout = false; - #maxPacketLength = IDX_QUIC_SESSION_MAX_PACKET_SIZE_DEFAULT; + #maxPacketLength = NGTCP2_DEFAULT_MAX_PKTLEN; #servername = undefined; #socket = undefined; #statelessReset = false; diff --git a/lib/internal/quic/util.js b/lib/internal/quic/util.js index bf12bc1a61f412..efeddee94b6e64 100644 --- a/lib/internal/quic/util.js +++ b/lib/internal/quic/util.js @@ -35,7 +35,7 @@ const { constants: { AF_INET, AF_INET6, - NGTCP2_ALPN_H3, + NGHTTP3_ALPN_H3, DEFAULT_RETRYTOKEN_EXPIRATION, DEFAULT_MAX_CONNECTIONS, DEFAULT_MAX_CONNECTIONS_PER_HOST, @@ -49,7 +49,8 @@ const { IDX_QUIC_SESSION_MAX_STREAMS_UNI, IDX_QUIC_SESSION_MAX_IDLE_TIMEOUT, IDX_QUIC_SESSION_MAX_ACK_DELAY, - IDX_QUIC_SESSION_MAX_PACKET_SIZE, + IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE, + IDX_QUIC_SESSION_CC_ALGO, IDX_QUIC_SESSION_CONFIG_COUNT, IDX_QUIC_SESSION_STATE_CERT_ENABLED, IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED, @@ -68,6 +69,8 @@ const { NGTCP2_NO_ERROR, NGTCP2_MAX_CIDLEN, NGTCP2_MIN_CIDLEN, + NGTCP2_CC_ALGO_CUBIC, + NGTCP2_CC_ALGO_RENO, QUIC_PREFERRED_ADDRESS_IGNORE, QUIC_PREFERRED_ADDRESS_USE, QUIC_ERROR_APPLICATION, @@ -167,11 +170,12 @@ function validateTransportParams(params) { maxStreamsBidi, maxStreamsUni, idleTimeout, - maxPacketSize, + maxUdpPayloadSize, maxAckDelay, preferredAddress, rejectUnauthorized, requestCert, + congestionAlgorithm = 'reno', h3: { qpackMaxTableCapacity, qpackBlockedStreams, @@ -231,10 +235,10 @@ function validateTransportParams(params) { 'options.idleTimeout', /* min */ 0); } - if (maxPacketSize !== undefined) { + if (maxUdpPayloadSize !== undefined) { validateInteger( - maxPacketSize, - 'options.maxPacketSize', + maxUdpPayloadSize, + 'options.maxUdpPayloadSize', /* min */ 0); } if (maxAckDelay !== undefined) { @@ -279,6 +283,23 @@ function validateTransportParams(params) { 'options.h3.maxHeaderLength', /* min */ 0); } + let ccAlgo = NGTCP2_CC_ALGO_RENO; + if (congestionAlgorithm !== undefined) { + validateString(congestionAlgorithm, 'options.conjestionAlgorithm'); + switch (congestionAlgorithm) { + case 'reno': + // This is the default + break; + case 'cubic': + ccAlgo = NGTCP2_CC_ALGO_CUBIC; + break; + default: + throw new ERR_INVALID_ARG_VALUE( + 'options.congestionAlgorithm', + congestionAlgorithm, + 'is not supported'); + } + } validatePreferredAddress(preferredAddress); @@ -291,11 +312,12 @@ function validateTransportParams(params) { maxStreamsBidi, maxStreamsUni, idleTimeout, - maxPacketSize, + maxUdpPayloadSize, maxAckDelay, preferredAddress, rejectUnauthorized, requestCert, + congestionAlgorithm: ccAlgo, h3: { qpackMaxTableCapacity, qpackBlockedStreams, @@ -549,7 +571,7 @@ function validateQuicSocketOptions(options = {}) { function validateQuicSocketListenOptions(options = {}) { validateObject(options); const { - alpn = NGTCP2_ALPN_H3, + alpn = NGHTTP3_ALPN_H3, defaultEncoding, highWaterMark, requestCert, @@ -656,8 +678,9 @@ function setTransportParams(config) { maxStreamsBidi, maxStreamsUni, idleTimeout, - maxPacketSize, + maxUdpPayloadSize, maxAckDelay, + congestionAlgorithm, h3: { qpackMaxTableCapacity, qpackBlockedStreams, @@ -698,8 +721,11 @@ function setTransportParams(config) { maxAckDelay, IDX_QUIC_SESSION_MAX_ACK_DELAY) | setConfigField(sessionConfig, - maxPacketSize, - IDX_QUIC_SESSION_MAX_PACKET_SIZE); + maxUdpPayloadSize, + IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE) | + setConfigField(sessionConfig, + congestionAlgorithm, + IDX_QUIC_SESSION_CC_ALGO); sessionConfig[IDX_QUIC_SESSION_CONFIG_COUNT] = flags; diff --git a/src/env.h b/src/env.h index 65163ec30fbc96..41de94106234f0 100644 --- a/src/env.h +++ b/src/env.h @@ -335,7 +335,7 @@ constexpr size_t kFsStatsBufferLength = V(psk_string, "psk") \ V(pubkey_string, "pubkey") \ V(query_string, "query") \ - V(quic_alpn_string, "h3-27") \ + V(http3_alpn_string, "h3-29") \ V(rate_string, "rate") \ V(raw_string, "raw") \ V(read_host_object_string, "_readHostObject") \ diff --git a/src/quic/node_quic.cc b/src/quic/node_quic.cc index 4f1d3e128dd38b..202767a670a30b 100644 --- a/src/quic/node_quic.cc +++ b/src/quic/node_quic.cc @@ -12,6 +12,7 @@ #include "node_quic_state.h" #include "node_quic_util-inl.h" #include "node_sockaddr-inl.h" +#include "nghttp3/nghttp3.h" #include #include @@ -169,10 +170,11 @@ void Initialize(Local target, V(IDX_QUIC_SESSION_MAX_STREAM_DATA_UNI) \ V(IDX_QUIC_SESSION_MAX_STREAMS_BIDI) \ V(IDX_QUIC_SESSION_MAX_STREAMS_UNI) \ - V(IDX_QUIC_SESSION_MAX_PACKET_SIZE) \ + V(IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE) \ V(IDX_QUIC_SESSION_ACK_DELAY_EXPONENT) \ V(IDX_QUIC_SESSION_DISABLE_MIGRATION) \ V(IDX_QUIC_SESSION_MAX_ACK_DELAY) \ + V(IDX_QUIC_SESSION_CC_ALGO) \ V(IDX_QUIC_SESSION_CONFIG_COUNT) \ V(IDX_QUIC_SESSION_STATE_CERT_ENABLED) \ V(IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED) \ @@ -190,6 +192,9 @@ void Initialize(Local target, V(NGTCP2_APP_NOERROR) \ V(NGTCP2_PATH_VALIDATION_RESULT_FAILURE) \ V(NGTCP2_PATH_VALIDATION_RESULT_SUCCESS) \ + V(NGTCP2_DEFAULT_MAX_PKTLEN) \ + V(NGTCP2_CC_ALGO_CUBIC) \ + V(NGTCP2_CC_ALGO_RENO) \ V(QUIC_ERROR_APPLICATION) \ V(QUIC_ERROR_CRYPTO) \ V(QUIC_ERROR_SESSION) \ @@ -238,8 +243,8 @@ void Initialize(Local target, NODE_DEFINE_CONSTANT(constants, AF_INET); NODE_DEFINE_CONSTANT(constants, AF_INET6); NODE_DEFINE_STRING_CONSTANT(constants, - NODE_STRINGIFY_HELPER(NGTCP2_ALPN_H3), - NGTCP2_ALPN_H3); + NODE_STRINGIFY_HELPER(NGHTTP3_ALPN_H3), + NGHTTP3_ALPN_H3); target->Set(context, env->constants_string(), constants).FromJust(); } diff --git a/src/quic/node_quic_crypto.cc b/src/quic/node_quic_crypto.cc index 0f5e2db9e1240c..e1762ce1837965 100644 --- a/src/quic/node_quic_crypto.cc +++ b/src/quic/node_quic_crypto.cc @@ -13,6 +13,7 @@ #include #include +#include // NGHTTP3_ALPN_H3 #include #include #include @@ -231,7 +232,6 @@ std::unique_ptr GenerateRetryPacket( cid.set_length(kScidLen); size_t pktlen = tokenlen + (2 * NGTCP2_MAX_CIDLEN) + scid.length() + 8; - CHECK_LE(pktlen, NGTCP2_MAX_PKT_SIZE); auto packet = QuicPacket::Create("retry", pktlen); ssize_t nwrite = @@ -258,13 +258,13 @@ std::unique_ptr GenerateRetryPacket( // is successful, ocid will be updated to the original connection ID encoded // in the encrypted token. bool InvalidRetryToken( - const ngtcp2_pkt_hd& hd, + const ngtcp2_vec& token, const SocketAddress& addr, QuicCID* ocid, const uint8_t* token_secret, uint64_t verification_expiration) { - if (hd.tokenlen < kTokenRandLen) + if (token.len < kTokenRandLen) return true; ngtcp2_crypto_ctx ctx; @@ -272,9 +272,9 @@ bool InvalidRetryToken( size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(&ctx.aead); - size_t ciphertextlen = hd.tokenlen - kTokenRandLen; - const uint8_t* ciphertext = hd.token; - const uint8_t* rand_data = hd.token + ciphertextlen; + size_t ciphertextlen = token.len - kTokenRandLen; + const uint8_t* ciphertext = token.base; + const uint8_t* rand_data = token.base + ciphertextlen; uint8_t token_key[kCryptoTokenKeylen]; uint8_t token_iv[kCryptoTokenIvlen]; @@ -559,10 +559,10 @@ Local GetALPNProtocol(const QuicSession& session) { QuicCryptoContext* ctx = session.crypto_context(); Environment* env = session.env(); std::string alpn = ctx->selected_alpn(); - // This supposed to be `NGTCP2_ALPN_H3 + 1` + // This supposed to be `NGHTTP3_ALPN_H3 + 1` // Details see https://github.com/nodejs/node/issues/33959 - if (alpn == &NGTCP2_ALPN_H3[1]) { - return env->quic_alpn_string(); + if (alpn == &NGHTTP3_ALPN_H3[1]) { + return env->http3_alpn_string(); } else { return ToV8Value( env->context(), @@ -800,6 +800,7 @@ void InitializeTLS(QuicSession* session, const crypto::SSLPointer& ssl) { UNREACHABLE(); } + ngtcp2_conn_set_tls_native_handle(session->connection(), ssl.get()); SetTransportParams(session, ssl); } @@ -859,33 +860,6 @@ void InitializeSecureContext( SSL_CTX_set_quic_method(**sc, &quic_method); } -bool DeriveAndInstallInitialKey( - const QuicSession& session, - const QuicCID& dcid) { - uint8_t initial_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; - uint8_t rx_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; - uint8_t tx_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; - uint8_t rx_key[NGTCP2_CRYPTO_INITIAL_KEYLEN]; - uint8_t tx_key[NGTCP2_CRYPTO_INITIAL_KEYLEN]; - uint8_t rx_hp[NGTCP2_CRYPTO_INITIAL_KEYLEN]; - uint8_t tx_hp[NGTCP2_CRYPTO_INITIAL_KEYLEN]; - uint8_t rx_iv[NGTCP2_CRYPTO_INITIAL_IVLEN]; - uint8_t tx_iv[NGTCP2_CRYPTO_INITIAL_IVLEN]; - return NGTCP2_OK(ngtcp2_crypto_derive_and_install_initial_key( - session.connection(), - rx_secret, - tx_secret, - initial_secret, - rx_key, - rx_iv, - rx_hp, - tx_key, - tx_iv, - tx_hp, - dcid.cid(), - session.crypto_context()->side())); -} - ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) { switch (ossl_level) { case ssl_encryption_initial: diff --git a/src/quic/node_quic_crypto.h b/src/quic/node_quic_crypto.h index 8ec0851ba3affe..21548022f17219 100644 --- a/src/quic/node_quic_crypto.h +++ b/src/quic/node_quic_crypto.h @@ -44,13 +44,6 @@ void InitializeSecureContext( // with the session. void InitializeTLS(QuicSession* session, const crypto::SSLPointer& ssl); -// Called when the client QuicSession is created and -// when the server QuicSession first receives the -// client hello. -bool DeriveAndInstallInitialKey( - const QuicSession& session, - const QuicCID& dcid); - // Generates a stateless reset token using HKDF with the // cid and token secret as input. The token secret is // either provided by user code when a QuicSocket is @@ -101,7 +94,7 @@ uint32_t GenerateFlowLabel( // the ocid will be updated to the original CID value encoded // within the successfully validated, encrypted token. bool InvalidRetryToken( - const ngtcp2_pkt_hd& hd, + const ngtcp2_vec& token, const SocketAddress& addr, QuicCID* ocid, const uint8_t* token_secret, diff --git a/src/quic/node_quic_default_application.cc b/src/quic/node_quic_default_application.cc index 8f3f0349cf9673..ea84099d70ae61 100644 --- a/src/quic/node_quic_default_application.cc +++ b/src/quic/node_quic_default_application.cc @@ -81,8 +81,8 @@ void DefaultApplication::ResumeStream(int64_t stream_id) { } bool DefaultApplication::ReceiveStreamData( + uint32_t flags, int64_t stream_id, - int fin, const uint8_t* data, size_t datalen, uint64_t offset) { @@ -110,7 +110,7 @@ bool DefaultApplication::ReceiveStreamData( } CHECK(stream); - stream->ReceiveData(fin, data, datalen, offset); + stream->ReceiveData(flags, data, datalen, offset); return true; } diff --git a/src/quic/node_quic_default_application.h b/src/quic/node_quic_default_application.h index 9fb5e050aa021f..d2dc1bfd7a5cc3 100644 --- a/src/quic/node_quic_default_application.h +++ b/src/quic/node_quic_default_application.h @@ -29,8 +29,8 @@ class DefaultApplication final : public QuicApplication { } bool ReceiveStreamData( + uint32_t flags, int64_t stream_id, - int fin, const uint8_t* data, size_t datalen, uint64_t offset) override; diff --git a/src/quic/node_quic_http3_application.cc b/src/quic/node_quic_http3_application.cc index 638830a99cefa1..673e8a4eace9ea 100644 --- a/src/quic/node_quic_http3_application.cc +++ b/src/quic/node_quic_http3_application.cc @@ -60,7 +60,7 @@ Http3Application::Http3Application( SetConfig(IDX_HTTP3_QPACK_BLOCKED_STREAMS, &Http3ApplicationConfig::qpack_blocked_streams); SetConfig(IDX_HTTP3_MAX_HEADER_LIST_SIZE, - &Http3ApplicationConfig::max_header_list_size); + &Http3ApplicationConfig::max_field_section_size); SetConfig(IDX_HTTP3_MAX_PUSHES, &Http3ApplicationConfig::max_pushes); SetConfig(IDX_HTTP3_MAX_HEADER_PAIRS, @@ -372,7 +372,7 @@ bool Http3Application::Initialize() { Debug(session(), "QPack Blocked Streams: %" PRIu64, config_.qpack_blocked_streams); Debug(session(), "Max Header List Size: %" PRIu64, - config_.max_header_list_size); + config_.max_field_section_size); Debug(session(), "Max Pushes: %" PRIu64, config_.max_pushes); @@ -401,16 +401,22 @@ bool Http3Application::Initialize() { // Here we pass the received data off to nghttp3 for processing. This will // trigger the invocation of the various nghttp3 callbacks. bool Http3Application::ReceiveStreamData( + uint32_t flags, int64_t stream_id, - int fin, const uint8_t* data, size_t datalen, uint64_t offset) { Debug(session(), "Receiving %" PRIu64 " bytes for stream %" PRIu64 "%s", - datalen, stream_id, fin == 1 ? " (fin)" : ""); + datalen, + stream_id, + flags & NGTCP2_STREAM_DATA_FLAG_FIN ? " (fin)" : ""); ssize_t nread = nghttp3_conn_read_stream( - connection(), stream_id, data, datalen, fin); + connection(), + stream_id, + data, + datalen, + flags & NGTCP2_STREAM_DATA_FLAG_FIN); if (nread < 0) { Debug(session(), "Failure to read HTTP/3 Stream Data [%" PRId64 "]", nread); return false; diff --git a/src/quic/node_quic_http3_application.h b/src/quic/node_quic_http3_application.h index aa5434f6cf30c9..fdedb1a45aff5d 100644 --- a/src/quic/node_quic_http3_application.h +++ b/src/quic/node_quic_http3_application.h @@ -63,7 +63,7 @@ struct Http3ApplicationConfig : public nghttp3_conn_settings { nghttp3_conn_settings_default(this); qpack_max_table_capacity = DEFAULT_QPACK_MAX_TABLE_CAPACITY; qpack_blocked_streams = DEFAULT_QPACK_BLOCKED_STREAMS; - max_header_list_size = DEFAULT_MAX_HEADER_LIST_SIZE; + max_field_section_size = DEFAULT_MAX_HEADER_LIST_SIZE; max_pushes = DEFAULT_MAX_PUSHES; } uint64_t max_header_pairs = DEFAULT_MAX_HEADER_LIST_PAIRS; @@ -90,8 +90,8 @@ class Http3Application final : } bool ReceiveStreamData( + uint32_t flags, int64_t stream_id, - int fin, const uint8_t* data, size_t datalen, uint64_t offset) override; diff --git a/src/quic/node_quic_session-inl.h b/src/quic/node_quic_session-inl.h index d1546205f1bf63..5acb8a2e9350ef 100644 --- a/src/quic/node_quic_session-inl.h +++ b/src/quic/node_quic_session-inl.h @@ -43,10 +43,13 @@ void QuicSessionConfig::GeneratePreferredAddressToken( *pscid); } -void QuicSessionConfig::set_original_connection_id(const QuicCID& ocid) { +void QuicSessionConfig::set_original_connection_id( + const QuicCID& ocid, + const QuicCID& scid) { if (ocid) { - transport_params.original_connection_id = *ocid; - transport_params.original_connection_id_present = 1; + transport_params.original_dcid = *ocid; + transport_params.retry_scid = *scid; + transport_params.retry_scid_present = 1; } } @@ -193,13 +196,6 @@ void QuicCryptoContext::set_tls_alert(int err) { session_->set_last_error(QuicError(QUIC_ERROR_CRYPTO, err)); } -// Derives and installs the initial keying material for a newly -// created session. -bool QuicCryptoContext::SetupInitialKey(const QuicCID& dcid) { - Debug(session(), "Deriving and installing initial keys"); - return DeriveAndInstallInitialKey(*session(), dcid); -} - QuicApplication::QuicApplication(QuicSession* session) : session_(session) {} void QuicApplication::set_stream_fin(int64_t stream_id) { @@ -214,17 +210,21 @@ ssize_t QuicApplication::WriteVStream( ssize_t* ndatalen, const StreamData& stream_data) { CHECK_LE(stream_data.count, kMaxVectorCount); + + uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_NONE; + if (stream_data.remaining > 0) + flags |= NGTCP2_WRITE_STREAM_FLAG_MORE; + if (stream_data.fin) + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + return ngtcp2_conn_writev_stream( session()->connection(), &path->path, buf, session()->max_packet_length(), ndatalen, - stream_data.remaining > 0 ? - NGTCP2_WRITE_STREAM_FLAG_MORE : - NGTCP2_WRITE_STREAM_FLAG_NONE, + flags, stream_data.id, - stream_data.fin, stream_data.buf, stream_data.count, uv_hrtime()); @@ -250,13 +250,6 @@ void QuicSession::DisassociateCID(const QuicCID& cid) { socket()->DisassociateCID(cid); } -void QuicSession::StartHandshake() { - if (crypto_context_->is_handshake_started() || is_server()) - return; - crypto_context_->handshake_started(); - SendPendingData(); -} - void QuicSession::ExtendMaxStreamData(int64_t stream_id, uint64_t max_data) { Debug(this, "Extending max stream %" PRId64 " data to %" PRIu64, diff --git a/src/quic/node_quic_session.cc b/src/quic/node_quic_session.cc index 16a1a623e21957..66a05e55248b4b 100644 --- a/src/quic/node_quic_session.cc +++ b/src/quic/node_quic_session.cc @@ -5,6 +5,7 @@ #include "env-inl.h" #include "node_crypto_common.h" #include "ngtcp2/ngtcp2.h" +#include "nghttp3/nghttp3.h" // NGHTTP3_ALPN_H3 #include "ngtcp2/ngtcp2_crypto.h" #include "ngtcp2/ngtcp2_crypto_openssl.h" #include "node.h" @@ -115,12 +116,14 @@ std::string QuicSession::RemoteTransportParamsDebug::ToString() const { out += " Max Idle Timeout: " + std::to_string(params.max_idle_timeout) + "\n"; out += " Max Packet Size: " + - std::to_string(params.max_packet_size) + "\n"; + std::to_string(params.max_udp_payload_size) + "\n"; if (!session->is_server()) { - if (params.original_connection_id_present) { - QuicCID cid(params.original_connection_id); + if (params.retry_scid_present) { + QuicCID cid(params.original_dcid); + QuicCID retry(params.retry_scid); out += " Original Connection ID: " + cid.ToString() + "\n"; + out += " Retry SCID: " + retry.ToString() + "\n"; } else { out += " Original Connection ID: N/A \n"; } @@ -165,13 +168,14 @@ void QuicSessionConfig::ResetToDefaults(QuicState* quic_state) { DEFAULT_MAX_STREAMS_UNI; transport_params.initial_max_data = DEFAULT_MAX_DATA; transport_params.max_idle_timeout = DEFAULT_MAX_IDLE_TIMEOUT; - transport_params.max_packet_size = - NGTCP2_MAX_PKT_SIZE; + transport_params.max_udp_payload_size = + NGTCP2_DEFAULT_MAX_UDP_PAYLOAD_SIZE; transport_params.max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; transport_params.disable_active_migration = 0; transport_params.preferred_address_present = 0; transport_params.stateless_reset_token_present = 0; + cc_algo = NGTCP2_CC_ALGO_RENO; } // Sets the QuicSessionConfig using an AliasedBuffer for efficiency. @@ -195,10 +199,13 @@ void QuicSessionConfig::Set( &transport_params.initial_max_streams_uni); SetConfig(quic_state, IDX_QUIC_SESSION_MAX_IDLE_TIMEOUT, &transport_params.max_idle_timeout); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_PACKET_SIZE, - &transport_params.max_packet_size); + SetConfig(quic_state, IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE, + &transport_params.max_udp_payload_size); SetConfig(quic_state, IDX_QUIC_SESSION_MAX_ACK_DELAY, &transport_params.max_ack_delay); + SetConfig(quic_state, + IDX_QUIC_SESSION_CC_ALGO, + reinterpret_cast(&cc_algo)); transport_params.max_idle_timeout = transport_params.max_idle_timeout * 1000000000; @@ -806,20 +813,25 @@ bool QuicCryptoContext::SetSecrets( uint8_t rx_iv[kCryptoIvlen]; uint8_t tx_iv[kCryptoIvlen]; - if (NGTCP2_ERR(ngtcp2_crypto_derive_and_install_key( + if (NGTCP2_ERR(ngtcp2_crypto_derive_and_install_rx_key( session()->connection(), - ssl_.get(), rx_key, rx_iv, rx_hp, + level, + rx_secret, + secretlen))) { + return false; + } + + if (NGTCP2_ERR(ngtcp2_crypto_derive_and_install_tx_key( + session()->connection(), tx_key, tx_iv, tx_hp, level, - rx_secret, tx_secret, - secretlen, - side_))) { + secretlen))) { return false; } @@ -864,19 +876,19 @@ bool QuicCryptoContext::SetSecrets( void QuicCryptoContext::AcknowledgeCryptoData( ngtcp2_crypto_level level, - size_t datalen) { + uint64_t datalen) { // It is possible for the QuicSession to have been destroyed but not yet // deconstructed. In such cases, we want to ignore the callback as there // is nothing to do but wait for further cleanup to happen. if (UNLIKELY(session_->is_destroyed())) return; Debug(session(), - "Acknowledging %d crypto bytes for %s level", + "Acknowledging %" PRIu64 " crypto bytes for %s level", datalen, crypto_level_name(level)); // Consumes (frees) the given number of bytes in the handshake buffer. - handshake_[level].Consume(datalen); + handshake_[level].Consume(static_cast(datalen)); // Update the statistics for the handshake, allowing us to track // how long the handshake is taking to be acknowledged. A malicious @@ -1117,7 +1129,6 @@ int QuicCryptoContext::Receive( // for processing. The handshake may or may not complete. int ret = ngtcp2_crypto_read_write_crypto_data( session_->connection(), - ssl_.get(), crypto_level, data, datalen); @@ -1357,7 +1368,7 @@ void QuicApplication::StreamReset( // and is not negotiable. In the future, we may allow it to be negotiated. QuicApplication* QuicSession::SelectApplication(QuicSession* session) { std::string alpn = session->alpn(); - if (alpn == NGTCP2_ALPN_H3) { + if (alpn == NGHTTP3_ALPN_H3) { Debug(this, "Selecting HTTP/3 Application"); return new Http3Application(session); } @@ -1374,10 +1385,10 @@ QuicSession::QuicSession( QuicSocket* socket, const QuicSessionConfig& config, Local wrap, - const QuicCID& rcid, const SocketAddress& local_addr, const SocketAddress& remote_addr, const QuicCID& dcid, + const QuicCID& scid, const QuicCID& ocid, uint32_t version, const std::string& alpn, @@ -1391,11 +1402,11 @@ QuicSession::QuicSession( AsyncWrap::PROVIDER_QUICSERVERSESSION, alpn, std::string(""), // empty hostname. not used on server side - rcid, + dcid, options, nullptr) { // The config is copied by assignment in the call below. - InitServer(config, local_addr, remote_addr, dcid, ocid, version, qlog); + InitServer(config, local_addr, remote_addr, dcid, scid, ocid, version, qlog); } // Client QuicSession Constructor @@ -1443,7 +1454,7 @@ QuicSession::QuicSession( AsyncWrap::ProviderType provider_type, const std::string& alpn, const std::string& hostname, - const QuicCID& rcid, + const QuicCID& dcid, uint32_t options, PreferredAddressStrategy preferred_address_strategy) : AsyncWrap(socket->env(), wrap, provider_type), @@ -1456,7 +1467,7 @@ QuicSession::QuicSession( hostname_(hostname), idle_(new Timer(socket->env(), [this]() { OnIdleTimeout(); })), retransmit_(new Timer(socket->env(), [this]() { MaybeTimeout(); })), - rcid_(rcid), + dcid_(dcid), state_(env()->isolate(), IDX_QUIC_SESSION_STATE_COUNT), quic_state_(socket->quic_state()) { PushListener(&default_listener_); @@ -1555,7 +1566,7 @@ BaseObjectPtr QuicSession::FindStream(int64_t id) const { void QuicSession::AckedStreamDataOffset( int64_t stream_id, uint64_t offset, - size_t datalen) { + uint64_t datalen) { // It is possible for the QuicSession to have been destroyed but not yet // deconstructed. In such cases, we want to ignore the callback as there // is nothing to do but wait for further cleanup to happen. @@ -1565,7 +1576,10 @@ void QuicSession::AckedStreamDataOffset( " bytes of stream %" PRId64 " data", datalen, stream_id); - application_->AcknowledgeStreamData(stream_id, offset, datalen); + application_->AcknowledgeStreamData( + stream_id, + offset, + static_cast(datalen)); } } @@ -1578,7 +1592,7 @@ void QuicSession::AddToSocket(QuicSocket* socket) { socket->AddSession(scid_, BaseObjectPtr(this)); switch (crypto_context_->side()) { case NGTCP2_CRYPTO_SIDE_SERVER: { - socket->AssociateCID(rcid_, scid_); + socket->AssociateCID(dcid_, scid_); socket->AssociateCID(pscid_, scid_); break; } @@ -1831,18 +1845,6 @@ void QuicSession::Ping() { ScheduleRetransmit(); } -// A Retry will effectively restart the TLS handshake process -// by generating new initial crypto material. This should only ever -// be called on client sessions -bool QuicSession::ReceiveRetry() { - CHECK(!is_server()); - if (is_flag_set(QUICSESSION_FLAG_DESTROYED)) - return false; - Debug(this, "A retry packet was received. Restarting the handshake"); - IncrementStat(&QuicSessionStats::retry_count); - return DeriveAndInstallInitialKey(*this, dcid()); -} - // When the QuicSocket receives a QUIC packet, it is forwarded on to here // for processing. bool QuicSession::Receive( @@ -1954,18 +1956,6 @@ bool QuicSession::Receive( return true; } -// The ReceiveClientInitial function is called by ngtcp2 when -// a new connection has been initiated. The very first step to -// establishing a communication channel is to setup the keys -// that will be used to secure the communication. -bool QuicSession::ReceiveClientInitial(const QuicCID& dcid) { - if (UNLIKELY(is_flag_set(QUICSESSION_FLAG_DESTROYED))) - return false; - Debug(this, "Receiving client initial parameters"); - crypto_context_->handshake_started(); - return DeriveAndInstallInitialKey(*this, dcid); -} - // Performs intake processing on a received QUIC packet. The received // data is passed on to ngtcp2 for parsing and processing. ngtcp2 will, // in turn, invoke a series of callbacks to handle the received packet. @@ -1996,7 +1986,7 @@ bool QuicSession::ReceivePacket( // address validation by sending a Retry packet // then immediately close the connection. if (err == NGTCP2_ERR_RETRY && is_server()) { - socket()->SendRetry(scid_, rcid_, local_address_, remote_address_); + socket()->SendRetry(scid_, dcid_, local_address_, remote_address_); ImmediateClose(); break; } @@ -2011,8 +2001,8 @@ bool QuicSession::ReceivePacket( // the stream does not yet exist, it is created, then the data is // forwarded on. bool QuicSession::ReceiveStreamData( + uint32_t flags, int64_t stream_id, - int fin, const uint8_t* data, size_t datalen, uint64_t offset) { @@ -2031,7 +2021,7 @@ bool QuicSession::ReceiveStreamData( // through but just in case of regression in that impl, // let's double check and simply ignore such packets // so we do not commit any resources. - if (UNLIKELY(fin == 0 && datalen == 0)) + if (UNLIKELY(!(flags & NGTCP2_STREAM_DATA_FLAG_FIN) && datalen == 0)) return true; if (is_flag_set(QUICSESSION_FLAG_DESTROYED)) @@ -2041,7 +2031,12 @@ bool QuicSession::ReceiveStreamData( Context::Scope context_scope(env()->context()); // From here, we defer to the QuicApplication specific processing logic - return application_->ReceiveStreamData(stream_id, fin, data, datalen, offset); + return application_->ReceiveStreamData( + flags, + stream_id, + data, + datalen, + offset); } // Removes the QuicSession from the current socket. This is @@ -2054,7 +2049,7 @@ void QuicSession::RemoveFromSocket() { CHECK(socket_); Debug(this, "Removing QuicSession from %s", socket_->diagnostic_name()); if (is_server()) { - socket_->DisassociateCID(rcid_); + socket_->DisassociateCID(dcid_); socket_->DisassociateCID(pscid_); } @@ -2621,10 +2616,10 @@ void QuicSession::MemoryInfo(MemoryTracker* tracker) const { BaseObjectPtr QuicSession::CreateServer( QuicSocket* socket, const QuicSessionConfig& config, - const QuicCID& rcid, const SocketAddress& local_addr, const SocketAddress& remote_addr, const QuicCID& dcid, + const QuicCID& scid, const QuicCID& ocid, uint32_t version, const std::string& alpn, @@ -2641,10 +2636,10 @@ BaseObjectPtr QuicSession::CreateServer( socket, config, obj, - rcid, local_addr, remote_addr, dcid, + scid, ocid, version, alpn, @@ -2661,6 +2656,7 @@ void QuicSession::InitServer( const SocketAddress& local_addr, const SocketAddress& remote_addr, const QuicCID& dcid, + const QuicCID& scid, const QuicCID& ocid, uint32_t version, QlogMode qlog) { @@ -2674,7 +2670,7 @@ void QuicSession::InitServer( remote_address_ = remote_addr; max_pktlen_ = GetMaxPktLen(remote_addr); - config.set_original_connection_id(ocid); + config.set_original_connection_id(ocid, scid); connection_id_strategy_(this, scid_.cid(), kScidLen); @@ -2741,12 +2737,13 @@ void QuicSessionOnCertDone(const FunctionCallbackInfo& args) { // the lifetime of a connection. Effectively, these communicate how // much time (from the perspective of the local peer) is being taken // to exchange data reliably with the remote peer. +// TODO(@jasnell): Revisit void QuicSession::UpdateRecoveryStats() { - const ngtcp2_rcvry_stat* stat = - ngtcp2_conn_get_rcvry_stat(connection()); - SetStat(&QuicSessionStats::min_rtt, stat->min_rtt); - SetStat(&QuicSessionStats::latest_rtt, stat->latest_rtt); - SetStat(&QuicSessionStats::smoothed_rtt, stat->smoothed_rtt); + ngtcp2_conn_stat stat; + ngtcp2_conn_get_conn_stat(connection(), &stat); + SetStat(&QuicSessionStats::min_rtt, stat.min_rtt); + SetStat(&QuicSessionStats::latest_rtt, stat.latest_rtt); + SetStat(&QuicSessionStats::smoothed_rtt, stat.smoothed_rtt); } // Data stats are used to allow user code to keep track of important @@ -2757,13 +2754,16 @@ void QuicSession::UpdateDataStats() { return; 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()); + + ngtcp2_conn_stat stat; + ngtcp2_conn_get_conn_stat(connection(), &stat); + state_[IDX_QUIC_SESSION_STATE_BYTES_IN_FLIGHT] = - static_cast(bytes_in_flight); + static_cast(stat.bytes_in_flight); // The max_bytes_in_flight is a highwater mark that can be used // in performance analysis operations. - if (bytes_in_flight > GetStat(&QuicSessionStats::max_bytes_in_flight)) - SetStat(&QuicSessionStats::max_bytes_in_flight, bytes_in_flight); + if (stat.bytes_in_flight > GetStat(&QuicSessionStats::max_bytes_in_flight)) + SetStat(&QuicSessionStats::max_bytes_in_flight, stat.bytes_in_flight); } // Static method for creating a new client QuicSession instance. @@ -2874,8 +2874,6 @@ void QuicSession::InitClient( crypto_context_->Initialize(); - CHECK(DeriveAndInstallInitialKey(*this, this->dcid())); - if (early_transport_params != nullptr) ngtcp2_conn_set_early_remote_transport_params(conn, early_transport_params); crypto_context_->set_session(std::move(early_session_ticket)); @@ -2888,39 +2886,6 @@ void QuicSession::InitClient( // created. These are static functions that, for the most part, simply defer to // a QuicSession instance that is passed through as user_data. -// Called by ngtcp2 upon creation of a new client connection -// to initiate the TLS handshake. This is only emitted on the client side. -int QuicSession::OnClientInitial( - ngtcp2_conn* conn, - void* user_data) { - QuicSession* session = static_cast(user_data); - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - QuicSession::Ngtcp2CallbackScope callback_scope(session); - return NGTCP2_OK(session->crypto_context()->Receive( - NGTCP2_CRYPTO_LEVEL_INITIAL, - 0, nullptr, 0)) ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; -} - -// Triggered by ngtcp2 when an initial handshake packet has been -// received. This is only invoked on server sessions and it is -// the absolute beginning of the communication between a client -// and a server. -int QuicSession::OnReceiveClientInitial( - ngtcp2_conn* conn, - const ngtcp2_cid* dcid, - void* user_data) { - QuicSession* session = static_cast(user_data); - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - QuicSession::Ngtcp2CallbackScope callback_scope(session); - if (!session->ReceiveClientInitial(QuicCID(dcid))) { - Debug(session, "Receiving initial client handshake failed"); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} - // Called by ngtcp2 for both client and server connections when // TLS handshake data has been received and needs to be processed. // This will be called multiple times during the TLS handshake @@ -2940,35 +2905,6 @@ int QuicSession::OnReceiveCryptoData( session->crypto_context()->Receive(crypto_level, offset, data, datalen)); } -// Triggered by ngtcp2 when a RETRY packet has been received. This is -// only emitted on the client side (only a server can send a RETRY). -// -// Per the QUIC specification, a RETRY is essentially a mechanism for -// the server to force path validation at the very start of a connection -// at the cost of a single round trip. The RETRY includes a token that -// the client must use in subsequent requests. When received, the client -// MUST restart the TLS handshake and must include the RETRY token in -// all initial packets. If the initial packets contain a valid RETRY -// token, then the server assumes the path to be validated. Fortunately -// ngtcp2 handles the retry token for us, so all we have to do is -// regenerate the initial keying material and restart the handshake -// and we can ignore the retry parameter. -int QuicSession::OnReceiveRetry( - ngtcp2_conn* conn, - const ngtcp2_pkt_hd* hd, - const ngtcp2_pkt_retry* retry, - void* user_data) { - QuicSession* session = static_cast(user_data); - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - QuicSession::Ngtcp2CallbackScope callback_scope(session); - if (!session->ReceiveRetry()) { - Debug(session, "Receiving retry token failed"); - return NGTCP2_ERR_CALLBACK_FAILURE; - } - return 0; -} - // Called by ngtcp2 for both client and server connections // when a request to extend the maximum number of bidirectional // streams has been received. @@ -3111,8 +3047,8 @@ int QuicSession::OnHandshakeConfirmed( // configuration option. int QuicSession::OnReceiveStreamData( ngtcp2_conn* conn, + uint32_t flags, int64_t stream_id, - int fin, uint64_t offset, const uint8_t* data, size_t datalen, @@ -3122,7 +3058,7 @@ int QuicSession::OnReceiveStreamData( if (UNLIKELY(session->is_destroyed())) return NGTCP2_ERR_CALLBACK_FAILURE; QuicSession::Ngtcp2CallbackScope callback_scope(session); - return session->ReceiveStreamData(stream_id, fin, data, datalen, offset) ? + return session->ReceiveStreamData(flags, stream_id, data, datalen, offset) ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; } @@ -3147,7 +3083,7 @@ int QuicSession::OnAckedCryptoOffset( ngtcp2_conn* conn, ngtcp2_crypto_level crypto_level, uint64_t offset, - size_t datalen, + uint64_t datalen, void* user_data) { QuicSession* session = static_cast(user_data); if (UNLIKELY(session->is_destroyed())) @@ -3166,7 +3102,7 @@ int QuicSession::OnAckedStreamDataOffset( ngtcp2_conn* conn, int64_t stream_id, uint64_t offset, - size_t datalen, + uint64_t datalen, void* user_data, void* stream_user_data) { QuicSession* session = static_cast(user_data); @@ -3373,7 +3309,7 @@ void QuicSession::OnQlogWrite(void* user_data, const void* data, size_t len) { const ngtcp2_conn_callbacks QuicSession::callbacks[2] = { // NGTCP2_CRYPTO_SIDE_CLIENT { - OnClientInitial, + ngtcp2_crypto_client_initial_cb, nullptr, OnReceiveCryptoData, OnHandshakeCompleted, @@ -3387,7 +3323,7 @@ const ngtcp2_conn_callbacks QuicSession::callbacks[2] = { OnStreamOpen, OnStreamClose, OnStatelessReset, - OnReceiveRetry, + ngtcp2_crypto_recv_retry_cb, OnExtendMaxStreamsBidi, OnExtendMaxStreamsUni, OnRand, @@ -3401,12 +3337,13 @@ const ngtcp2_conn_callbacks QuicSession::callbacks[2] = { OnExtendMaxStreamsRemoteUni, OnExtendMaxStreamData, OnConnectionIDStatus, - OnHandshakeConfirmed + OnHandshakeConfirmed, + nullptr, // recv_new_token }, // NGTCP2_CRYPTO_SIDE_SERVER { nullptr, - OnReceiveClientInitial, + ngtcp2_crypto_recv_client_initial_cb, OnReceiveCryptoData, OnHandshakeCompleted, nullptr, // recv_version_negotiation @@ -3434,6 +3371,7 @@ const ngtcp2_conn_callbacks QuicSession::callbacks[2] = { OnExtendMaxStreamData, OnConnectionIDStatus, nullptr, // handshake_confirmed + nullptr, // recv_new_token } }; @@ -3583,12 +3521,6 @@ crypto::SSLSessionPointer DecodeSessionTicket(Local value) { return crypto::GetTLSSession(sbuf.data(), sbuf.length()); } -void QuicSessionStartHandshake(const FunctionCallbackInfo& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - session->StartHandshake(); -} - void NewQuicClientSession(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); @@ -3600,7 +3532,7 @@ void NewQuicClientSession(const FunctionCallbackInfo& args) { int32_t preferred_address_policy; PreferredAddressStrategy preferred_address_strategy; uint32_t options = QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY; - std::string alpn(NGTCP2_ALPN_H3); + std::string alpn(NGHTTP3_ALPN_H3); enum ARG_IDX : int { SOCKET, @@ -3615,8 +3547,7 @@ void NewQuicClientSession(const FunctionCallbackInfo& args) { PREFERRED_ADDRESS_POLICY, ALPN, OPTIONS, - QLOG, - AUTO_START + QLOG }; CHECK(args[ARG_IDX::SOCKET]->IsObject()); @@ -3626,7 +3557,6 @@ void NewQuicClientSession(const FunctionCallbackInfo& args) { CHECK(args[ARG_IDX::TYPE]->Int32Value(env->context()).To(&family)); CHECK(args[ARG_IDX::PORT]->Uint32Value(env->context()).To(&port)); CHECK(args[ARG_IDX::OPTIONS]->Uint32Value(env->context()).To(&options)); - CHECK(args[ARG_IDX::AUTO_START]->IsBoolean()); if (!args[ARG_IDX::SNI]->IsUndefined()) CHECK(args[ARG_IDX::SNI]->IsString()); @@ -3681,15 +3611,9 @@ void NewQuicClientSession(const FunctionCallbackInfo& args) { QlogMode::kEnabled : QlogMode::kDisabled); - // Start the TLS handshake if the autoStart option is true - // (which it is by default). - if (args[ARG_IDX::AUTO_START]->BooleanValue(env->isolate())) { - session->StartHandshake(); - // Session was created but was unable to bootstrap properly during - // the start of the TLS handshake. - if (session->is_destroyed()) - return args.GetReturnValue().Set(ERR_FAILED_TO_CREATE_SESSION); - } + session->SendPendingData(); + if (session->is_destroyed()) + return args.GetReturnValue().Set(ERR_FAILED_TO_CREATE_SESSION); args.GetReturnValue().Set(session->object()); } @@ -3745,7 +3669,6 @@ void QuicSession::Initialize( env->SetProtoMethod(session, "setSocket", QuicSessionSetSocket); - env->SetProtoMethod(session, "startHandshake", QuicSessionStartHandshake); env->set_quicclientsession_instance_template(sessiont); env->SetMethod(target, "createClientSession", NewQuicClientSession); diff --git a/src/quic/node_quic_session.h b/src/quic/node_quic_session.h index 543d554c4d1731..f27aaabdd98ef1 100644 --- a/src/quic/node_quic_session.h +++ b/src/quic/node_quic_session.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -72,6 +73,9 @@ class QuicSessionConfig : public ngtcp2_settings { QuicSessionConfig(const QuicSessionConfig& config) { initial_ts = uv_hrtime(); transport_params = config.transport_params; + max_udp_payload_size = config.max_udp_payload_size; + cc_algo = config.cc_algo; + cc = config.cc; qlog = config.qlog; log_printf = config.log_printf; token = config.token; @@ -86,7 +90,9 @@ class QuicSessionConfig : public ngtcp2_settings { void Set(QuicState* quic_state, const struct sockaddr* preferred_addr = nullptr); - inline void set_original_connection_id(const QuicCID& ocid); + inline void set_original_connection_id( + const QuicCID& ocid, + const QuicCID& scid); // Generates the stateless reset token for the settings_ inline void GenerateStatelessResetToken( @@ -215,7 +221,6 @@ enum QuicSessionState : int { V(STREAMS_IN_COUNT, streams_in_count, "Streams In Count") \ V(STREAMS_OUT_COUNT, streams_out_count, "Streams Out Count") \ V(KEYUPDATE_COUNT, keyupdate_count, "Key Update Count") \ - V(RETRY_COUNT, retry_count, "Retry Count") \ V(LOSS_RETRANSMIT_COUNT, loss_retransmit_count, "Loss Retransmit Count") \ V(ACK_DELAY_RETRANSMIT_COUNT, \ ack_delay_retransmit_count, \ @@ -358,7 +363,7 @@ class QuicCryptoContext : public MemoryRetainer { // when ngtcp2 determines that it has received an acknowledgement // for crypto data at the specified level. This is our indication // that the data for that level can be released. - void AcknowledgeCryptoData(ngtcp2_crypto_level level, size_t datalen); + void AcknowledgeCryptoData(ngtcp2_crypto_level level, uint64_t datalen); inline void Initialize(); @@ -433,8 +438,6 @@ class QuicCryptoContext : public MemoryRetainer { inline void set_tls_alert(int err); - inline bool SetupInitialKey(const QuicCID& dcid); - ngtcp2_crypto_side side() const { return side_; } void WriteHandshake( @@ -450,12 +453,6 @@ class QuicCryptoContext : public MemoryRetainer { void MemoryInfo(MemoryTracker* tracker) const override; - void handshake_started() { - is_handshake_started_ = true; - } - - bool is_handshake_started() const { return is_handshake_started_; } - SET_MEMORY_INFO_NAME(QuicCryptoContext) SET_SELF_SIZE(QuicCryptoContext) @@ -477,7 +474,6 @@ class QuicCryptoContext : public MemoryRetainer { ngtcp2_crypto_side side_; crypto::SSLPointer ssl_; QuicBuffer handshake_[3]; - bool is_handshake_started_ = false; bool in_tls_callback_ = false; bool in_key_update_ = false; bool in_ocsp_request_ = false; @@ -552,8 +548,8 @@ class QuicApplication : public MemoryRetainer, virtual bool Initialize() = 0; virtual bool ReceiveStreamData( + uint32_t flags, int64_t stream_id, - int fin, const uint8_t* data, size_t datalen, uint64_t offset) = 0; @@ -698,13 +694,13 @@ class QuicSession : public AsyncWrap, static BaseObjectPtr CreateServer( QuicSocket* socket, const QuicSessionConfig& config, - const QuicCID& rcid, const SocketAddress& local_addr, const SocketAddress& remote_addr, const QuicCID& dcid, + const QuicCID& scid, const QuicCID& ocid, uint32_t version, - const std::string& alpn = NGTCP2_ALPN_H3, + const std::string& alpn = NGHTTP3_ALPN_H3, uint32_t options = 0, QlogMode qlog = QlogMode::kDisabled); @@ -718,15 +714,13 @@ class QuicSession : public AsyncWrap, v8::Local dcid, PreferredAddressStrategy preferred_address_strategy = IgnorePreferredAddressStrategy, - const std::string& alpn = NGTCP2_ALPN_H3, + const std::string& alpn = NGHTTP3_ALPN_H3, const std::string& hostname = "", uint32_t options = 0, QlogMode qlog = QlogMode::kDisabled); static const int kInitialClientBufferLength = 4096; - // The QuicSession::CryptoContext encapsulates all details of the - // TLS context on behalf of the QuicSession. QuicSession( ngtcp2_crypto_side side, // The QuicSocket that created this session. Note that @@ -745,7 +739,7 @@ class QuicSession : public AsyncWrap, // is always required. const std::string& alpn, const std::string& hostname, - const QuicCID& rcid, + const QuicCID& dcid, uint32_t options = 0, PreferredAddressStrategy preferred_address_strategy = IgnorePreferredAddressStrategy); @@ -755,10 +749,10 @@ class QuicSession : public AsyncWrap, QuicSocket* socket, const QuicSessionConfig& config, v8::Local wrap, - const QuicCID& rcid, const SocketAddress& local_addr, const SocketAddress& remote_addr, const QuicCID& dcid, + const QuicCID& scid, const QuicCID& ocid, uint32_t version, const std::string& alpn, @@ -787,12 +781,6 @@ class QuicSession : public AsyncWrap, inline QuicCID dcid() const; - // When a client QuicSession is created, if the autoStart - // option is true, the handshake will be immediately started. - // If autoStart is false, the start of the handshake will be - // deferred until the start handshake method is called; - inline void StartHandshake(); - QuicApplication* application() const { return application_.get(); } QuicCryptoContext* crypto_context() const { return crypto_context_.get(); } @@ -924,8 +912,8 @@ class QuicSession : public AsyncWrap, // Receive a chunk of QUIC stream data received from the peer bool ReceiveStreamData( + uint32_t flags, int64_t stream_id, - int fin, const uint8_t* data, size_t datalen, uint64_t offset); @@ -1104,24 +1092,18 @@ class QuicSession : public AsyncWrap, // complete. class SendSessionScope { public: - explicit SendSessionScope( - QuicSession* session, - bool wait_for_handshake = false) - : session_(session), - wait_for_handshake_(wait_for_handshake) { + explicit SendSessionScope(QuicSession* session) + : session_(session) { CHECK(session_); } ~SendSessionScope() { - if (!Ngtcp2CallbackScope::InNgtcp2CallbackScope(session_.get()) && - (!wait_for_handshake_ || - session_->crypto_context()->is_handshake_started())) + if (!Ngtcp2CallbackScope::InNgtcp2CallbackScope(session_.get())) session_->SendPendingData(); } private: BaseObjectPtr session_; - bool wait_for_handshake_ = false; }; // Tracks whether or not we are currently within an ngtcp2 callback @@ -1165,6 +1147,7 @@ class QuicSession : public AsyncWrap, const SocketAddress& local_addr, const SocketAddress& remote_addr, const QuicCID& dcid, + const QuicCID& scid, const QuicCID& ocid, uint32_t version, QlogMode qlog); @@ -1183,7 +1166,7 @@ class QuicSession : public AsyncWrap, void AckedStreamDataOffset( int64_t stream_id, uint64_t offset, - size_t datalen); + uint64_t datalen); inline void AssociateCID(const QuicCID& cid); @@ -1213,12 +1196,8 @@ class QuicSession : public AsyncWrap, const ngtcp2_path* path, ngtcp2_path_validation_result res); - bool ReceiveClientInitial(const QuicCID& dcid); - bool ReceivePacket(ngtcp2_path* path, const uint8_t* data, ssize_t nread); - bool ReceiveRetry(); - inline void RemoveConnectionID(const QuicCID& cid); void ScheduleRetransmit(); @@ -1251,16 +1230,6 @@ class QuicSession : public AsyncWrap, inline void VersionNegotiation(const uint32_t* sv, size_t nsv); - // static ngtcp2 callbacks - static int OnClientInitial( - ngtcp2_conn* conn, - void* user_data); - - static int OnReceiveClientInitial( - ngtcp2_conn* conn, - const ngtcp2_cid* dcid, - void* user_data); - static int OnReceiveCryptoData( ngtcp2_conn* conn, ngtcp2_crypto_level crypto_level, @@ -1279,32 +1248,26 @@ class QuicSession : public AsyncWrap, static int OnReceiveStreamData( ngtcp2_conn* conn, + uint32_t flags, int64_t stream_id, - int fin, uint64_t offset, const uint8_t* data, size_t datalen, void* user_data, void* stream_user_data); - static int OnReceiveRetry( - ngtcp2_conn* conn, - const ngtcp2_pkt_hd* hd, - const ngtcp2_pkt_retry* retry, - void* user_data); - static int OnAckedCryptoOffset( ngtcp2_conn* conn, ngtcp2_crypto_level crypto_level, uint64_t offset, - size_t datalen, + uint64_t datalen, void* user_data); static int OnAckedStreamDataOffset( ngtcp2_conn* conn, int64_t stream_id, uint64_t offset, - size_t datalen, + uint64_t datalen, void* user_data, void* stream_user_data); @@ -1530,7 +1493,7 @@ class QuicSession : public AsyncWrap, TimerPointer retransmit_; QuicCID scid_; - QuicCID rcid_; + QuicCID dcid_; QuicCID pscid_; ngtcp2_transport_params transport_params_; diff --git a/src/quic/node_quic_socket.cc b/src/quic/node_quic_socket.cc index 695c82e217508e..ce7c5820b2d7b1 100644 --- a/src/quic/node_quic_socket.cc +++ b/src/quic/node_quic_socket.cc @@ -5,6 +5,7 @@ #include "env-inl.h" #include "memory_tracker-inl.h" #include "nghttp2/nghttp2.h" +#include "nghttp3/nghttp3.h" #include "node.h" #include "node_buffer.h" #include "node_crypto.h" @@ -248,7 +249,7 @@ QuicSocket::QuicSocket( max_stateless_resets_per_host_(max_stateless_resets_per_host), retry_token_expiration_(retry_token_expiration), qlog_(qlog), - server_alpn_(NGTCP2_ALPN_H3), + server_alpn_(NGHTTP3_ALPN_H3), quic_state_(quic_state) { MakeWeak(); PushListener(&default_listener_); @@ -757,7 +758,7 @@ BaseObjectPtr QuicSocket::AcceptInitialPacket( QuicCID(hd.dcid), local_addr, remote_addr, - NGTCP2_SERVER_BUSY); + NGTCP2_CONNECTION_REFUSED); return {}; } @@ -771,16 +772,16 @@ BaseObjectPtr QuicSocket::AcceptInitialPacket( switch (hd.type) { case NGTCP2_PKT_INITIAL: if (is_option_set(QUICSOCKET_OPTIONS_VALIDATE_ADDRESS) || - hd.tokenlen > 0) { + hd.token.len > 0) { Debug(this, "Performing explicit address validation"); - if (hd.tokenlen == 0) { + if (hd.token.len == 0) { Debug(this, "No retry token was detected. Generating one"); SendRetry(dcid, scid, local_addr, remote_addr); // Sending a retry token terminates this connection attempt. return {}; } if (InvalidRetryToken( - hd, + hd.token, remote_addr, &ocid, token_secret_, @@ -805,10 +806,10 @@ BaseObjectPtr QuicSocket::AcceptInitialPacket( QuicSession::CreateServer( this, server_session_config_, - dcid, local_addr, remote_addr, scid, + dcid, ocid, version, server_alpn_, @@ -1069,7 +1070,7 @@ void QuicSocketListen(const FunctionCallbackInfo& args) { } } - std::string alpn(NGTCP2_ALPN_H3); + std::string alpn(NGHTTP3_ALPN_H3); if (args[4]->IsString()) { Utf8Value val(env->isolate(), args[4]); alpn = val.length(); diff --git a/src/quic/node_quic_socket.h b/src/quic/node_quic_socket.h index badad5aed1e82c..08ccd74dcb52f0 100644 --- a/src/quic/node_quic_socket.h +++ b/src/quic/node_quic_socket.h @@ -8,6 +8,7 @@ #include "node_crypto.h" #include "node_internals.h" #include "ngtcp2/ngtcp2.h" +#include "nghttp3/nghttp3.h" #include "node_quic_state.h" #include "node_quic_session.h" #include "node_quic_util.h" @@ -322,7 +323,7 @@ class QuicSocket : public AsyncWrap, void Listen( BaseObjectPtr context, const sockaddr* preferred_address = nullptr, - const std::string& alpn = NGTCP2_ALPN_H3, + const std::string& alpn = NGHTTP3_ALPN_H3, uint32_t options = 0); inline void ReceiveStart(); diff --git a/src/quic/node_quic_state.h b/src/quic/node_quic_state.h index 31f2f42a729bb9..6eb76529f895fe 100644 --- a/src/quic/node_quic_state.h +++ b/src/quic/node_quic_state.h @@ -17,10 +17,11 @@ enum QuicSessionConfigIndex : int { IDX_QUIC_SESSION_MAX_STREAMS_BIDI, IDX_QUIC_SESSION_MAX_STREAMS_UNI, IDX_QUIC_SESSION_MAX_IDLE_TIMEOUT, - IDX_QUIC_SESSION_MAX_PACKET_SIZE, + IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE, IDX_QUIC_SESSION_ACK_DELAY_EXPONENT, IDX_QUIC_SESSION_DISABLE_MIGRATION, IDX_QUIC_SESSION_MAX_ACK_DELAY, + IDX_QUIC_SESSION_CC_ALGO, IDX_QUIC_SESSION_CONFIG_COUNT }; diff --git a/src/quic/node_quic_stream.cc b/src/quic/node_quic_stream.cc index 63cbecc5581bcb..7a1054db407f59 100644 --- a/src/quic/node_quic_stream.cc +++ b/src/quic/node_quic_stream.cc @@ -149,7 +149,7 @@ int QuicStream::DoShutdown(ShutdownWrap* req_wrap) { if (is_destroyed()) return UV_EPIPE; - QuicSession::SendSessionScope send_scope(session(), true); + QuicSession::SendSessionScope send_scope(session()); if (is_writable()) { Debug(this, "Shutdown writable side"); @@ -181,7 +181,7 @@ int QuicStream::DoWrite( return 0; } - QuicSession::SendSessionScope send_scope(session(), true); + QuicSession::SendSessionScope send_scope(session()); Debug(this, "Queuing %" PRIu64 " bytes of data from %d buffers", length, nbufs); @@ -283,14 +283,14 @@ BaseObjectPtr QuicStream::New( // to the sending peer if the stream is in flowing mode, so the sender // should not be sending too much data. void QuicStream::ReceiveData( - int fin, + uint32_t flags, const uint8_t* data, size_t datalen, uint64_t offset) { CHECK(!is_destroyed()); Debug(this, "Receiving %d bytes. Final? %s. Readable? %s", datalen, - fin ? "yes" : "no", + flags & NGTCP2_STREAM_DATA_FLAG_FIN ? "yes" : "no", is_readable() ? "yes" : "no"); // If the QuicStream is not (or was never) readable, just ignore the chunk. @@ -299,7 +299,7 @@ void QuicStream::ReceiveData( // ngtcp2 guarantees that datalen will only be 0 if fin is set. // Let's just make sure. - CHECK(datalen > 0 || fin == 1); + CHECK(datalen > 0 || flags & NGTCP2_STREAM_DATA_FLAG_FIN); // ngtcp2 guarantees that offset is always greater than the previously // received offset. Let's just make sure. @@ -354,7 +354,7 @@ void QuicStream::ReceiveData( // When fin != 0, we've received that last chunk of data for this // stream, indicating that the stream will no longer be readable. - if (fin) { + if (flags & NGTCP2_STREAM_DATA_FLAG_FIN) { set_flag(QUICSTREAM_FLAG_FIN); set_final_size(offset + datalen); EmitRead(UV_EOF); diff --git a/src/quic/node_quic_stream.h b/src/quic/node_quic_stream.h index 6e968292b00936..97174dcb7b925d 100644 --- a/src/quic/node_quic_stream.h +++ b/src/quic/node_quic_stream.h @@ -301,7 +301,7 @@ class QuicStream : public AsyncWrap, // Passes a chunk of data on to the QuicStream listener. void ReceiveData( - int fin, + uint32_t flags, const uint8_t* data, size_t datalen, uint64_t offset); diff --git a/test/common/quic.js b/test/common/quic.js index 6fe121886ad0a2..bee54d81717deb 100644 --- a/test/common/quic.js +++ b/test/common/quic.js @@ -5,7 +5,7 @@ const { debuglog } = require('util'); const { readKeys } = require('./fixtures'); const { createWriteStream } = require('fs'); -const kHttp3Alpn = 'h3-27'; +const kHttp3Alpn = 'h3-29'; const [ key, cert, ca ] = readKeys( diff --git a/test/parallel/test-quic-binding.js b/test/parallel/test-quic-binding.js index 3d5a5b581fbc24..6f28944664ffc9 100644 --- a/test/parallel/test-quic-binding.js +++ b/test/parallel/test-quic-binding.js @@ -17,8 +17,8 @@ assert(quic.constants); // Version numbers used to identify IETF drafts are created by // adding the draft number to 0xff0000, in this case 19 (25). -assert.strictEqual(quic.constants.NGTCP2_PROTO_VER.toString(16), 'ff00001b'); -assert.strictEqual(quic.constants.NGTCP2_ALPN_H3, '\u0005h3-27'); +assert.strictEqual(quic.constants.NGTCP2_PROTO_VER.toString(16), 'ff00001d'); +assert.strictEqual(quic.constants.NGHTTP3_ALPN_H3, '\u0005h3-29'); // The following just tests for the presence of things we absolutely need. // They don't test the functionality of those things. diff --git a/test/parallel/test-quic-errors-quicsocket-connect.js b/test/parallel/test-quic-errors-quicsocket-connect.js index 49926851f8f899..cec0fb4563cbdc 100644 --- a/test/parallel/test-quic-errors-quicsocket-connect.js +++ b/test/parallel/test-quic-errors-quicsocket-connect.js @@ -100,7 +100,7 @@ const client = createQuicSocket(); 'activeConnectionIdLimit', 'maxAckDelay', 'maxData', - 'maxPacketSize', + 'maxUdpPayloadSize', 'maxStreamDataBidiLocal', 'maxStreamDataBidiRemote', 'maxStreamDataUni', @@ -206,7 +206,7 @@ assert.throws(() => client.connect(), { // [x] activeConnectionIdLimit - must be a number between 2 and 8 // [x] maxAckDelay - must be a number greater than zero // [x] maxData - must be a number greater than zero -// [x] maxPacketSize - must be a number greater than zero +// [x] maxUdpPayloadSize - must be a number greater than zero // [x] maxStreamDataBidiLocal - must be a number greater than zero // [x] maxStreamDataBidiRemote - must be a number greater than zero // [x] maxStreamDataUni - must be a number greater than zero diff --git a/test/parallel/test-quic-errors-quicsocket-listen.js b/test/parallel/test-quic-errors-quicsocket-listen.js index 432322441476c3..c3caa0a6bca9ec 100644 --- a/test/parallel/test-quic-errors-quicsocket-listen.js +++ b/test/parallel/test-quic-errors-quicsocket-listen.js @@ -55,7 +55,7 @@ const { createQuicSocket } = require('net'); 'activeConnectionIdLimit', 'maxAckDelay', 'maxData', - 'maxPacketSize', + 'maxUdpPayloadSize', 'maxStreamDataBidiLocal', 'maxStreamDataBidiRemote', 'maxStreamDataUni', @@ -152,7 +152,7 @@ const { createQuicSocket } = require('net'); // * [x] activeConnectionIdLimit // * [x] maxAckDelay // * [x] maxData -// * [x] maxPacketSize +// * [x] maxUdpPayloadSize // * [x] maxStreamsBidi // * [x] maxStreamsUni // * [x] maxStreamDataBidiLocal diff --git a/test/parallel/test-quic-quicsession-resume.js b/test/parallel/test-quic-quicsession-resume.js index 4d4a00abcb175c..1217b682f10e19 100644 --- a/test/parallel/test-quic-quicsession-resume.js +++ b/test/parallel/test-quic-quicsession-resume.js @@ -79,7 +79,7 @@ server.on('ready', common.mustCall(() => { stream.on('error', common.mustNotCall()); stream.on('close', common.mustCall(() => countdown.dec())); - req.startHandshake(); + // req.startHandshake(); // TODO(@jasnell): There's a slight bug in here in that // calling end() will uncork the stream, causing data to