From 4ff06d667c6996a70aae98aba5a908022496fda5 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 2 Aug 2025 13:05:22 -0700 Subject: [PATCH 1/4] quic: few additional small comment edits in cid.h Signed-off-by: James M Snell --- src/quic/cid.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/quic/cid.h b/src/quic/cid.h index 16883e50f1637a..992d355b07aedc 100644 --- a/src/quic/cid.h +++ b/src/quic/cid.h @@ -19,10 +19,10 @@ namespace node::quic { // // Each peer in a QUIC session generates one or more CIDs that the *other* // peer will use to identify the session. When a QUIC client initiates a -// brand new session, it will initially generates a CID of its own (its +// brand new session, it will initially generate a CID of its own (its // source CID) and a random placeholder CID for the server (the original // destination CID). When the server receives the initial packet, it will -// generate its own source CID and use the clients source CID as the +// generate its own source CID and use the client's source CID as the // server's destination CID. // // Client Server @@ -30,9 +30,9 @@ namespace node::quic { // Source CID <====> Destination CID // Destination CID <====> Source CID // -// While the connection is being established, it is possible for either -// peer to generate additional CIDs that are also associated with the -// connection. +// While the connection is being established, or even while the connection +// is active, it is possible for either peer to generate additional CIDs that +// are also associated with the connection. class CID final : public MemoryRetainer { public: static constexpr size_t kMinLength = NGTCP2_MIN_CIDLEN; From 7a61f90aeef40e7f333dd1675139a233940a28db Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 2 Aug 2025 13:17:17 -0700 Subject: [PATCH 2/4] quic: update more of the quic to the new compile guard Signed-off-by: James M Snell --- node.gyp | 29 ++++---- src/quic/application.cc | 10 +-- src/quic/bindingdata.cc | 9 ++- src/quic/bindingdata.h | 30 ++++---- src/quic/endpoint.cc | 4 +- src/quic/logstream.cc | 8 ++- src/quic/logstream.h | 10 ++- src/quic/packet.cc | 18 ++--- src/quic/packet.h | 7 +- src/quic/preferredaddress.cc | 83 ++++++++++------------ src/quic/preferredaddress.h | 26 +++---- src/quic/session.cc | 12 ++-- src/quic/sessionticket.cc | 25 +++++-- src/quic/sessionticket.h | 2 - src/quic/tokens.cc | 8 ++- src/quic/tokens.h | 2 - src/quic/transportparams.cc | 8 ++- src/quic/transportparams.h | 2 - test/cctest/test_quic_error.cc | 2 - test/cctest/test_quic_preferredaddress.cc | 84 +++++++++++++++++++++++ test/cctest/test_quic_tokens.cc | 7 +- 21 files changed, 240 insertions(+), 146 deletions(-) create mode 100644 test/cctest/test_quic_preferredaddress.cc diff --git a/node.gyp b/node.gyp index 65b4a30c32f3bc..a2a14dc3119e27 100644 --- a/node.gyp +++ b/node.gyp @@ -185,8 +185,15 @@ 'src/udp_wrap.cc', 'src/util.cc', 'src/uv.cc', + 'src/quic/bindingdata.cc', 'src/quic/cid.cc', 'src/quic/data.cc', + 'src/quic/logstream.cc', + 'src/quic/packet.cc', + 'src/quic/preferredaddress.cc', + 'src/quic/sessionticket.cc', + 'src/quic/tokens.cc', +# 'src/quic/transportparams.cc', # headers to make for a more pleasant IDE experience 'src/aliased_buffer.h', 'src/aliased_buffer-inl.h', @@ -323,9 +330,16 @@ 'src/udp_wrap.h', 'src/util.h', 'src/util-inl.h', + 'src/quic/bindingdata.h', 'src/quic/cid.h', 'src/quic/data.h', 'src/quic/defs.h', + 'src/quic/logstream.h', + 'src/quic/packet.h', + 'src/quic/preferredaddress.h', + 'src/quic/sessionticket.h', + 'src/quic/tokens.h', +# 'src/quic/transportparams.h', 'src/quic/guard.h', ], 'node_crypto_sources': [ @@ -390,31 +404,17 @@ ], 'node_quic_sources': [ 'src/quic/application.cc', - 'src/quic/bindingdata.cc', 'src/quic/endpoint.cc', 'src/quic/http3.cc', - 'src/quic/logstream.cc', - 'src/quic/packet.cc', - 'src/quic/preferredaddress.cc', 'src/quic/session.cc', - 'src/quic/sessionticket.cc', 'src/quic/streams.cc', 'src/quic/tlscontext.cc', - 'src/quic/tokens.cc', - 'src/quic/transportparams.cc', 'src/quic/application.h', - 'src/quic/bindingdata.h', 'src/quic/endpoint.h', 'src/quic/http3.h', - 'src/quic/logstream.h', - 'src/quic/packet.h', - 'src/quic/preferredaddress.h', 'src/quic/session.h', - 'src/quic/sessionticket.h', 'src/quic/streams.h', 'src/quic/tlscontext.h', - 'src/quic/tokens.h', - 'src/quic/transportparams.h', 'src/quic/quic.cc', ], 'node_cctest_openssl_sources': [ @@ -423,6 +423,7 @@ 'test/cctest/test_node_crypto_env.cc', 'test/cctest/test_quic_cid.cc', 'test/cctest/test_quic_error.cc', + 'test/cctest/test_quic_preferredaddress.cc', 'test/cctest/test_quic_tokens.cc', ], 'node_cctest_inspector_sources': [ diff --git a/src/quic/application.cc b/src/quic/application.cc index e01abfb95ddaca..649785161ada9b 100644 --- a/src/quic/application.cc +++ b/src/quic/application.cc @@ -290,7 +290,7 @@ void Session::Application::SendPendingData() { // The stream_data is the next block of data from the application stream. if (GetStreamData(&stream_data) < 0) { Debug(session_, "Application failed to get stream data"); - packet->Done(UV_ECANCELED); + packet->CancelPacket(); session_->SetLastError(QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL)); return session_->Close(CloseMethod::SILENT); } @@ -357,7 +357,7 @@ void Session::Application::SendPendingData() { if (ndatalen >= 0 && !StreamCommit(&stream_data, ndatalen)) { Debug(session_, "Failed to commit stream data while writing packets"); - packet->Done(UV_ECANCELED); + packet->CancelPacket();; session_->SetLastError( QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL)); return session_->Close(CloseMethod::SILENT); @@ -371,11 +371,11 @@ void Session::Application::SendPendingData() { Debug(session_, "Application encountered error while writing packet: %s", ngtcp2_strerror(nwrite)); - packet->Done(UV_ECANCELED); + packet->CancelPacket(); session_->SetLastError(QuicError::ForNgtcp2Error(nwrite)); return session_->Close(CloseMethod::SILENT); } else if (ndatalen >= 0 && !StreamCommit(&stream_data, ndatalen)) { - packet->Done(UV_ECANCELED); + packet->CancelPacket(); session_->SetLastError(QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL)); return session_->Close(CloseMethod::SILENT); } @@ -394,7 +394,7 @@ void Session::Application::SendPendingData() { packet->Truncate(datalen); session_->Send(packet, path); } else { - packet->Done(UV_ECANCELED); + packet->CancelPacket(); } return; diff --git a/src/quic/bindingdata.cc b/src/quic/bindingdata.cc index 47305cf815057d..1fc40ee4393c37 100644 --- a/src/quic/bindingdata.cc +++ b/src/quic/bindingdata.cc @@ -1,4 +1,6 @@ -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#if HAVE_OPENSSL +#include "guard.h" +#ifndef OPENSSL_NO_QUIC #include "bindingdata.h" #include #include @@ -49,10 +51,12 @@ void BindingData::CheckAllocatedSize(size_t previous_size) const { } void BindingData::IncreaseAllocatedSize(size_t size) { + CHECK_GE(current_ngtcp2_memory_ + size, current_ngtcp2_memory_); current_ngtcp2_memory_ += size; } void BindingData::DecreaseAllocatedSize(size_t size) { + CHECK_LE(current_ngtcp2_memory_ - size, current_ngtcp2_memory_); current_ngtcp2_memory_ -= size; } @@ -220,4 +224,5 @@ void IllegalConstructor(const FunctionCallbackInfo& args) { } // namespace quic } // namespace node -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#endif // OPENSSL_NO_QUIC +#endif // HAVE_OPENSSL diff --git a/src/quic/bindingdata.h b/src/quic/bindingdata.h index 9d8ac0c6fb1f6d..0c83280d43a99d 100644 --- a/src/quic/bindingdata.h +++ b/src/quic/bindingdata.h @@ -1,7 +1,6 @@ #pragma once #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #include #include @@ -26,12 +25,12 @@ class Packet; // The FunctionTemplates the BindingData will store for us. #define QUIC_CONSTRUCTORS(V) \ V(endpoint) \ + V(http3application) \ V(logstream) \ V(packet) \ V(session) \ V(stream) \ - V(udp) \ - V(http3application) + V(udp) // The callbacks are persistent v8::Function references that are set in the // quic::BindingState used to communicate data and events back out to the JS @@ -39,19 +38,19 @@ class Packet; // internalBinding('quic') is first loaded. #define QUIC_JS_CALLBACKS(V) \ V(endpoint_close, EndpointClose) \ - V(session_new, SessionNew) \ V(session_close, SessionClose) \ V(session_datagram, SessionDatagram) \ V(session_datagram_status, SessionDatagramStatus) \ V(session_handshake, SessionHandshake) \ + V(session_new, SessionNew) \ + V(session_path_validation, SessionPathValidation) \ V(session_ticket, SessionTicket) \ V(session_version_negotiation, SessionVersionNegotiation) \ - V(session_path_validation, SessionPathValidation) \ + V(stream_blocked, StreamBlocked) \ V(stream_close, StreamClose) \ V(stream_created, StreamCreated) \ - V(stream_reset, StreamReset) \ V(stream_headers, StreamHeaders) \ - V(stream_blocked, StreamBlocked) \ + V(stream_reset, StreamReset) \ V(stream_trailers, StreamTrailers) // The various JS strings the implementation uses. @@ -64,10 +63,10 @@ class Packet; V(application_provider, "provider") \ V(bbr, "bbr") \ V(ca, "ca") \ - V(certs, "certs") \ V(cc_algorithm, "cc") \ - V(crl, "crl") \ + V(certs, "certs") \ V(ciphers, "ciphers") \ + V(crl, "crl") \ V(cubic, "cubic") \ V(disable_stateless_reset, "disableStatelessReset") \ V(enable_connect_protocol, "enableConnectProtocol") \ @@ -114,8 +113,8 @@ class Packet; V(qpack_max_dtable_capacity, "qpackMaxDTableCapacity") \ V(reject_unauthorized, "rejectUnauthorized") \ V(reno, "reno") \ - V(retry_token_expiration, "retryTokenExpiration") \ V(reset_token_secret, "resetTokenSecret") \ + V(retry_token_expiration, "retryTokenExpiration") \ V(rx_loss, "rxDiagnosticLoss") \ V(servername, "servername") \ V(session, "Session") \ @@ -150,6 +149,9 @@ class BindingData final static void RegisterExternalReferences(ExternalReferenceRegistry* registry); static BindingData& Get(Environment* env); + static inline BindingData& Get(Realm* realm) { + return Get(realm->env()); + } BindingData(Realm* realm, v8::Local object); DISALLOW_COPY_AND_MOVE(BindingData) @@ -179,6 +181,7 @@ class BindingData final bool in_ngtcp2_callback_scope = false; bool in_nghttp3_callback_scope = false; + size_t current_ngtcp2_memory_ = 0; // The following set up various storage and accessors for common strings, // construction templates, and callbacks stored on the BindingData. These @@ -205,8 +208,6 @@ class BindingData final QUIC_JS_CALLBACKS(V) #undef V - size_t current_ngtcp2_memory_ = 0; - #define V(name) v8::Global name##_constructor_template_; QUIC_CONSTRUCTORS(V) #undef V @@ -229,7 +230,7 @@ void IllegalConstructor(const v8::FunctionCallbackInfo& args); // The ngtcp2 and nghttp3 callbacks have certain restrictions // that forbid re-entry. We provide the following scopes for // use in those to help protect against it. -struct NgTcp2CallbackScope { +struct NgTcp2CallbackScope final { Environment* env; explicit NgTcp2CallbackScope(Environment* env); DISALLOW_COPY_AND_MOVE(NgTcp2CallbackScope) @@ -237,7 +238,7 @@ struct NgTcp2CallbackScope { static bool in_ngtcp2_callback(Environment* env); }; -struct NgHttp3CallbackScope { +struct NgHttp3CallbackScope final { Environment* env; explicit NgHttp3CallbackScope(Environment* env); DISALLOW_COPY_AND_MOVE(NgHttp3CallbackScope) @@ -268,5 +269,4 @@ struct CallbackScope final : public CallbackScopeBase { } // namespace node::quic -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/quic/endpoint.cc b/src/quic/endpoint.cc index 5ffb7e94b8e0e5..c5110532254c38 100644 --- a/src/quic/endpoint.cc +++ b/src/quic/endpoint.cc @@ -751,14 +751,14 @@ void Endpoint::Send(const BaseObjectPtr& packet) { // dropped. This can happen to any type of packet. We use this only in // testing to test various reliability issues. if (is_diagnostic_packet_loss(options_.tx_loss)) [[unlikely]] { - packet->Done(0); + packet->Done(); // Simulating tx packet loss return; } #endif // DEBUG if (is_closed() || is_closing() || packet->length() == 0) { - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return; } Debug(this, "Sending %s", packet->ToString()); diff --git a/src/quic/logstream.cc b/src/quic/logstream.cc index 149ceaf0cdee94..aafc6fcee497af 100644 --- a/src/quic/logstream.cc +++ b/src/quic/logstream.cc @@ -1,5 +1,6 @@ -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC - +#if HAVE_OPENSSL +#include "guard.h" +#ifndef OPENSSL_NO_QUIC #include "logstream.h" #include #include @@ -149,4 +150,5 @@ void LogStream::ensure_space(size_t amt) { } // namespace quic } // namespace node -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#endif // OPENSSL_NO_QUIC +#endif // HAVE_OPENSSL diff --git a/src/quic/logstream.h b/src/quic/logstream.h index b5f2b59e3aa747..916467b6943cab 100644 --- a/src/quic/logstream.h +++ b/src/quic/logstream.h @@ -1,19 +1,18 @@ #pragma once #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #include #include #include #include -#include +#include namespace node::quic { // The LogStream is a utility that the QUIC impl uses to publish both QLog // and Keylog diagnostic data (one instance for each). -class LogStream : public AsyncWrap, public StreamBase { +class LogStream final : public AsyncWrap, public StreamBase { public: static v8::Local GetConstructorTemplate( Environment* env); @@ -22,7 +21,7 @@ class LogStream : public AsyncWrap, public StreamBase { LogStream(Environment* env, v8::Local obj); - enum class EmitOption { + enum class EmitOption : uint8_t { NONE, FIN, }; @@ -61,10 +60,10 @@ class LogStream : public AsyncWrap, public StreamBase { uv_buf_t buf; }; size_t total_ = 0; + std::list buffer_; bool fin_seen_ = false; bool ended_ = false; bool reading_ = false; - std::deque buffer_; // The value here is fairly arbitrary. Once we get everything // fully implemented and start working with this, we might @@ -77,5 +76,4 @@ class LogStream : public AsyncWrap, public StreamBase { } // namespace node::quic -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/quic/packet.cc b/src/quic/packet.cc index df6e7978ec6413..d91975a2ca5b77 100644 --- a/src/quic/packet.cc +++ b/src/quic/packet.cc @@ -1,5 +1,6 @@ -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC - +#if HAVE_OPENSSL +#include "guard.h" +#ifndef OPENSSL_NO_QUIC #include "packet.h" #include #include @@ -260,7 +261,7 @@ BaseObjectPtr Packet::CreateRetryPacket( vec.base, vec.len); if (nwrite <= 0) { - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return {}; } packet->Truncate(static_cast(nwrite)); @@ -281,7 +282,7 @@ BaseObjectPtr Packet::CreateConnectionClosePacket( ssize_t nwrite = ngtcp2_conn_write_connection_close( conn, nullptr, nullptr, vec.base, vec.len, error, uv_hrtime()); if (nwrite < 0) { - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return {}; } packet->Truncate(static_cast(nwrite)); @@ -312,7 +313,7 @@ BaseObjectPtr Packet::CreateImmediateConnectionClosePacket( nullptr, 0); if (nwrite <= 0) { - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return {}; } packet->Truncate(static_cast(nwrite)); @@ -349,7 +350,7 @@ BaseObjectPtr Packet::CreateStatelessResetPacket( ssize_t nwrite = ngtcp2_pkt_write_stateless_reset( vec.base, pktlen, token, random, kRandlen); if (nwrite <= static_cast(kMinStatelessResetLen)) { - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return {}; } @@ -407,7 +408,7 @@ BaseObjectPtr Packet::CreateVersionNegotiationPacket( sv, arraysize(sv)); if (nwrite <= 0) { - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return {}; } packet->Truncate(static_cast(nwrite)); @@ -417,4 +418,5 @@ BaseObjectPtr Packet::CreateVersionNegotiationPacket( } // namespace quic } // namespace node -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#endif // OPENSSL_NO_QUIC +#endif // HAVE_OPENSSL diff --git a/src/quic/packet.h b/src/quic/packet.h index ae6f76272e0156..955a0bb3dacaf2 100644 --- a/src/quic/packet.h +++ b/src/quic/packet.h @@ -1,7 +1,6 @@ #pragma once #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #include #include @@ -20,7 +19,7 @@ namespace node::quic { -struct PathDescriptor { +struct PathDescriptor final { uint32_t version; const CID& dcid; const CID& scid; @@ -136,7 +135,8 @@ class Packet final : public ReqWrap { const PathDescriptor& path_descriptor); // Called when the packet is done being sent. - void Done(int status); + void Done(int status = 0); + inline void CancelPacket() { Done(UV_ECANCELED); } private: static BaseObjectPtr FromFreeList(Environment* env, @@ -151,5 +151,4 @@ class Packet final : public ReqWrap { } // namespace node::quic -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/quic/preferredaddress.cc b/src/quic/preferredaddress.cc index 2251851e9d93ae..fd8a02d23344c5 100644 --- a/src/quic/preferredaddress.cc +++ b/src/quic/preferredaddress.cc @@ -1,7 +1,10 @@ -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC - +#if HAVE_OPENSSL +#include "guard.h" +#ifndef OPENSSL_NO_QUIC +#include "cid.h" #include "preferredaddress.h" #include +#include #include #include #include @@ -14,8 +17,6 @@ namespace node { using v8::Just; using v8::Local; using v8::Maybe; -using v8::Nothing; -using v8::Uint32; using v8::Value; namespace quic { @@ -24,21 +25,20 @@ namespace { template std::optional get_address_info( const ngtcp2_preferred_addr& paddr) { + PreferredAddress::AddressInfo address; + address.family = FAMILY; if constexpr (FAMILY == AF_INET) { if (!paddr.ipv4_present) return std::nullopt; - PreferredAddress::AddressInfo address; - address.family = FAMILY; address.port = paddr.ipv4.sin_port; - if (uv_inet_ntop( - FAMILY, &paddr.ipv4.sin_addr, address.host, sizeof(address.host)) == + if (uv_inet_ntop(FAMILY, + &paddr.ipv4.sin_addr, + address.host, + sizeof(address.host)) == 0) { address.address = address.host; } - return address; } else { if (!paddr.ipv6_present) return std::nullopt; - PreferredAddress::AddressInfo address; - address.family = FAMILY; address.port = paddr.ipv6.sin6_port; if (uv_inet_ntop(FAMILY, &paddr.ipv6.sin6_addr, @@ -46,29 +46,22 @@ std::optional get_address_info( sizeof(address.host)) == 0) { address.address = address.host; } - return address; } + return address; } template void copy_to_transport_params(ngtcp2_transport_params* params, const sockaddr* addr) { - params->preferred_addr_present = true; + params->preferred_addr_present = 1; if constexpr (FAMILY == AF_INET) { - const auto* src = reinterpret_cast(addr); - params->preferred_addr.ipv4.sin_port = SocketAddress::GetPort(addr); - memcpy(¶ms->preferred_addr.ipv4.sin_addr, - &src->sin_addr, - sizeof(params->preferred_addr.ipv4.sin_addr)); + params->preferred_addr.ipv4_present = 1; + memcpy(¶ms->preferred_addr.ipv4, addr, sizeof(sockaddr_in)); } else { DCHECK_EQ(FAMILY, AF_INET6); - const auto* src = reinterpret_cast(addr); - params->preferred_addr.ipv6.sin6_port = SocketAddress::GetPort(addr); - memcpy(¶ms->preferred_addr.ipv6.sin6_addr, - &src->sin6_addr, - sizeof(params->preferred_addr.ipv4.sin_addr)); + params->preferred_addr.ipv6_present = 1; + memcpy(¶ms->preferred_addr.ipv6, addr, sizeof(sockaddr_in6)); } - UNREACHABLE(); } bool resolve(const PreferredAddress::AddressInfo& address, @@ -85,13 +78,7 @@ bool resolve(const PreferredAddress::AddressInfo& address, req, nullptr, address.host, - // TODO(@jasnell): The to_string here is not really - // the most performant way of converting the uint16_t - // port into a string. Depending on execution count, - // the potential cost here could be mitigated with a - // more efficient conversion. For now, however, this - // works. - std::to_string(address.port).c_str(), + std::to_string(address.port).data(), &hints) == 0 && req->addrinfo != nullptr; } @@ -144,30 +131,32 @@ void PreferredAddress::Set(ngtcp2_transport_params* params, Maybe PreferredAddress::tryGetPolicy( Environment* env, Local value) { - if (value->IsUndefined()) { - return Just(Policy::USE_PREFERRED); - } - if (value->IsUint32()) { - switch (value.As()->Value()) { - case PREFERRED_ADDRESS_IGNORE: - return Just(Policy::IGNORE_PREFERRED); - case PREFERRED_ADDRESS_USE: - return Just(Policy::USE_PREFERRED); - default: - UNREACHABLE("Unreachable"); - } - } - THROW_ERR_INVALID_ARG_VALUE(env, "invalid preferred address policy"); - return Nothing(); + return value->IsUndefined() + ? Just(Policy::USE_PREFERRED) + : Just(FromV8Value(value)); } void PreferredAddress::Initialize(Environment* env, Local target) { + // The QUIC_* constants are expected to be exported out to be used on + // the JavaScript side of the API. + static constexpr auto PREFERRED_ADDRESS_USE = + static_cast(Policy::USE_PREFERRED); + static constexpr auto PREFERRED_ADDRESS_IGNORE = + static_cast(Policy::IGNORE_PREFERRED); + static constexpr auto DEFAULT_PREFERRED_ADDRESS_POLICY = + static_cast(Policy::USE_PREFERRED); + NODE_DEFINE_CONSTANT(target, PREFERRED_ADDRESS_IGNORE); NODE_DEFINE_CONSTANT(target, PREFERRED_ADDRESS_USE); NODE_DEFINE_CONSTANT(target, DEFAULT_PREFERRED_ADDRESS_POLICY); } +const CID PreferredAddress::cid() const { + return CID(&paddr_->cid); +} + } // namespace quic } // namespace node -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#endif // OPENSSL_NO_QUIC +#endif // HAVE_OPENSSL diff --git a/src/quic/preferredaddress.h b/src/quic/preferredaddress.h index 369c09c8463dc5..0a441fefb07a46 100644 --- a/src/quic/preferredaddress.h +++ b/src/quic/preferredaddress.h @@ -1,11 +1,11 @@ #pragma once #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #include #include #include +#include #include #include #include "defs.h" @@ -18,7 +18,7 @@ namespace node::quic { // the preferred address to be selected. class PreferredAddress final { public: - enum class Policy : uint32_t { + enum class Policy : uint8_t { // Ignore the server-advertised preferred address. IGNORE_PREFERRED, // Use the server-advertised preferred address. @@ -27,17 +27,16 @@ class PreferredAddress final { static v8::Maybe tryGetPolicy(Environment* env, v8::Local value); - - // The QUIC_* constants are expected to be exported out to be used on - // the JavaScript side of the API. - static constexpr auto PREFERRED_ADDRESS_USE = - static_cast(Policy::USE_PREFERRED); - static constexpr auto PREFERRED_ADDRESS_IGNORE = - static_cast(Policy::IGNORE_PREFERRED); - static constexpr auto DEFAULT_PREFERRED_ADDRESS_POLICY = - static_cast(Policy::USE_PREFERRED); + static inline v8::Maybe tryGetPolicy(Realm* realm, + v8::Local value) { + return tryGetPolicy(realm->env(), value); + } static void Initialize(Environment* env, v8::Local target); + static inline void Initialize(Realm* realm, + v8::Local target) { + return Initialize(realm->env(), target); + } struct AddressInfo final { char host[NI_MAXHOST]; @@ -49,12 +48,16 @@ class PreferredAddress final { explicit PreferredAddress(ngtcp2_path* dest, const ngtcp2_preferred_addr* paddr); DISALLOW_COPY_AND_MOVE(PreferredAddress) + void* operator new(size_t) = delete; + void operator delete(void*) = delete; void Use(const AddressInfo& address); std::optional ipv4() const; std::optional ipv6() const; + const CID cid() const; + // Set the preferred address in the transport params. // The address family (ipv4 or ipv6) will be automatically // detected from the given addr. Any other address family @@ -68,5 +71,4 @@ class PreferredAddress final { } // namespace node::quic -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/quic/session.cc b/src/quic/session.cc index 416dc9c16d756b..194a6169f186ed 100644 --- a/src/quic/session.cc +++ b/src/quic/session.cc @@ -1698,7 +1698,7 @@ void Session::Send(const BaseObjectPtr& packet) { DCHECK(!is_in_draining_period()); if (!can_send_packets()) [[unlikely]] { - return packet->Done(UV_ECANCELED); + return packet->CancelPacket(); } Debug(this, "Session is sending %s", packet->ToString()); @@ -1799,7 +1799,7 @@ uint64_t Session::SendDatagram(Store&& data) { // not fit. Since datagrams are best effort, we are going to abandon // the attempt and just return. CHECK_EQ(accepted, 0); - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return 0; } case NGTCP2_ERR_WRITE_MORE: { @@ -1809,13 +1809,13 @@ uint64_t Session::SendDatagram(Store&& data) { case NGTCP2_ERR_INVALID_STATE: { // The remote endpoint does not want to accept datagrams. That's ok, // just return 0. - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return 0; } case NGTCP2_ERR_INVALID_ARGUMENT: { // The datagram is too large. That should have been caught above but // that's ok. We'll just abandon the attempt and return. - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return 0; } case NGTCP2_ERR_PKT_NUM_EXHAUSTED: { @@ -1829,7 +1829,7 @@ uint64_t Session::SendDatagram(Store&& data) { break; } } - packet->Done(UV_ECANCELED); + packet->CancelPacket(); SetLastError(QuicError::ForTransport(nwrite)); Close(CloseMethod::SILENT); return 0; @@ -2260,7 +2260,7 @@ void Session::SendConnectionClose() { uv_hrtime()); if (nwrite < 0) [[unlikely]] { - packet->Done(UV_ECANCELED); + packet->CancelPacket(); return ErrorAndSilentClose(); } diff --git a/src/quic/sessionticket.cc b/src/quic/sessionticket.cc index 829a49c7d54533..1648fca900db70 100644 --- a/src/quic/sessionticket.cc +++ b/src/quic/sessionticket.cc @@ -1,5 +1,6 @@ -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC - +#if HAVE_OPENSSL +#include "guard.h" +#ifndef OPENSSL_NO_QUIC #include "sessionticket.h" #include #include @@ -44,7 +45,10 @@ Maybe SessionTicket::FromV8Value(Environment* env, return Nothing(); } - Store content(value.As()); + auto view = value.As(); + Store content(view->Buffer()->GetBackingStore(), + view->ByteLength(), + view->ByteOffset()); ngtcp2_vec vec = content; ValueDeserializer des(env->isolate(), vec.base, vec.len); @@ -76,8 +80,16 @@ Maybe SessionTicket::FromV8Value(Environment* env, return Nothing(); } - return Just(SessionTicket(Store(ticket.As()), - Store(transport_params.As()))); + auto ticketview = ticket.As(); + auto transport_params_view = transport_params.As(); + + return Just(SessionTicket( + Store(ticketview->Buffer()->GetBackingStore(), + ticketview->ByteLength(), + ticketview->ByteOffset()), + Store(transport_params_view->Buffer()->GetBackingStore(), + transport_params_view->ByteLength(), + transport_params_view->ByteOffset()))); } MaybeLocal SessionTicket::encode(Environment* env) const { @@ -172,4 +184,5 @@ SessionTicket::AppData::Status SessionTicket::AppData::Extract(SSL* ssl) { } // namespace quic } // namespace node -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#endif // OPENSSL_NO_QUIC +#endif // HAVE_OPENSSL diff --git a/src/quic/sessionticket.h b/src/quic/sessionticket.h index bdfd38be72d22a..8234b0ae1208f0 100644 --- a/src/quic/sessionticket.h +++ b/src/quic/sessionticket.h @@ -1,7 +1,6 @@ #pragma once #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #include #include @@ -104,5 +103,4 @@ class SessionTicket::AppData final { } // namespace node::quic -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/quic/tokens.cc b/src/quic/tokens.cc index d3ebde6225da56..a2653ab746e09f 100644 --- a/src/quic/tokens.cc +++ b/src/quic/tokens.cc @@ -1,5 +1,6 @@ -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC - +#if HAVE_OPENSSL +#include "guard.h" +#ifndef OPENSSL_NO_QUIC #include "tokens.h" #include #include @@ -301,4 +302,5 @@ RegularToken::operator const char*() const { } // namespace node::quic -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#endif // OPENSSL_NO_QUIC +#endif // HAVE_OPENSSL diff --git a/src/quic/tokens.h b/src/quic/tokens.h index 4f9f25dd12324b..5949cd58640d15 100644 --- a/src/quic/tokens.h +++ b/src/quic/tokens.h @@ -1,7 +1,6 @@ #pragma once #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #include #include @@ -252,5 +251,4 @@ class RegularToken final : public MemoryRetainer { } // namespace node::quic -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/quic/transportparams.cc b/src/quic/transportparams.cc index 208b0246c825e2..bab1c192249aa5 100644 --- a/src/quic/transportparams.cc +++ b/src/quic/transportparams.cc @@ -1,5 +1,6 @@ -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC - +#if HAVE_OPENSSL +#include "guard.h" +#ifndef OPENSSL_NO_QUIC #include "transportparams.h" #include #include @@ -291,4 +292,5 @@ void TransportParams::Initialize(Environment* env, Local target) { } // namespace quic } // namespace node -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#endif // OPENSSL_NO_QUIC +#endif // HAVE_OPENSSL diff --git a/src/quic/transportparams.h b/src/quic/transportparams.h index 77f367deaa4d41..839ab066bee668 100644 --- a/src/quic/transportparams.h +++ b/src/quic/transportparams.h @@ -1,7 +1,6 @@ #pragma once #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #include #include @@ -162,5 +161,4 @@ class TransportParams final { } // namespace node::quic -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/test/cctest/test_quic_error.cc b/test/cctest/test_quic_error.cc index b3677603c4a0d3..70c18db54fcc93 100644 --- a/test/cctest/test_quic_error.cc +++ b/test/cctest/test_quic_error.cc @@ -113,8 +113,6 @@ TEST(QuicError, InternalError) { CHECK_EQ(err.type(), QuicError::Type::TRANSPORT); CHECK_EQ(err.reason(), "a reason"); CHECK_EQ(err.ToString(), "QuicError(transport) 1: a reason"); - - printf("%s\n", QuicError::INTERNAL_ERROR.ToString().c_str()); CHECK_EQ(err, QuicError::INTERNAL_ERROR); } diff --git a/test/cctest/test_quic_preferredaddress.cc b/test/cctest/test_quic_preferredaddress.cc new file mode 100644 index 00000000000000..f929569bd149fd --- /dev/null +++ b/test/cctest/test_quic_preferredaddress.cc @@ -0,0 +1,84 @@ +#if HAVE_OPENSSL +#include "quic/guard.h" +#ifndef OPENSSL_NO_QUIC +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using node::quic::CID; +using node::quic::Path; +using node::quic::PreferredAddress; +using node::SocketAddress; + +TEST(PreferredAddress, Basic) { + const auto cid = CID::Factory::random().Generate(); + ngtcp2_preferred_addr paddr; + paddr.cid = cid; + + sockaddr_storage storage; + sockaddr_storage storage6; + SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage); + SocketAddress::ToSockAddr(AF_INET6, "2001:db8::1", 123, &storage6); + + memcpy(&paddr.ipv4, &storage, sizeof(sockaddr_in)); + paddr.ipv4.sin_family = AF_INET; + paddr.ipv4_present = 1; + paddr.ipv6_present = 0; + + sockaddr_storage storage1; + sockaddr_storage storage2; + SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage1); + SocketAddress::ToSockAddr(AF_INET, "123.123.123.124", 443, &storage2); + SocketAddress addr1(reinterpret_cast(&storage1)); + SocketAddress addr2(reinterpret_cast(&storage2)); + Path path(addr1, addr2); + + PreferredAddress preferred_address(&path, &paddr); + + CHECK_EQ(preferred_address.ipv4().has_value(), true); + CHECK_EQ(preferred_address.ipv6().has_value(), false); + + const auto ipv4 = preferred_address.ipv4().value(); + CHECK_EQ(ipv4.family, AF_INET); + CHECK_EQ(htons(ipv4.port), 443); + CHECK_EQ(ipv4.address, "123.123.123.123"); + + memcpy(&paddr.ipv6, &storage6, sizeof(sockaddr_in6)); + paddr.ipv6_present = 1; + + CHECK_EQ(preferred_address.ipv6().has_value(), true); + const auto ipv6 = preferred_address.ipv6().value(); + CHECK_EQ(ipv6.family, AF_INET6); + CHECK_EQ(htons(ipv6.port), 123); + CHECK_EQ(ipv6.address, "2001:db8::1"); + + CHECK_EQ(preferred_address.cid(), cid); +} + +TEST(PreferredAddress, SetTransportParams) { + sockaddr_storage storage1; + SocketAddress::ToSockAddr(AF_INET, "123.123.123.123", 443, &storage1); + SocketAddress addr1(reinterpret_cast(&storage1)); + Path path(addr1, addr1); + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + PreferredAddress::Set(¶ms, addr1.data()); + + CHECK_EQ(params.preferred_addr_present, 1); + + PreferredAddress paddr2(&path, ¶ms.preferred_addr); + CHECK_EQ(paddr2.ipv4().has_value(), true); + const auto ipv4_2 = paddr2.ipv4().value(); + CHECK_EQ(ipv4_2.family, AF_INET); + CHECK_EQ(htons(ipv4_2.port), 443); + CHECK_EQ(ipv4_2.address, "123.123.123.123"); +} +#endif // OPENSSL_NO_QUIC +#endif // HAVE_OPENSSL diff --git a/test/cctest/test_quic_tokens.cc b/test/cctest/test_quic_tokens.cc index a480c003cc6f3a..53e4d96599c3c7 100644 --- a/test/cctest/test_quic_tokens.cc +++ b/test/cctest/test_quic_tokens.cc @@ -1,4 +1,6 @@ -#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#if HAVE_OPENSSL +#include "quic/guard.h" +#ifndef OPENSSL_NO_QUIC #include #include #include @@ -166,4 +168,5 @@ TEST(RegularToken, Basic) { CHECK(!token.Validate(NGTCP2_PROTO_VER_MAX, address, secret, 10000000000)); } -#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC +#endif // OPENSSL_NO_QUIC +#endif // HAVE_OPENSSL From 4d7ef498ddbcb07df0f33cd11bc528a0e9cd4bb9 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sun, 3 Aug 2025 06:16:03 -0700 Subject: [PATCH 3/4] quic: multiple fixups and updates Signed-off-by: James M Snell --- configure.py | 11 - doc/api/cli.md | 20 ++ doc/node-config-schema.json | 3 + doc/node.1 | 3 + lib/internal/bootstrap/node.js | 7 + node.gyp | 37 +- node.gypi | 6 - src/node_binding.h | 3 +- src/node_builtins.cc | 5 +- src/node_external_reference.h | 3 +- src/node_metadata.cc | 7 +- src/node_metadata.h | 5 +- src/node_options.cc | 5 +- src/node_options.h | 3 +- src/quic/application.cc | 32 +- src/quic/application.h | 18 +- src/quic/bindingdata.h | 4 +- src/quic/cid.h | 1 - src/quic/data.cc | 33 +- src/quic/data.h | 45 ++- src/quic/endpoint.cc | 14 +- src/quic/endpoint.h | 2 - src/quic/guard.h | 2 +- src/quic/http3.cc | 35 +- src/quic/http3.h | 2 - src/quic/preferredaddress.cc | 17 +- src/quic/preferredaddress.h | 3 +- src/quic/quic.cc | 7 +- src/quic/session.cc | 44 ++- src/quic/session.h | 2 - src/quic/sessionticket.cc | 53 ++- src/quic/streams.cc | 17 +- src/quic/streams.h | 6 +- src/quic/tlscontext.cc | 338 ++++++++++++++---- src/quic/tlscontext.h | 80 ++++- src/quic/transportparams.cc | 21 +- src/quic/transportparams.h | 4 +- test/cctest/test_quic_preferredaddress.cc | 2 +- test/common/index.js | 2 +- test/parallel/test-cli-node-options-docs.js | 3 + ...rocess-env-allowed-flags-are-documented.js | 4 +- test/parallel/test-process-features.js | 1 + test/parallel/test-process-versions.js | 2 + test/parallel/test-quic-handshake.js | 96 ++--- 44 files changed, 647 insertions(+), 361 deletions(-) diff --git a/configure.py b/configure.py index 7dcfa9dface9d7..fd7450d327d35c 100755 --- a/configure.py +++ b/configure.py @@ -846,12 +846,6 @@ # End dummy list. -parser.add_argument('--with-quic', - action='store_true', - dest='quic', - default=None, - help='build with QUIC support') - parser.add_argument('--without-ssl', action='store_true', dest='without_ssl', @@ -1826,7 +1820,6 @@ def configure_openssl(o): variables['node_shared_ngtcp2'] = b(options.shared_ngtcp2) variables['node_shared_nghttp3'] = b(options.shared_nghttp3) variables['openssl_is_fips'] = b(options.openssl_is_fips) - variables['node_quic'] = b(options.quic) variables['node_fipsinstall'] = b(False) if options.openssl_no_asm: @@ -1888,10 +1881,6 @@ def without_ssl_error(option): if options.openssl_is_fips and not options.shared_openssl: variables['node_fipsinstall'] = b(True) - variables['openssl_quic'] = b(options.quic) - if options.quic: - o['defines'] += ['NODE_OPENSSL_HAS_QUIC'] - configure_library('openssl', o) o['variables']['openssl_version'] = get_openssl_version(o) diff --git a/doc/api/cli.md b/doc/api/cli.md index 311a2ca2674453..149aea5239d689 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1135,6 +1135,16 @@ If the ES module being `require()`'d contains top-level `await`, this flag allows Node.js to evaluate the module, try to locate the top-level awaits, and print their location to help users find them. +### `--experimental-quic` + + + +> Stability: 1.1 - Active development + +Enable experimental support for the QUIC protocol. + ### `--experimental-require-module` + +> Stability: 1.1 - Active Development + +Use this flag to disable QUIC. + ### `--no-experimental-repl-await` - -> Stability: 1.1 - Active Development - -Use this flag to disable QUIC. - ### `--no-experimental-repl-await`