From 7ecf99404e9896e9fd2b06ab3e1a924ab0bac671 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 26 Dec 2023 08:47:33 -0800 Subject: [PATCH] quic: update quic impl to use latest ngtcp2/nghttp3 PR-URL: https://github.com/nodejs/node/pull/51291 Reviewed-By: Stephen Belanger Reviewed-By: Jiawen Geng --- src/quic/application.cc | 26 +++--- src/quic/bindingdata.h | 5 +- src/quic/data.cc | 24 +++--- src/quic/data.h | 25 +++--- src/quic/endpoint.cc | 27 ++++--- src/quic/endpoint.h | 7 +- src/quic/preferredaddress.cc | 27 ++++--- src/quic/session.cc | 153 +++++++++++++++++++++-------------- src/quic/session.h | 10 ++- src/quic/streams.cc | 6 +- src/quic/tlscontext.cc | 38 ++++----- src/quic/tlscontext.h | 9 ++- src/quic/transportparams.cc | 58 ++++++------- src/quic/transportparams.h | 22 +++-- 14 files changed, 237 insertions(+), 200 deletions(-) diff --git a/src/quic/application.cc b/src/quic/application.cc index ce630ae35e456f..3ae6c8f2efef0d 100644 --- a/src/quic/application.cc +++ b/src/quic/application.cc @@ -151,7 +151,7 @@ BaseObjectPtr Session::Application::CreateStreamDataPacket() { return Packet::Create(env(), session_->endpoint_.get(), session_->remote_address_, - ngtcp2_conn_get_max_udp_payload_size(*session_), + ngtcp2_conn_get_max_tx_udp_payload_size(*session_), "stream data"); } @@ -291,18 +291,18 @@ ssize_t Session::Application::WriteVStream(PathStorage* path, 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; - ssize_t ret = - ngtcp2_conn_writev_stream(*session_, - &path->path, - nullptr, - buf, - ngtcp2_conn_get_max_udp_payload_size(*session_), - ndatalen, - flags, - stream_data.id, - stream_data.buf, - stream_data.count, - uv_hrtime()); + ssize_t ret = ngtcp2_conn_writev_stream( + *session_, + &path->path, + nullptr, + buf, + ngtcp2_conn_get_max_tx_udp_payload_size(*session_), + ndatalen, + flags, + stream_data.id, + stream_data.buf, + stream_data.count, + uv_hrtime()); return ret; } diff --git a/src/quic/bindingdata.h b/src/quic/bindingdata.h index 015265967fd58e..53622cf2d36eba 100644 --- a/src/quic/bindingdata.h +++ b/src/quic/bindingdata.h @@ -23,8 +23,8 @@ class Endpoint; class Packet; enum class Side { - CLIENT = NGTCP2_CRYPTO_SIDE_CLIENT, - SERVER = NGTCP2_CRYPTO_SIDE_SERVER, + CLIENT, + SERVER, }; enum class EndpointLabel { @@ -119,7 +119,6 @@ constexpr size_t kMaxVectorCount = 16; V(alpn, "alpn") \ V(application_options, "application") \ V(bbr, "bbr") \ - V(bbr2, "bbr2") \ V(ca, "ca") \ V(certs, "certs") \ V(cc_algorithm, "cc") \ diff --git a/src/quic/data.cc b/src/quic/data.cc index c5cba75a5c9374..2dd542f24b02ee 100644 --- a/src/quic/data.cc +++ b/src/quic/data.cc @@ -126,13 +126,15 @@ std::string TypeName(QuicError::Type type) { } // namespace QuicError::QuicError(const std::string_view reason) - : reason_(reason), ptr_(&error_) {} + : reason_(reason), error_(), ptr_(&error_) { + ngtcp2_ccerr_default(&error_); +} -QuicError::QuicError(const ngtcp2_connection_close_error* ptr) +QuicError::QuicError(const ngtcp2_ccerr* ptr) : reason_(reinterpret_cast(ptr->reason), ptr->reasonlen), ptr_(ptr) {} -QuicError::QuicError(const ngtcp2_connection_close_error& error) +QuicError::QuicError(const ngtcp2_ccerr& error) : reason_(reinterpret_cast(error.reason), error.reasonlen), error_(error), ptr_(&error_) {} @@ -175,11 +177,11 @@ const std::string_view QuicError::reason() const { return reason_; } -QuicError::operator const ngtcp2_connection_close_error&() const { +QuicError::operator const ngtcp2_ccerr&() const { return *ptr_; } -QuicError::operator const ngtcp2_connection_close_error*() const { +QuicError::operator const ngtcp2_ccerr*() const { return ptr_; } @@ -212,7 +214,7 @@ void QuicError::MemoryInfo(MemoryTracker* tracker) const { QuicError QuicError::ForTransport(error_code code, const std::string_view reason) { QuicError error(reason); - ngtcp2_connection_close_error_set_transport_error( + ngtcp2_ccerr_set_transport_error( &error.error_, code, error.reason_c_str(), reason.length()); return error; } @@ -220,7 +222,7 @@ QuicError QuicError::ForTransport(error_code code, QuicError QuicError::ForApplication(error_code code, const std::string_view reason) { QuicError error(reason); - ngtcp2_connection_close_error_set_application_error( + ngtcp2_ccerr_set_application_error( &error.error_, code, error.reason_c_str(), reason.length()); return error; } @@ -235,22 +237,20 @@ QuicError QuicError::ForIdleClose(const std::string_view reason) { QuicError QuicError::ForNgtcp2Error(int code, const std::string_view reason) { QuicError error(reason); - ngtcp2_connection_close_error_set_transport_error_liberr( + ngtcp2_ccerr_set_liberr( &error.error_, code, error.reason_c_str(), reason.length()); return error; } QuicError QuicError::ForTlsAlert(int code, const std::string_view reason) { QuicError error(reason); - ngtcp2_connection_close_error_set_transport_error_tls_alert( + ngtcp2_ccerr_set_tls_alert( &error.error_, code, error.reason_c_str(), reason.length()); return error; } QuicError QuicError::FromConnectionClose(ngtcp2_conn* session) { - QuicError error; - ngtcp2_conn_get_connection_close_error(session, &error.error_); - return error; + return QuicError(ngtcp2_conn_get_ccerr(session)); } QuicError QuicError::TRANSPORT_NO_ERROR = diff --git a/src/quic/data.h b/src/quic/data.h index ef103254104ec3..56a8c8c6d5e869 100644 --- a/src/quic/data.h +++ b/src/quic/data.h @@ -71,29 +71,28 @@ class QuicError final : public MemoryRetainer { static constexpr error_code QUIC_APP_NO_ERROR = 65280; enum class Type { - TRANSPORT = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT, - APPLICATION = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION, - VERSION_NEGOTIATION = - NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_VERSION_NEGOTIATION, - IDLE_CLOSE = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE, + TRANSPORT = NGTCP2_CCERR_TYPE_TRANSPORT, + APPLICATION = NGTCP2_CCERR_TYPE_APPLICATION, + VERSION_NEGOTIATION = NGTCP2_CCERR_TYPE_VERSION_NEGOTIATION, + IDLE_CLOSE = NGTCP2_CCERR_TYPE_IDLE_CLOSE, }; static constexpr error_code QUIC_ERROR_TYPE_TRANSPORT = - NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT; + NGTCP2_CCERR_TYPE_TRANSPORT; static constexpr error_code QUIC_ERROR_TYPE_APPLICATION = - NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION; + NGTCP2_CCERR_TYPE_APPLICATION; explicit QuicError(const std::string_view reason = ""); - explicit QuicError(const ngtcp2_connection_close_error* ptr); - explicit QuicError(const ngtcp2_connection_close_error& error); + explicit QuicError(const ngtcp2_ccerr* ptr); + explicit QuicError(const ngtcp2_ccerr& error); Type type() const; error_code code() const; const std::string_view reason() const; uint64_t frame_type() const; - operator const ngtcp2_connection_close_error&() const; - operator const ngtcp2_connection_close_error*() const; + operator const ngtcp2_ccerr&() const; + operator const ngtcp2_ccerr*() const; // Returns false if the QuicError uses a no_error code with type // transport or application. @@ -130,8 +129,8 @@ class QuicError final : public MemoryRetainer { const uint8_t* reason_c_str() const; std::string reason_; - ngtcp2_connection_close_error error_ = ngtcp2_connection_close_error(); - const ngtcp2_connection_close_error* ptr_ = nullptr; + ngtcp2_ccerr error_; + const ngtcp2_ccerr* ptr_ = nullptr; }; } // namespace quic diff --git a/src/quic/endpoint.cc b/src/quic/endpoint.cc index c8feed459a2ff4..93154c1fe29883 100644 --- a/src/quic/endpoint.cc +++ b/src/quic/endpoint.cc @@ -73,8 +73,7 @@ namespace quic { #define ENDPOINT_CC(V) \ V(RENO, reno) \ V(CUBIC, cubic) \ - V(BBR, bbr) \ - V(BBR2, bbr2) + V(BBR, bbr) struct Endpoint::State { #define V(_, name, type) type name; @@ -427,11 +426,15 @@ int Endpoint::UDP::Bind(const Endpoint::Options& options) { } void Endpoint::UDP::Ref() { - if (!is_closed()) uv_ref(reinterpret_cast(&impl_->handle_)); + if (!is_closed_or_closing()) { + uv_ref(reinterpret_cast(&impl_->handle_)); + } } void Endpoint::UDP::Unref() { - if (!is_closed()) uv_unref(reinterpret_cast(&impl_->handle_)); + if (!is_closed_or_closing()) { + uv_unref(reinterpret_cast(&impl_->handle_)); + } } int Endpoint::UDP::Start() { @@ -476,7 +479,7 @@ Endpoint::UDP::operator bool() const { } SocketAddress Endpoint::UDP::local_address() const { - DCHECK(!is_closed() && is_bound()); + DCHECK(!is_closed_or_closing() && is_bound()); return SocketAddress::FromSockName(impl_->handle_); } @@ -1012,7 +1015,7 @@ void Endpoint::Receive(const uv_buf_t& buf, if (options_.validate_address) { // If there is no token, generate and send one. - if (hd.token.len == 0) { + if (hd.tokenlen == 0) { SendRetry(PathDescriptor{ version, dcid, @@ -1027,9 +1030,9 @@ void Endpoint::Receive(const uv_buf_t& buf, // We have two kinds of tokens, each prefixed with a different magic // byte. - switch (hd.token.base[0]) { + switch (hd.token[0]) { case RetryToken::kTokenMagic: { - RetryToken token(hd.token.base, hd.token.len); + RetryToken token(hd.token, hd.tokenlen); auto ocid = token.Validate( version, remote_address, @@ -1055,7 +1058,7 @@ void Endpoint::Receive(const uv_buf_t& buf, break; } case RegularToken::kTokenMagic: { - RegularToken token(hd.token.base, hd.token.len); + RegularToken token(hd.token, hd.tokenlen); if (!token.Validate( version, remote_address, @@ -1072,8 +1075,8 @@ void Endpoint::Receive(const uv_buf_t& buf, // if a retry is sent. return true; } - hd.token.base = nullptr; - hd.token.len = 0; + hd.token = nullptr; + hd.tokenlen = 0; break; } default: { @@ -1093,7 +1096,7 @@ void Endpoint::Receive(const uv_buf_t& buf, // so we don't have to do this dance again for this endpoint // instance. addrLRU_.Upsert(remote_address)->validated = true; - } else if (hd.token.len > 0) { + } else if (hd.tokenlen > 0) { // If validation is turned off and there is a token, that's weird. // The peer should only have a token if we sent it to them and we // wouldn't have sent it unless validation was turned on. Let's diff --git a/src/quic/endpoint.h b/src/quic/endpoint.h index e1503030fd5ff1..04f8f5ae50f082 100644 --- a/src/quic/endpoint.h +++ b/src/quic/endpoint.h @@ -36,7 +36,6 @@ class Endpoint final : public AsyncWrap, public Packet::Listener { static constexpr auto QUIC_CC_ALGO_RENO = NGTCP2_CC_ALGO_RENO; static constexpr auto QUIC_CC_ALGO_CUBIC = NGTCP2_CC_ALGO_CUBIC; static constexpr auto QUIC_CC_ALGO_BBR = NGTCP2_CC_ALGO_BBR; - static constexpr auto QUIC_CC_ALGO_BBR2 = NGTCP2_CC_ALGO_BBR2; // Endpoint configuration options struct Options final : public MemoryRetainer { @@ -131,9 +130,9 @@ class Endpoint final : public AsyncWrap, public Packet::Listener { #endif // DEBUG // There are several common congestion control algorithms that ngtcp2 uses - // to determine how it manages the flow control window: RENO, CUBIC, BBR, - // and BBR2. The details of how each works is not relevant here. The choice - // of which to use by default is arbitrary and we can choose whichever we'd + // to determine how it manages the flow control window: RENO, CUBIC, and + // BBR. The details of how each works is not relevant here. The choice of + // which to use by default is arbitrary and we can choose whichever we'd // like. Additional performance profiling will be needed to determine which // is the better of the two for our needs. ngtcp2_cc_algo cc_algorithm = NGTCP2_CC_ALGO_CUBIC; diff --git a/src/quic/preferredaddress.cc b/src/quic/preferredaddress.cc index 66a44cd5bcf2f4..7675aebd765f9c 100644 --- a/src/quic/preferredaddress.cc +++ b/src/quic/preferredaddress.cc @@ -28,9 +28,10 @@ std::optional get_address_info( if (!paddr.ipv4_present) return std::nullopt; PreferredAddress::AddressInfo address; address.family = FAMILY; - address.port = paddr.ipv4_port; + address.port = paddr.ipv4.sin_port; if (uv_inet_ntop( - FAMILY, paddr.ipv4_addr, address.host, sizeof(address.host)) == 0) { + FAMILY, &paddr.ipv4.sin_addr, address.host, sizeof(address.host)) == + 0) { address.address = address.host; } return address; @@ -38,9 +39,11 @@ std::optional get_address_info( if (!paddr.ipv6_present) return std::nullopt; PreferredAddress::AddressInfo address; address.family = FAMILY; - address.port = paddr.ipv6_port; - if (uv_inet_ntop( - FAMILY, paddr.ipv6_addr, address.host, sizeof(address.host)) == 0) { + address.port = paddr.ipv6.sin6_port; + if (uv_inet_ntop(FAMILY, + &paddr.ipv6.sin6_addr, + address.host, + sizeof(address.host)) == 0) { address.address = address.host; } return address; @@ -50,20 +53,20 @@ std::optional get_address_info( template void copy_to_transport_params(ngtcp2_transport_params* params, const sockaddr* addr) { - params->preferred_address_present = true; + params->preferred_addr_present = true; if constexpr (FAMILY == AF_INET) { const sockaddr_in* src = reinterpret_cast(addr); - params->preferred_address.ipv4_port = SocketAddress::GetPort(addr); - memcpy(params->preferred_address.ipv4_addr, + params->preferred_addr.ipv4.sin_port = SocketAddress::GetPort(addr); + memcpy(¶ms->preferred_addr.ipv4.sin_addr, &src->sin_addr, - sizeof(params->preferred_address.ipv4_addr)); + sizeof(params->preferred_addr.ipv4.sin_addr)); } else { DCHECK_EQ(FAMILY, AF_INET6); const sockaddr_in6* src = reinterpret_cast(addr); - params->preferred_address.ipv6_port = SocketAddress::GetPort(addr); - memcpy(params->preferred_address.ipv6_addr, + params->preferred_addr.ipv6.sin6_port = SocketAddress::GetPort(addr); + memcpy(¶ms->preferred_addr.ipv6.sin6_addr, &src->sin6_addr, - sizeof(params->preferred_address.ipv4_addr)); + sizeof(params->preferred_addr.ipv4.sin_addr)); } UNREACHABLE(); } diff --git a/src/quic/session.cc b/src/quic/session.cc index e839aed992ab5d..271fdc89152c57 100644 --- a/src/quic/session.cc +++ b/src/quic/session.cc @@ -36,6 +36,7 @@ using v8::Array; using v8::ArrayBuffer; using v8::ArrayBufferView; using v8::BigInt; +using v8::Boolean; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; @@ -48,6 +49,7 @@ using v8::Object; using v8::PropertyAttribute; using v8::String; using v8::Uint32; +using v8::Undefined; using v8::Value; namespace quic { @@ -299,8 +301,7 @@ Session::Config::Config(Side side, settings.initial_ts = uv_hrtime(); if (options.qlog) { - if (ocid) settings.qlog.odcid = ocid; - settings.qlog.write = on_qlog_write; + settings.qlog_write = on_qlog_write; } if (endpoint.env()->enabled_debug_list()->enabled( @@ -311,7 +312,7 @@ Session::Config::Config(Side side, // We pull parts of the settings for the session from the endpoint options. auto& config = endpoint.options(); settings.cc_algo = config.cc_algorithm; - settings.max_udp_payload_size = config.max_payload_size; + settings.max_tx_udp_payload_size = config.max_payload_size; if (config.unacknowledged_packet_threshold > 0) { settings.ack_thresh = config.unacknowledged_packet_threshold; } @@ -572,14 +573,12 @@ BaseObjectPtr Session::keylog() const { TransportParams Session::GetLocalTransportParams() const { DCHECK(!is_destroyed()); - return TransportParams(TransportParams::Type::ENCRYPTED_EXTENSIONS, - ngtcp2_conn_get_local_transport_params(*this)); + return TransportParams(ngtcp2_conn_get_local_transport_params(*this)); } TransportParams Session::GetRemoteTransportParams() const { DCHECK(!is_destroyed()); - return TransportParams(TransportParams::Type::ENCRYPTED_EXTENSIONS, - ngtcp2_conn_get_remote_transport_params(*this)); + return TransportParams(ngtcp2_conn_get_remote_transport_params(*this)); } void Session::SetLastError(QuicError&& error) { @@ -629,9 +628,11 @@ void Session::Destroy() { // be deconstructed once the stack unwinds and any remaining // BaseObjectPtr instances fall out of scope. - std::vector cids(ngtcp2_conn_get_num_scid(*this)); - std::vector tokens(ngtcp2_conn_get_num_active_dcid(*this)); + std::vector cids(ngtcp2_conn_get_scid(*this, nullptr)); ngtcp2_conn_get_scid(*this, cids.data()); + + std::vector tokens( + ngtcp2_conn_get_active_dcid(*this, nullptr)); ngtcp2_conn_get_active_dcid(*this, tokens.data()); endpoint_->DisassociateCID(config_.dcid); @@ -764,7 +765,7 @@ uint64_t Session::SendDatagram(Store&& data) { packet = Packet::Create(env(), endpoint_.get(), remote_address_, - ngtcp2_conn_get_max_udp_payload_size(*this), + ngtcp2_conn_get_max_tx_udp_payload_size(*this), "datagram"); if (!packet) { last_error_ = QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL); @@ -961,6 +962,7 @@ void Session::ResumeStream(int64_t id) { void Session::ShutdownStream(int64_t id, QuicError error) { SendPendingDataScope send_scope(this); ngtcp2_conn_shutdown_stream(*this, + 0, id, error.type() == QuicError::Type::APPLICATION ? error.code() @@ -975,6 +977,7 @@ void Session::StreamDataBlocked(int64_t id) { void Session::ShutdownStreamWrite(int64_t id, QuicError code) { SendPendingDataScope send_scope(this); ngtcp2_conn_shutdown_stream_write(*this, + 0, id, code.type() == QuicError::Type::APPLICATION ? code.code() @@ -1007,11 +1010,11 @@ void Session::MemoryInfo(MemoryTracker* tracker) const { } bool Session::is_in_closing_period() const { - return ngtcp2_conn_is_in_closing_period(*this); + return ngtcp2_conn_in_closing_period(*this) != 0; } bool Session::is_in_draining_period() const { - return ngtcp2_conn_is_in_draining_period(*this); + return ngtcp2_conn_in_draining_period(*this) != 0; } bool Session::wants_session_ticket() const { @@ -1041,7 +1044,7 @@ uint64_t Session::max_data_left() const { } uint64_t Session::max_local_streams_uni() const { - return ngtcp2_conn_get_max_local_streams_uni(*this); + return ngtcp2_conn_get_streams_uni_left(*this); } uint64_t Session::max_local_streams_bidi() const { @@ -1094,30 +1097,35 @@ void Session::ExtendOffset(size_t amount) { void Session::UpdateDataStats() { if (state_->destroyed) return; - ngtcp2_conn_stat stat; - ngtcp2_conn_get_conn_stat(*this, &stat); - STAT_SET(Stats, bytes_in_flight, stat.bytes_in_flight); - STAT_SET( - Stats, congestion_recovery_start_ts, stat.congestion_recovery_start_ts); - STAT_SET(Stats, cwnd, stat.cwnd); - STAT_SET(Stats, delivery_rate_sec, stat.delivery_rate_sec); - STAT_SET(Stats, first_rtt_sample_ts, stat.first_rtt_sample_ts); - STAT_SET(Stats, initial_rtt, stat.initial_rtt); - STAT_SET( - Stats, last_tx_pkt_ts, reinterpret_cast(stat.last_tx_pkt_ts)); - STAT_SET(Stats, latest_rtt, stat.latest_rtt); - STAT_SET(Stats, loss_detection_timer, stat.loss_detection_timer); - STAT_SET(Stats, loss_time, reinterpret_cast(stat.loss_time)); - STAT_SET(Stats, max_udp_payload_size, stat.max_udp_payload_size); - STAT_SET(Stats, min_rtt, stat.min_rtt); - STAT_SET(Stats, pto_count, stat.pto_count); - STAT_SET(Stats, rttvar, stat.rttvar); - STAT_SET(Stats, smoothed_rtt, stat.smoothed_rtt); - STAT_SET(Stats, ssthresh, stat.ssthresh); + ngtcp2_conn_info info; + ngtcp2_conn_get_conn_info(*this, &info); + STAT_SET(Stats, bytes_in_flight, info.bytes_in_flight); + STAT_SET(Stats, cwnd, info.cwnd); + STAT_SET(Stats, latest_rtt, info.latest_rtt); + STAT_SET(Stats, min_rtt, info.min_rtt); + STAT_SET(Stats, rttvar, info.rttvar); + STAT_SET(Stats, smoothed_rtt, info.smoothed_rtt); + STAT_SET(Stats, ssthresh, info.ssthresh); STAT_SET( Stats, max_bytes_in_flight, - std::max(STAT_GET(Stats, max_bytes_in_flight), stat.bytes_in_flight)); + std::max(STAT_GET(Stats, max_bytes_in_flight), info.bytes_in_flight)); + + // TODO(@jasnell): Want to see if ngtcp2 provides an alternative way of + // getting these before removing them. Will handle that in one of the + // follow-up PRs STAT_SET( + // Stats, congestion_recovery_start_ts, + // info.congestion_recovery_start_ts); + // STAT_SET(Stats, delivery_rate_sec, info.delivery_rate_sec); + // STAT_SET(Stats, first_rtt_sample_ts, stat.first_rtt_sample_ts); + // STAT_SET(Stats, initial_rtt, info.initial_rtt); + // STAT_SET( + // Stats, last_tx_pkt_ts, + // reinterpret_cast(stat.last_tx_pkt_ts)); + // STAT_SET(Stats, loss_detection_timer, info.loss_detection_timer); + // STAT_SET(Stats, loss_time, reinterpret_cast(stat.loss_time)); + // STAT_SET(Stats, max_udp_payload_size, stat.max_udp_payload_size); + // STAT_SET(Stats, pto_count, stat.pto_count); } void Session::SendConnectionClose() { @@ -1254,7 +1262,7 @@ bool Session::HandshakeCompleted() { STAT_RECORD_TIMESTAMP(Stats, handshake_completed_at); if (!tls_context_.early_data_was_accepted()) - ngtcp2_conn_early_data_rejected(*this); + ngtcp2_conn_tls_early_data_rejected(*this); // When in a server session, handshake completed == handshake confirmed. if (is_server()) { @@ -1429,8 +1437,8 @@ void Session::EmitHandshakeComplete() { void Session::EmitPathValidation(PathValidationResult result, PathValidationFlags flags, - const SocketAddress& local_address, - const SocketAddress& remote_address) { + const ValidatedPath& newPath, + const std::optional& oldPath) { DCHECK(!is_destroyed()); if (!env()->can_call_into_js()) return; if (LIKELY(state_->path_validation == 0)) return; @@ -1451,15 +1459,18 @@ void Session::EmitPathValidation(PathValidationResult result, UNREACHABLE(); }; - Local argv[4] = { + Local argv[] = { resultToString(), - SocketAddressBase::Create(env(), - std::make_shared(local_address)) - ->object(), - SocketAddressBase::Create(env(), - std::make_shared(remote_address)) - ->object(), - v8::Boolean::New(isolate, flags.preferredAddress)}; + SocketAddressBase::Create(env(), newPath.local)->object(), + SocketAddressBase::Create(env(), newPath.remote)->object(), + Undefined(isolate), + Undefined(isolate), + Boolean::New(isolate, flags.preferredAddress)}; + + if (oldPath.has_value()) { + argv[3] = SocketAddressBase::Create(env(), oldPath->local)->object(); + argv[4] = SocketAddressBase::Create(env(), oldPath->remote)->object(); + } MakeCallback(state.session_path_validation_callback(), arraysize(argv), argv); } @@ -1664,7 +1675,7 @@ struct Session::Impl { } static int on_cid_status(ngtcp2_conn* conn, - int type, + ngtcp2_connection_id_status_type type, uint64_t seq, const ngtcp2_cid* cid, const uint8_t* token, @@ -1781,26 +1792,37 @@ struct Session::Impl { static int on_path_validation(ngtcp2_conn* conn, uint32_t flags, const ngtcp2_path* path, + const ngtcp2_path* old_path, ngtcp2_path_validation_result res, void* user_data) { NGTCP2_CALLBACK_SCOPE(session) bool flag_preferred_address = flags & NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR; + ValidatedPath newValidatedPath{ + std::make_shared(path->local.addr), + std::make_shared(path->remote.addr)}; + std::optional oldValidatedPath = std::nullopt; + if (old_path != nullptr) { + oldValidatedPath = + ValidatedPath{std::make_shared(old_path->local.addr), + std::make_shared(old_path->remote.addr)}; + } session->EmitPathValidation(static_cast(res), PathValidationFlags{flag_preferred_address}, - SocketAddress(path->local.addr), - SocketAddress(path->remote.addr)); + newValidatedPath, + oldValidatedPath); return NGTCP2_SUCCESS; } static int on_receive_crypto_data(ngtcp2_conn* conn, - ngtcp2_crypto_level crypto_level, + ngtcp2_encryption_level level, uint64_t offset, const uint8_t* data, size_t datalen, void* user_data) { NGTCP2_CALLBACK_SCOPE(session) - return session->tls_context().Receive(crypto_level, offset, data, datalen); + return session->tls_context().Receive( + static_cast(level), offset, data, datalen); } static int on_receive_datagram(ngtcp2_conn* conn, @@ -1810,13 +1832,14 @@ struct Session::Impl { void* user_data) { NGTCP2_CALLBACK_SCOPE(session) DatagramReceivedFlags f; - f.early = flags & NGTCP2_DATAGRAM_FLAG_EARLY; + f.early = flags & NGTCP2_DATAGRAM_FLAG_0RTT; session->DatagramReceived(data, datalen, f); return NGTCP2_SUCCESS; } static int on_receive_new_token(ngtcp2_conn* conn, - const ngtcp2_vec* token, + const uint8_t* token, + size_t tokenlen, void* user_data) { NGTCP2_CALLBACK_SCOPE(session) // We currently do nothing with this callback. @@ -1824,11 +1847,12 @@ struct Session::Impl { } static int on_receive_rx_key(ngtcp2_conn* conn, - ngtcp2_crypto_level level, + ngtcp2_encryption_level level, void* user_data) { NGTCP2_CALLBACK_SCOPE(session) - if (!session->is_server() && level == NGTCP2_CRYPTO_LEVEL_APPLICATION) { + if (!session->is_server() && (level == NGTCP2_ENCRYPTION_LEVEL_0RTT || + level == NGTCP2_ENCRYPTION_LEVEL_1RTT)) { if (!session->application().Start()) return NGTCP2_ERR_CALLBACK_FAILURE; } return NGTCP2_SUCCESS; @@ -1852,7 +1876,7 @@ struct Session::Impl { void* stream_user_data) { NGTCP2_CALLBACK_SCOPE(session) Stream::ReceiveDataFlags f; - f.early = flags & NGTCP2_STREAM_DATA_FLAG_EARLY; + f.early = flags & NGTCP2_STREAM_DATA_FLAG_0RTT; f.fin = flags & NGTCP2_STREAM_DATA_FLAG_FIN; if (stream_user_data == nullptr) { @@ -1864,7 +1888,7 @@ struct Session::Impl { stream.get(), data, datalen, f); } else { return ngtcp2_conn_shutdown_stream( - *session, stream_id, NGTCP2_APP_NOERROR) == 0 + *session, 0, stream_id, NGTCP2_APP_NOERROR) == 0 ? NGTCP2_SUCCESS : NGTCP2_ERR_CALLBACK_FAILURE; } @@ -1876,10 +1900,11 @@ struct Session::Impl { } static int on_receive_tx_key(ngtcp2_conn* conn, - ngtcp2_crypto_level level, + ngtcp2_encryption_level level, void* user_data) { NGTCP2_CALLBACK_SCOPE(session) - if (session->is_server() && level == NGTCP2_CRYPTO_LEVEL_APPLICATION) { + if (session->is_server() && (level == NGTCP2_ENCRYPTION_LEVEL_0RTT || + level == NGTCP2_ENCRYPTION_LEVEL_1RTT)) { if (!session->application().Start()) return NGTCP2_ERR_CALLBACK_FAILURE; } return NGTCP2_SUCCESS; @@ -1970,6 +1995,12 @@ struct Session::Impl { CHECK(crypto::CSPRNG(dest, destlen).is_ok()); } + static int on_early_data_rejected(ngtcp2_conn* conn, void* user_data) { + // TODO(@jasnell): Called when early data was rejected by server during the + // TLS handshake or client decided not to attempt early data. + return NGTCP2_SUCCESS; + } + static constexpr ngtcp2_callbacks CLIENT = { ngtcp2_crypto_client_initial_cb, nullptr, @@ -2009,7 +2040,8 @@ struct Session::Impl { on_stream_stop_sending, ngtcp2_crypto_version_negotiation_cb, on_receive_rx_key, - on_receive_tx_key}; + on_receive_tx_key, + on_early_data_rejected}; static constexpr ngtcp2_callbacks SERVER = { nullptr, @@ -2050,7 +2082,8 @@ struct Session::Impl { on_stream_stop_sending, ngtcp2_crypto_version_negotiation_cb, on_receive_rx_key, - on_receive_tx_key}; + on_receive_tx_key, + on_early_data_rejected}; }; #undef NGTCP2_CALLBACK_SCOPE diff --git a/src/quic/session.h b/src/quic/session.h index 21af8d801d550e..a844e2a3c2709f 100644 --- a/src/quic/session.h +++ b/src/quic/session.h @@ -349,10 +349,16 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source { void EmitDatagramStatus(uint64_t id, DatagramStatus status); void EmitHandshakeComplete(); void EmitKeylog(const char* line); + + struct ValidatedPath { + std::shared_ptr local; + std::shared_ptr remote; + }; + void EmitPathValidation(PathValidationResult result, PathValidationFlags flags, - const SocketAddress& local_address, - const SocketAddress& remote_address); + const ValidatedPath& newPath, + const std::optional& oldPath); void EmitSessionTicket(Store&& ticket); void EmitStream(BaseObjectPtr stream); void EmitVersionNegotiation(const ngtcp2_pkt_hd& hd, diff --git a/src/quic/streams.cc b/src/quic/streams.cc index 4cca65f56d900e..f20a160717e803 100644 --- a/src/quic/streams.cc +++ b/src/quic/streams.cc @@ -180,7 +180,7 @@ struct Stream::Impl { if (stream->is_destroyed()) return; stream->EndReadable(); Session::SendPendingDataScope send_scope(&stream->session()); - ngtcp2_conn_shutdown_stream_read(stream->session(), stream->id(), code); + ngtcp2_conn_shutdown_stream_read(stream->session(), 0, stream->id(), code); } // Sends a reset stream to the peer to tell it we will not be sending any @@ -205,7 +205,7 @@ struct Stream::Impl { stream->outbound_.reset(); stream->state_->reset = 1; Session::SendPendingDataScope send_scope(&stream->session()); - ngtcp2_conn_shutdown_stream_write(stream->session(), stream->id(), code); + ngtcp2_conn_shutdown_stream_write(stream->session(), 0, stream->id(), code); } static void SetPriority(const FunctionCallbackInfo& args) { @@ -959,7 +959,7 @@ void Stream::ReceiveStopSending(QuicError error) { // Note that this comes from *this* endpoint, not the other side. We handle it // if we haven't already shutdown our *receiving* side of the stream. if (is_destroyed() || state_->read_ended) return; - ngtcp2_conn_shutdown_stream_read(session(), id(), error.code()); + ngtcp2_conn_shutdown_stream_read(session(), 0, id(), error.code()); EndReadable(); } diff --git a/src/quic/tlscontext.cc b/src/quic/tlscontext.cc index efb8dd02b11f10..d60e4a7dbf3cea 100644 --- a/src/quic/tlscontext.cc +++ b/src/quic/tlscontext.cc @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include #include @@ -93,7 +93,7 @@ BaseObjectPtr InitializeSecureContext( ctx.reset(SSL_CTX_new(TLS_server_method())); SSL_CTX_set_app_data(ctx.get(), context); - if (ngtcp2_crypto_openssl_configure_server_context(ctx.get()) != 0) { + if (ngtcp2_crypto_quictls_configure_server_context(ctx.get()) != 0) { return BaseObjectPtr(); } @@ -123,7 +123,7 @@ BaseObjectPtr InitializeSecureContext( ctx.reset(SSL_CTX_new(TLS_client_method())); SSL_CTX_set_app_data(ctx.get(), context); - if (ngtcp2_crypto_openssl_configure_client_context(ctx.get()) != 0) { + if (ngtcp2_crypto_quictls_configure_client_context(ctx.get()) != 0) { return BaseObjectPtr(); } @@ -388,8 +388,7 @@ TLSContext::TLSContext(Environment* env, void TLSContext::Start() { ngtcp2_conn_set_tls_native_handle(*session_, ssl_.get()); - TransportParams tp(TransportParams::Type::ENCRYPTED_EXTENSIONS, - ngtcp2_conn_get_local_transport_params(*session_)); + TransportParams tp(ngtcp2_conn_get_local_transport_params(*session_)); Store store = tp.Encode(env_); if (store && store.length() > 0) { ngtcp2_vec vec = store; @@ -401,7 +400,7 @@ void TLSContext::Keylog(const char* line) const { session_->EmitKeylog(line); } -int TLSContext::Receive(ngtcp2_crypto_level crypto_level, +int TLSContext::Receive(TLSContext::EncryptionLevel level, uint64_t offset, const uint8_t* data, size_t datalen) { @@ -414,7 +413,7 @@ int TLSContext::Receive(ngtcp2_crypto_level crypto_level, // Internally, this passes the handshake data off to openssl for processing. // The handshake may or may not complete. int ret = ngtcp2_crypto_read_write_crypto_data( - *session_, crypto_level, data, datalen); + *session_, static_cast(level), data, datalen); switch (ret) { case 0: @@ -423,9 +422,9 @@ int TLSContext::Receive(ngtcp2_crypto_level crypto_level, // In either of following cases, the handshake is being paused waiting for // user code to take action (for instance OCSP requests or client hello // modification) - case NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_X509_LOOKUP: + case NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_X509_LOOKUP: [[fallthrough]]; - case NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_CLIENT_HELLO_CB: + case NGTCP2_CRYPTO_QUICTLS_ERR_TLS_WANT_CLIENT_HELLO_CB: return 0; } return ret; @@ -468,12 +467,6 @@ int TLSContext::VerifyPeerIdentity() { } void TLSContext::MaybeSetEarlySession(const SessionTicket& sessionTicket) { - TransportParams rtp(TransportParams::Type::ENCRYPTED_EXTENSIONS, - sessionTicket.transport_params()); - - // Ignore invalid remote transport parameters. - if (!rtp) return; - uv_buf_t buf = sessionTicket.ticket(); crypto::SSLSessionPointer ticket = crypto::GetTLSSession( reinterpret_cast(buf.base), buf.len); @@ -482,10 +475,17 @@ void TLSContext::MaybeSetEarlySession(const SessionTicket& sessionTicket) { if (!ticket || !SSL_SESSION_get_max_early_data(ticket.get())) return; // The early data will just be ignored if it's invalid. - if (crypto::SetTLSSession(ssl_, ticket)) { - ngtcp2_conn_set_early_remote_transport_params(*session_, rtp); - session_->SetStreamOpenAllowed(); - } + if (!crypto::SetTLSSession(ssl_, ticket)) return; + + ngtcp2_vec rtp = sessionTicket.transport_params(); + // Decode and attempt to set the early transport parameters configured + // for the early session. If non-zero is returned, decoding or setting + // failed, in which case we just ignore it. + if (ngtcp2_conn_decode_and_set_0rtt_transport_params( + *session_, rtp.base, rtp.len) != 0) + return; + + session_->SetStreamOpenAllowed(); } void TLSContext::MemoryInfo(MemoryTracker* tracker) const { diff --git a/src/quic/tlscontext.h b/src/quic/tlscontext.h index 82593269d3fe2c..b163a68cfa3e73 100644 --- a/src/quic/tlscontext.h +++ b/src/quic/tlscontext.h @@ -23,6 +23,13 @@ class Session; // convenience to help make the code more maintainable and understandable. class TLSContext final : public MemoryRetainer { public: + enum class EncryptionLevel { + INITIAL = NGTCP2_ENCRYPTION_LEVEL_INITIAL, + HANDSHAKE = NGTCP2_ENCRYPTION_LEVEL_HANDSHAKE, + ONERTT = NGTCP2_ENCRYPTION_LEVEL_1RTT, + ZERORTT = NGTCP2_ENCRYPTION_LEVEL_0RTT, + }; + static constexpr auto DEFAULT_CIPHERS = "TLS_AES_128_GCM_SHA256:" "TLS_AES_256_GCM_SHA384:" "TLS_CHACHA20_POLY1305_" @@ -118,7 +125,7 @@ class TLSContext final : public MemoryRetainer { // Called when a chunk of peer TLS handshake data is received. For every // chunk, we move the TLS handshake further along until it is complete. - int Receive(ngtcp2_crypto_level crypto_level, + int Receive(TLSContext::EncryptionLevel level, uint64_t offset, const uint8_t* data, size_t datalen); diff --git a/src/quic/transportparams.cc b/src/quic/transportparams.cc index 07c4ed45c50c5b..792396df499543 100644 --- a/src/quic/transportparams.cc +++ b/src/quic/transportparams.cc @@ -82,13 +82,13 @@ void TransportParams::Options::MemoryInfo(MemoryTracker* tracker) const { } } -TransportParams::TransportParams(Type type) : type_(type), ptr_(¶ms_) {} +TransportParams::TransportParams() : ptr_(¶ms_) {} -TransportParams::TransportParams(Type type, const ngtcp2_transport_params* ptr) - : type_(type), ptr_(ptr) {} +TransportParams::TransportParams(const ngtcp2_transport_params* ptr) + : ptr_(ptr) {} TransportParams::TransportParams(const Config& config, const Options& options) - : TransportParams(Type::ENCRYPTED_EXTENSIONS) { + : TransportParams() { ngtcp2_transport_params_default(¶ms_); params_.active_connection_id_limit = options.active_connection_id_limit; params_.initial_max_stream_data_bidi_local = @@ -104,7 +104,7 @@ TransportParams::TransportParams(const Config& config, const Options& options) params_.ack_delay_exponent = options.ack_delay_exponent; params_.max_datagram_frame_size = options.max_datagram_frame_size; params_.disable_active_migration = options.disable_active_migration ? 1 : 0; - params_.preferred_address_present = 0; + params_.preferred_addr_present = 0; params_.stateless_reset_token_present = 0; params_.retry_scid_present = 0; @@ -127,13 +127,10 @@ TransportParams::TransportParams(const Config& config, const Options& options) SetPreferredAddress(options.preferred_address_ipv6.value()); } -TransportParams::TransportParams(Type type, const ngtcp2_vec& vec) - : TransportParams(type) { - int ret = ngtcp2_decode_transport_params( - ¶ms_, - static_cast(type), - vec.base, - vec.len); +TransportParams::TransportParams(const ngtcp2_vec& vec, int version) + : TransportParams() { + int ret = ngtcp2_transport_params_decode_versioned( + version, ¶ms_, vec.base, vec.len); if (ret != 0) { ptr_ = nullptr; @@ -141,25 +138,22 @@ TransportParams::TransportParams(Type type, const ngtcp2_vec& vec) } } -Store TransportParams::Encode(Environment* env) { +Store TransportParams::Encode(Environment* env, int version) { if (ptr_ == nullptr) { error_ = QuicError::ForNgtcp2Error(NGTCP2_INTERNAL_ERROR); return Store(); } // Preflight to see how much storage we'll need. - ssize_t size = ngtcp2_encode_transport_params( - nullptr, 0, static_cast(type_), ¶ms_); + ssize_t size = + ngtcp2_transport_params_encode_versioned(nullptr, 0, version, ¶ms_); DCHECK_GT(size, 0); auto result = ArrayBuffer::NewBackingStore(env->isolate(), size); - auto ret = ngtcp2_encode_transport_params( - static_cast(result->Data()), - size, - static_cast(type_), - ¶ms_); + auto ret = ngtcp2_transport_params_encode_versioned( + static_cast(result->Data()), size, version, ¶ms_); if (ret != 0) { error_ = QuicError::ForNgtcp2Error(ret); @@ -171,24 +165,24 @@ Store TransportParams::Encode(Environment* env) { void TransportParams::SetPreferredAddress(const SocketAddress& address) { DCHECK(ptr_ == ¶ms_); - params_.preferred_address_present = 1; + params_.preferred_addr_present = 1; switch (address.family()) { case AF_INET: { const sockaddr_in* src = reinterpret_cast(address.data()); - memcpy(params_.preferred_address.ipv4_addr, + memcpy(¶ms_.preferred_addr.ipv4.sin_addr, &src->sin_addr, - sizeof(params_.preferred_address.ipv4_addr)); - params_.preferred_address.ipv4_port = address.port(); + sizeof(params_.preferred_addr.ipv4.sin_addr)); + params_.preferred_addr.ipv4.sin_port = address.port(); return; } case AF_INET6: { const sockaddr_in6* src = reinterpret_cast(address.data()); - memcpy(params_.preferred_address.ipv6_addr, + memcpy(¶ms_.preferred_addr.ipv6.sin6_addr, &src->sin6_addr, - sizeof(params_.preferred_address.ipv6_addr)); - params_.preferred_address.ipv6_port = address.port(); + sizeof(params_.preferred_addr.ipv6.sin6_addr)); + params_.preferred_addr.ipv6.sin6_port = address.port(); return; } } @@ -212,22 +206,18 @@ void TransportParams::GenerateStatelessResetToken(const Endpoint& endpoint, void TransportParams::GeneratePreferredAddressToken(Session* session) { DCHECK(ptr_ == ¶ms_); - if (params_.preferred_address_present) { + if (params_.preferred_addr_present) { session->config_.preferred_address_cid = session->new_cid(); - params_.preferred_address.cid = session->config_.preferred_address_cid; + params_.preferred_addr.cid = session->config_.preferred_address_cid; auto& endpoint = session->endpoint(); endpoint.AssociateStatelessResetToken( endpoint.GenerateNewStatelessResetToken( - params_.preferred_address.stateless_reset_token, + params_.preferred_addr.stateless_reset_token, session->config_.preferred_address_cid), session); } } -TransportParams::Type TransportParams::type() const { - return type_; -} - TransportParams::operator const ngtcp2_transport_params&() const { DCHECK_NOT_NULL(ptr_); return *ptr_; diff --git a/src/quic/transportparams.h b/src/quic/transportparams.h index b8fa7b2aec0984..237927e7cd3ed9 100644 --- a/src/quic/transportparams.h +++ b/src/quic/transportparams.h @@ -24,13 +24,11 @@ class Session; // should use when communicating with this session. class TransportParams final { public: - enum class Type { - CLIENT_HELLO = NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, - ENCRYPTED_EXTENSIONS = NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, - }; - static void Initialize(Environment* env, v8::Local target); + static constexpr int QUIC_TRANSPORT_PARAMS_V1 = NGTCP2_TRANSPORT_PARAMS_V1; + static constexpr int QUIC_TRANSPORT_PARAMS_VERSION = + NGTCP2_TRANSPORT_PARAMS_VERSION; static constexpr uint64_t DEFAULT_MAX_STREAM_DATA = 256 * 1024; static constexpr uint64_t DEFAULT_MAX_DATA = 1 * 1024 * 1024; static constexpr uint64_t DEFAULT_MAX_IDLE_TIMEOUT = 10; // seconds @@ -48,6 +46,8 @@ class TransportParams final { }; struct Options : public MemoryRetainer { + int transportParamsVersion = QUIC_TRANSPORT_PARAMS_V1; + // Set only on server Sessions, the preferred address communicates the IP // address and port that the server would prefer the client to use when // communicating with it. See the QUIC specification for more detail on how @@ -120,11 +120,11 @@ class TransportParams final { v8::Local value); }; - explicit TransportParams(Type type); + explicit TransportParams(); // Creates an instance of TransportParams wrapping the existing const // ngtcp2_transport_params pointer. - TransportParams(Type type, const ngtcp2_transport_params* ptr); + TransportParams(const ngtcp2_transport_params* ptr); TransportParams(const Config& config, const Options& options); @@ -132,15 +132,14 @@ class TransportParams final { // If the parameters cannot be successfully decoded, the error() // property will be set with an appropriate QuicError and the bool() // operator will return false. - TransportParams(Type type, const ngtcp2_vec& buf); + TransportParams(const ngtcp2_vec& buf, + int version = QUIC_TRANSPORT_PARAMS_V1); void GenerateSessionTokens(Session* session); void GenerateStatelessResetToken(const Endpoint& endpoint, const CID& cid); void GeneratePreferredAddressToken(Session* session); void SetPreferredAddress(const SocketAddress& address); - Type type() const; - operator const ngtcp2_transport_params&() const; operator const ngtcp2_transport_params*() const; @@ -151,10 +150,9 @@ class TransportParams final { // Returns an ArrayBuffer containing the encoded transport parameters. // If an error occurs during encoding, an empty shared_ptr will be returned // and the error() property will be set to an appropriate QuicError. - Store Encode(Environment* env); + Store Encode(Environment* env, int version = QUIC_TRANSPORT_PARAMS_V1); private: - Type type_; ngtcp2_transport_params params_{}; const ngtcp2_transport_params* ptr_; QuicError error_ = QuicError::TRANSPORT_NO_ERROR;