quic: multiple fixups and updates

Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/59342
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
This commit is contained in:
James M Snell committed 2025-09-07 19:29:01 -07:00
1 parent a6c5d27739
commit cd9fd09a27
44 files changed
+646 -360

No files matched your search

+2 -1
View File
@@ -10,6 +10,7 @@
#include "node.h"
#define NAPI_EXPERIMENTAL
#include "node_api.h"
#include "quic/guard.h"
#include "uv.h"
enum {
@@ -30,7 +31,7 @@ static_assert(static_cast<int>(NM_F_LINKED) ==
#define NODE_BUILTIN_ICU_BINDINGS(V)
#endif
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#if HAVE_OPENSSL && OPENSSL_NO_QUIC != 1
#define NODE_BUILTIN_QUIC_BINDINGS(V) V(quic)
#else
#define NODE_BUILTIN_QUIC_BINDINGS(V)
+3 -2
View File
@@ -4,6 +4,7 @@
#include "node_external_reference.h"
#include "node_internals.h"
#include "node_threadsafe_cow-inl.h"
#include "quic/guard.h"
#include "simdutf.h"
#include "util-inl.h"
@@ -137,10 +138,10 @@ BuiltinLoader::BuiltinCategories BuiltinLoader::GetBuiltinCategories() const {
"internal/http2/core", "internal/http2/compat",
"internal/streams/lazy_transform",
#endif // !HAVE_OPENSSL
#if !NODE_OPENSSL_HAS_QUIC
#ifndef OPENSSL_NO_QUIC
"internal/quic/quic", "internal/quic/symbols", "internal/quic/stats",
"internal/quic/state",
#endif // !NODE_OPENSSL_HAS_QUIC
#endif // !OPENSSL_NO_QUIC
"quic", // Experimental.
"sqlite", // Experimental.
"sys", // Deprecated.
+2 -1
View File
@@ -5,6 +5,7 @@
#include <cinttypes>
#include <vector>
#include "quic/guard.h"
#include "v8-fast-api-calls.h"
#include "v8.h"
@@ -135,7 +136,7 @@ class ExternalReferenceRegistry {
#define EXTERNAL_REFERENCE_BINDING_LIST_CRYPTO(V)
#endif // HAVE_OPENSSL
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#if HAVE_OPENSSL && OPENSSL_NO_QUIC != 1
#define EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V) V(quic)
#else
#define EXTERNAL_REFERENCE_BINDING_LIST_QUIC(V)
+4 -3
View File
@@ -12,6 +12,7 @@
#include "simdjson.h"
#include "simdutf.h"
#if HAVE_SQLITE
#include "quic/guard.h"
#include "sqlite3.h"
#endif // HAVE_SQLITE
#include "undici_version.h"
@@ -30,12 +31,12 @@
#if HAVE_OPENSSL
#include <openssl/crypto.h>
#include "ncrypto.h"
#if NODE_OPENSSL_HAS_QUIC
#ifndef OPENSSL_NO_QUIC
#include <openssl/quic.h>
#endif
#endif // HAVE_OPENSSL
#ifdef NODE_OPENSSL_HAS_QUIC
#ifndef OPENSSL_NO_QUIC
#include <ngtcp2/version.h>
#include <nghttp3/version.h>
#endif
@@ -147,7 +148,7 @@ Metadata::Versions::Versions() {
unicode = U_UNICODE_VERSION;
#endif // NODE_HAVE_I18N_SUPPORT
#ifdef NODE_OPENSSL_HAS_QUIC
#ifndef OPENSSL_NO_QUIC
ngtcp2 = NGTCP2_VERSION;
nghttp3 = NGHTTP3_VERSION;
#endif
+3 -2
View File
@@ -10,7 +10,8 @@
#if HAVE_OPENSSL
#include <openssl/crypto.h>
#if NODE_OPENSSL_HAS_QUIC
#include <quic/guard.h>
#ifndef OPENSSL_NO_QUIC
#include <openssl/quic.h>
#endif
#endif // HAVE_OPENSSL
@@ -79,7 +80,7 @@ namespace node {
#define NODE_VERSIONS_KEY_INTL(V)
#endif // NODE_HAVE_I18N_SUPPORT
#ifdef OPENSSL_INFO_QUIC
#ifndef OPENSSL_NO_QUIC
#define NODE_VERSIONS_KEY_QUIC(V) \
V(ngtcp2) \
V(nghttp3)
+2 -3
View File
@@ -10,6 +10,7 @@
#include "uv.h"
#if HAVE_OPENSSL
#include "openssl/opensslv.h"
#include "quic/guard.h"
#endif
#include <algorithm>
@@ -551,11 +552,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
true);
AddOption("--experimental-quic",
"" /* undocumented until its development */,
#ifdef NODE_OPENSSL_HAS_QUIC
#ifndef OPENSSL_NO_QUIC
&EnvironmentOptions::experimental_quic,
#else
// Option is a no-op if the NODE_OPENSSL_HAS_QUIC
// compile flag is not enabled
NoOp{},
#endif
kAllowedInEnvvar);
+2 -1
View File
@@ -13,6 +13,7 @@
#if HAVE_OPENSSL
#include "openssl/opensslv.h"
#include "quic/guard.h"
#endif
namespace node {
@@ -127,7 +128,7 @@ class EnvironmentOptions : public Options {
bool experimental_websocket = true;
bool experimental_sqlite = true;
bool experimental_webstorage = false;
#ifdef NODE_OPENSSL_HAS_QUIC
#ifndef OPENSSL_NO_QUIC
bool experimental_quic = false;
#endif
std::string localstorage_file;
+28 -4
View File
@@ -1,5 +1,6 @@
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#if HAVE_OPENSSL
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include "application.h"
#include <async_wrap-inl.h>
#include <debug_utils-inl.h>
@@ -42,6 +43,8 @@ Session::Application_Options::operator const nghttp3_settings() const {
.qpack_blocked_streams = static_cast<size_t>(qpack_blocked_streams),
.enable_connect_protocol = enable_connect_protocol,
.h3_datagram = enable_datagrams,
// TODO(@jasnell): Support origin frames?
.origin_list = nullptr,
};
}
@@ -236,7 +239,9 @@ void Session::Application::SendPendingData() {
PathStorage path;
StreamData stream_data;
bool closed = false;
auto update_stats = OnScopeLeave([&] {
if (closed) return;
auto& s = session();
if (!s.is_destroyed()) [[likely]] {
s.UpdatePacketTxTime();
@@ -284,6 +289,7 @@ void Session::Application::SendPendingData() {
Debug(session_, "Failed to create packet for stream data");
// Doh! Could not create a packet. Time to bail.
session_->SetLastError(QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL));
closed = true;
return session_->Close(CloseMethod::SILENT);
}
@@ -292,6 +298,7 @@ void Session::Application::SendPendingData() {
Debug(session_, "Application failed to get stream data");
packet->CancelPacket();
session_->SetLastError(QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL));
closed = true;
return session_->Close(CloseMethod::SILENT);
}
@@ -357,13 +364,25 @@ void Session::Application::SendPendingData() {
if (ndatalen >= 0 && !StreamCommit(&stream_data, ndatalen)) {
Debug(session_,
"Failed to commit stream data while writing packets");
packet->CancelPacket();;
packet->CancelPacket();
session_->SetLastError(
QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL));
closed = true;
return session_->Close(CloseMethod::SILENT);
}
continue;
}
case NGTCP2_ERR_CALLBACK_FAILURE: {
// This case really should not happen. It indicates that the
// ngtcp2 callback failed for some reason. This would be a
// bug in our code.
Debug(session_, "Internal failure with ngtcp2 callback");
packet->CancelPacket();
session_->SetLastError(
QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL));
closed = true;
return session_->Close(CloseMethod::SILENT);
}
}
// Some other type of error happened.
@@ -373,10 +392,12 @@ void Session::Application::SendPendingData() {
ngtcp2_strerror(nwrite));
packet->CancelPacket();
session_->SetLastError(QuicError::ForNgtcp2Error(nwrite));
closed = true;
return session_->Close(CloseMethod::SILENT);
} else if (ndatalen >= 0 && !StreamCommit(&stream_data, ndatalen)) {
packet->CancelPacket();
session_->SetLastError(QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL));
closed = true;
return session_->Close(CloseMethod::SILENT);
}
@@ -448,6 +469,8 @@ class DefaultApplication final : public Session::Application {
// of the namespace.
using Application::Application; // NOLINT
error_code GetNoErrorCode() const override { return 0; }
bool ReceiveStreamData(int64_t stream_id,
const uint8_t* data,
size_t datalen,
@@ -612,4 +635,5 @@ std::unique_ptr<Session::Application> Session::SelectApplication(
} // namespace quic
} // namespace node
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // OPENSSL_NO_QUIC
#endif // HAVE_OPENSSL
+12 -6
View File
@@ -1,7 +1,6 @@
#pragma once
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include "base_object.h"
#include "bindingdata.h"
@@ -23,6 +22,8 @@ class Session::Application : public MemoryRetainer {
virtual bool Start();
virtual error_code GetNoErrorCode() const = 0;
// Session will forward all received stream data immediately on to the
// Application. The only additional processing the Session does is to
// automatically adjust the session-level flow control window. It is up to
@@ -121,9 +122,15 @@ class Session::Application : public MemoryRetainer {
virtual bool StreamCommit(StreamData* data, size_t datalen) = 0;
virtual bool ShouldSetFin(const StreamData& data) = 0;
inline Environment* env() const { return session_->env(); }
inline Session& session() { return *session_; }
inline const Session& session() const { return *session_; }
inline Environment* env() const { return session().env(); }
inline Session& session() {
CHECK_NOT_NULL(session_);
return *session_;
}
inline const Session& session() const {
CHECK_NOT_NULL(session_);
return *session_;
}
private:
BaseObjectPtr<Packet> CreateStreamDataPacket();
@@ -135,7 +142,7 @@ class Session::Application : public MemoryRetainer {
size_t max_packet_size,
const StreamData& stream_data);
Session* session_;
Session* session_ = nullptr;
};
struct Session::Application::StreamData final {
@@ -161,5 +168,4 @@ struct Session::Application::StreamData final {
} // namespace node::quic
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+1 -3
View File
@@ -149,9 +149,7 @@ class BindingData final
static void RegisterExternalReferences(ExternalReferenceRegistry* registry);
static BindingData& Get(Environment* env);
static inline BindingData& Get(Realm* realm) {
return Get(realm->env());
}
static inline BindingData& Get(Realm* realm) { return Get(realm->env()); }
BindingData(Realm* realm, v8::Local<v8::Object> object);
DISALLOW_COPY_AND_MOVE(BindingData)
-1
View File
@@ -51,7 +51,6 @@ class CID final : public MemoryRetainer {
CID(const CID& other);
CID& operator=(const CID& other);
DISALLOW_MOVE(CID)
struct Hash final {
size_t operator()(const CID& cid) const;
+15 -18
View File
@@ -88,9 +88,7 @@ Store::Store(std::unique_ptr<BackingStore> store, size_t length, size_t offset)
CHECK_LE(length_, store_->ByteLength() - offset_);
}
Maybe<Store> Store::From(
Local<ArrayBuffer> buffer,
Local<Value> detach_key) {
Maybe<Store> Store::From(Local<ArrayBuffer> buffer, Local<Value> detach_key) {
if (!buffer->IsDetachable()) {
return Nothing<Store>();
}
@@ -103,9 +101,7 @@ Maybe<Store> Store::From(
return Just(Store(std::move(backing), length, 0));
}
Maybe<Store> Store::From(
Local<ArrayBufferView> view,
Local<Value> detach_key) {
Maybe<Store> Store::From(Local<ArrayBufferView> view, Local<Value> detach_key) {
if (!view->Buffer()->IsDetachable()) {
return Nothing<Store>();
}
@@ -323,63 +319,64 @@ void QuicError::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("reason", reason_.length());
}
const QuicError QuicError::ForTransport(TransportError code,
std::string reason) {
QuicError QuicError::ForTransport(TransportError code, std::string reason) {
return ForTransport(static_cast<error_code>(code), std::move(reason));
}
const QuicError QuicError::ForTransport(error_code code, std::string reason) {
QuicError QuicError::ForTransport(error_code code, std::string reason) {
QuicError error(std::move(reason));
ngtcp2_ccerr_set_transport_error(
&error.error_, code, error.reason_c_str(), error.reason().length());
return error;
}
const QuicError QuicError::ForApplication(Http3Error code, std::string reason) {
QuicError QuicError::ForApplication(Http3Error code, std::string reason) {
return ForApplication(static_cast<error_code>(code), std::move(reason));
}
const QuicError QuicError::ForApplication(error_code code, std::string reason) {
QuicError QuicError::ForApplication(error_code code, std::string reason) {
QuicError error(std::move(reason));
ngtcp2_ccerr_set_application_error(
&error.error_, code, error.reason_c_str(), error.reason().length());
return error;
}
const QuicError QuicError::ForVersionNegotiation(std::string reason) {
QuicError QuicError::ForVersionNegotiation(std::string reason) {
return ForNgtcp2Error(NGTCP2_ERR_RECV_VERSION_NEGOTIATION, std::move(reason));
}
const QuicError QuicError::ForIdleClose(std::string reason) {
QuicError QuicError::ForIdleClose(std::string reason) {
return ForNgtcp2Error(NGTCP2_ERR_IDLE_CLOSE, std::move(reason));
}
const QuicError QuicError::ForDropConnection(std::string reason) {
QuicError QuicError::ForDropConnection(std::string reason) {
return ForNgtcp2Error(NGTCP2_ERR_DROP_CONN, std::move(reason));
}
const QuicError QuicError::ForRetry(std::string reason) {
QuicError QuicError::ForRetry(std::string reason) {
return ForNgtcp2Error(NGTCP2_ERR_RETRY, std::move(reason));
}
const QuicError QuicError::ForNgtcp2Error(int code, std::string reason) {
QuicError QuicError::ForNgtcp2Error(int code, std::string reason) {
QuicError error(std::move(reason));
ngtcp2_ccerr_set_liberr(
&error.error_, code, error.reason_c_str(), error.reason().length());
return error;
}
const QuicError QuicError::ForTlsAlert(int code, std::string reason) {
QuicError QuicError::ForTlsAlert(int code, std::string reason) {
QuicError error(std::move(reason));
ngtcp2_ccerr_set_tls_alert(
&error.error_, code, error.reason_c_str(), error.reason().length());
return error;
}
const QuicError QuicError::FromConnectionClose(ngtcp2_conn* session) {
QuicError QuicError::FromConnectionClose(ngtcp2_conn* session) {
return QuicError(ngtcp2_conn_get_ccerr(session));
}
const QuicError QuicError::TRANSPORT_NO_ERROR =
ForTransport(TransportError::NO_ERROR_);
#define V(name) \
const QuicError QuicError::TRANSPORT_##name = \
ForTransport(TransportError::name);
+19 -24
View File
@@ -53,25 +53,22 @@ class Store final : public MemoryRetainer {
Store(std::unique_ptr<v8::BackingStore> store,
size_t length,
size_t offset = 0);
Store(Store&& other) noexcept = default;
Store& operator=(Store&& other) noexcept = default;
DISALLOW_COPY(Store)
// Creates a Store from the contents of an ArrayBuffer, always detaching
// it in the process. An empty Maybe will be returned if the ArrayBuffer
// is not detachable or detaching failed (likely due to a detach key
// mismatch).
static v8::Maybe<Store> From(
v8::Local<v8::ArrayBuffer> buffer,
v8::Local<v8::Value> detach_key = v8::Local<v8::Value>());
v8::Local<v8::ArrayBuffer> buffer,
v8::Local<v8::Value> detach_key = v8::Local<v8::Value>());
// Creates a Store from the contents of an ArrayBufferView, always detaching
// it in the process. An empty Maybe will be returned if the ArrayBuffer
// is not detachable or detaching failed (likely due to a detach key
// mismatch).
static v8::Maybe<Store> From(
v8::Local<v8::ArrayBufferView> view,
v8::Local<v8::Value> detach_key = v8::Local<v8::Value>());
v8::Local<v8::ArrayBufferView> view,
v8::Local<v8::Value> detach_key = v8::Local<v8::Value>());
v8::Local<v8::Uint8Array> ToUint8Array(Environment* env) const;
inline v8::Local<v8::Uint8Array> ToUint8Array(Realm* realm) const {
@@ -159,6 +156,9 @@ class QuicError final : public MemoryRetainer {
// it as is.
NO_ERROR_ = NGTCP2_NO_ERROR,
#define V(name) name = NGTCP2_##name,
// The NO_ERROR here has to be treated as a special case since there
// is a NO_ERROR macro defined in Windows that conflicts.
NO_ERROR_ = NGTCP2_NO_ERROR,
QUIC_TRANSPORT_ERRORS(V)
#undef V
};
@@ -202,9 +202,6 @@ class QuicError final : public MemoryRetainer {
explicit QuicError(const std::string& reason = "");
explicit QuicError(const ngtcp2_ccerr* ptr);
explicit QuicError(const ngtcp2_ccerr& error);
QuicError(QuicError&& other) noexcept = default;
QuicError& operator=(QuicError&& other) noexcept = default;
DISALLOW_COPY(QuicError)
Type type() const;
error_code code() const;
@@ -260,21 +257,19 @@ class QuicError final : public MemoryRetainer {
// Utility functions for creating QuicError instances.
// The reason is expected to always be UTF-8 encoded.
static const QuicError ForTransport(TransportError code,
std::string reason = "");
static const QuicError ForTransport(error_code code, std::string reason = "");
static const QuicError ForApplication(Http3Error code,
std::string reason = "");
static const QuicError ForApplication(error_code code,
std::string reason = "");
static const QuicError ForVersionNegotiation(std::string reason = "");
static const QuicError ForIdleClose(std::string reason = "");
static const QuicError ForDropConnection(std::string reason = "");
static const QuicError ForRetry(std::string reason = "");
static const QuicError ForNgtcp2Error(int code, std::string reason = "");
static const QuicError ForTlsAlert(int code, std::string reason = "");
static QuicError ForTransport(TransportError code = TransportError::NO_ERROR_,
std::string reason = "");
static QuicError ForTransport(error_code code, std::string reason = "");
static QuicError ForApplication(Http3Error code, std::string reason = "");
static QuicError ForApplication(error_code code, std::string reason = "");
static QuicError ForVersionNegotiation(std::string reason = "");
static QuicError ForIdleClose(std::string reason = "");
static QuicError ForDropConnection(std::string reason = "");
static QuicError ForRetry(std::string reason = "");
static QuicError ForNgtcp2Error(int code, std::string reason = "");
static QuicError ForTlsAlert(int code, std::string reason = "");
static const QuicError FromConnectionClose(ngtcp2_conn* session);
static QuicError FromConnectionClose(ngtcp2_conn* session);
static const QuicError TRANSPORT_NO_ERROR;
#define V(name) static const QuicError TRANSPORT_##name;
+9 -5
View File
@@ -1,5 +1,6 @@
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#if HAVE_OPENSSL
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include "endpoint.h"
#include <aliased_struct-inl.h>
#include <async_wrap-inl.h>
@@ -156,7 +157,10 @@ bool SetOption(Environment* env,
env, "The %s option must be an ArrayBufferView", *nameStr);
return false;
}
Store store(value.As<ArrayBufferView>());
Store store;
if (!Store::From(value.As<ArrayBufferView>()).To(&store)) {
return false;
}
if (store.length() != TokenSecret::QUIC_TOKENSECRET_LEN) {
Utf8Value nameStr(env->isolate(), name);
THROW_ERR_INVALID_ARG_VALUE(
@@ -1765,5 +1769,5 @@ void Endpoint::Ref(const FunctionCallbackInfo<Value>& args) {
} // namespace quic
} // namespace node
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // OPENSSL_NO_QUIC
#endif // HAVE_OPENSSL
-2
View File
@@ -1,7 +1,6 @@
#pragma once
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include <aliased_struct.h>
#include <async_wrap.h>
@@ -417,5 +416,4 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
} // namespace node::quic
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+1 -1
View File
@@ -1,5 +1,5 @@
#pragma once
#ifndef QUIC_NGTCP2_USE_OPENSSL_3_5
#define OPENSSL_NO_QUIC = 1
#define OPENSSL_NO_QUIC 1
#endif
+30 -5
View File
@@ -1,5 +1,6 @@
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#if HAVE_OPENSSL
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include "http3.h"
#include <async_wrap-inl.h>
#include <base_object-inl.h>
@@ -176,6 +177,8 @@ class Http3ApplicationImpl final : public Session::Application {
session->set_priority_supported();
}
error_code GetNoErrorCode() const override { return NGHTTP3_H3_NO_ERROR; }
bool Start() override {
CHECK(!started_);
started_ = true;
@@ -966,6 +969,25 @@ class Http3ApplicationImpl final : public Session::Application {
return NGTCP2_SUCCESS;
}
static int on_receive_origin(nghttp3_conn* conn,
const uint8_t* origin,
size_t originlen,
void* conn_user_data) {
// TODO(@jasnell): Handle the origin callback. This is called
// when a single origin in an ORIGIN frame is received.
return NGTCP2_SUCCESS;
}
static int on_end_origin(nghttp3_conn* conn, void* conn_user_data) {
// TODO(@jasnell): Handle the end of origin callback. This is called
// when the end of an ORIGIN frame is received.
return NGTCP2_SUCCESS;
}
static void on_rand(uint8_t* dest, size_t destlen) {
CHECK(ncrypto::CSPRNG(dest, destlen));
}
static constexpr nghttp3_callbacks kCallbacks = {on_acked_stream_data,
on_stream_close,
on_receive_data,
@@ -980,7 +1002,10 @@ class Http3ApplicationImpl final : public Session::Application {
on_end_stream,
on_reset_stream,
on_shutdown,
on_receive_settings};
on_receive_settings,
on_receive_origin,
on_end_origin,
on_rand};
};
std::unique_ptr<Session::Application> Http3Application::Create(
@@ -991,5 +1016,5 @@ std::unique_ptr<Session::Application> Http3Application::Create(
} // namespace quic
} // namespace node
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // OPENSSL_NO_QUIC
#endif // HAVE_OPENSSL
-2
View File
@@ -1,7 +1,6 @@
#pragma once
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include <base_object.h>
#include <env.h>
@@ -40,5 +39,4 @@ class Http3Application final : public Session::ApplicationProvider {
} // namespace node::quic
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+7 -10
View File
@@ -1,16 +1,16 @@
#if HAVE_OPENSSL
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include "cid.h"
#include "preferredaddress.h"
#include <env-inl.h>
#include <node_realm-inl.h>
#include <ngtcp2/ngtcp2.h>
#include <node_errors.h>
#include <node_realm-inl.h>
#include <node_sockaddr-inl.h>
#include <util-inl.h>
#include <uv.h>
#include <v8.h>
#include "cid.h"
#include "preferredaddress.h"
namespace node {
@@ -30,10 +30,8 @@ std::optional<const PreferredAddress::AddressInfo> get_address_info(
if constexpr (FAMILY == AF_INET) {
if (!paddr.ipv4_present) return std::nullopt;
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;
}
@@ -131,9 +129,8 @@ void PreferredAddress::Set(ngtcp2_transport_params* params,
Maybe<PreferredAddress::Policy> PreferredAddress::tryGetPolicy(
Environment* env, Local<Value> value) {
return value->IsUndefined()
? Just(Policy::USE_PREFERRED)
: Just(FromV8Value<Policy>(value));
return value->IsUndefined() ? Just(Policy::USE_PREFERRED)
: Just(FromV8Value<Policy>(value));
}
void PreferredAddress::Initialize(Environment* env, Local<v8::Object> target) {
+1 -2
View File
@@ -33,8 +33,7 @@ class PreferredAddress final {
}
static void Initialize(Environment* env, v8::Local<v8::Object> target);
static inline void Initialize(Realm* realm,
v8::Local<v8::Object> target) {
static inline void Initialize(Realm* realm, v8::Local<v8::Object> target) {
return Initialize(realm->env(), target);
}
+5 -2
View File
@@ -1,4 +1,6 @@
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#if HAVE_OPENSSL
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include <base_object-inl.h>
#include <env-inl.h>
@@ -53,4 +55,5 @@ NODE_BINDING_CONTEXT_AWARE_INTERNAL(quic,
NODE_BINDING_PER_ISOLATE_INIT(quic, node::quic::CreatePerIsolateProperties)
NODE_BINDING_EXTERNAL_REFERENCE(quic, node::quic::RegisterExternalReferences)
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // OPENSSL_NO_QUIC
#endif // HAVE_OPENSSL
+31 -13
View File
@@ -1,5 +1,6 @@
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#if HAVE_OPENSSL
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include "session.h"
#include <aliased_struct-inl.h>
#include <async_wrap-inl.h>
@@ -800,9 +801,14 @@ struct Session::Impl final : public MemoryRetainer {
DCHECK(args[0]->IsArrayBufferView());
SendPendingDataScope send_scope(session);
args.GetReturnValue().Set(BigInt::New(
env->isolate(),
session->SendDatagram(Store(args[0].As<ArrayBufferView>()))));
Store store;
if (!Store::From(args[0].As<ArrayBufferView>()).To(&store)) {
return;
}
args.GetReturnValue().Set(
BigInt::New(env->isolate(), session->SendDatagram(std::move(store))));
}
// Internal ngtcp2 callbacks
@@ -1151,6 +1157,15 @@ struct Session::Impl final : public MemoryRetainer {
return NGTCP2_SUCCESS;
}
static int on_begin_path_validation(ngtcp2_conn* conn,
uint32_t flags,
const ngtcp2_path* path,
const ngtcp2_path* fallback_path,
void* user_data) {
// TODO(@jasnell): Implement?
return NGTCP2_SUCCESS;
}
static constexpr ngtcp2_callbacks CLIENT = {
ngtcp2_crypto_client_initial_cb,
nullptr,
@@ -1191,7 +1206,8 @@ struct Session::Impl final : public MemoryRetainer {
ngtcp2_crypto_version_negotiation_cb,
on_receive_rx_key,
nullptr,
on_early_data_rejected};
on_early_data_rejected,
on_begin_path_validation};
static constexpr ngtcp2_callbacks SERVER = {
nullptr,
@@ -1233,7 +1249,8 @@ struct Session::Impl final : public MemoryRetainer {
ngtcp2_crypto_version_negotiation_cb,
nullptr,
on_receive_tx_key,
on_early_data_rejected};
on_early_data_rejected,
on_begin_path_validation};
};
#undef NGTCP2_CALLBACK_SCOPE
@@ -1342,7 +1359,7 @@ Session::QuicConnectionPointer Session::InitConnection() {
CHECK_EQ(ngtcp2_conn_server_new(&conn,
config().dcid,
config().scid,
path,
&path,
config().version,
&Impl::SERVER,
&config().settings,
@@ -1356,7 +1373,7 @@ Session::QuicConnectionPointer Session::InitConnection() {
CHECK_EQ(ngtcp2_conn_client_new(&conn,
config().dcid,
config().scid,
path,
&path,
config().version,
&Impl::CLIENT,
&config().settings,
@@ -1602,7 +1619,7 @@ bool Session::Receive(Store&& store,
// session is not destroyed before we try doing anything with it
// (like updating stats, sending pending data, etc).
int err = ngtcp2_conn_read_pkt(
*this, path, nullptr, vec.base, vec.len, uv_hrtime());
*this, &path, nullptr, vec.base, vec.len, uv_hrtime());
switch (err) {
case 0: {
@@ -2069,7 +2086,7 @@ void Session::ShutdownStream(int64_t id, QuicError error) {
id,
error.type() == QuicError::Type::APPLICATION
? error.code()
: NGTCP2_APP_NOERROR);
: application().GetNoErrorCode());
}
void Session::ShutdownStreamWrite(int64_t id, QuicError code) {
@@ -2081,7 +2098,7 @@ void Session::ShutdownStreamWrite(int64_t id, QuicError code) {
id,
code.type() == QuicError::Type::APPLICATION
? code.code()
: NGTCP2_APP_NOERROR);
: application().GetNoErrorCode());
}
void Session::StreamDataBlocked(int64_t id) {
@@ -2845,4 +2862,5 @@ void Session::InitPerContext(Realm* realm, Local<Object> target) {
} // namespace quic
} // namespace node
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // OPENSSL_NO_QUIC
#endif // HAVE_OPENSSL
-2
View File
@@ -1,7 +1,6 @@
#pragma once
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include <async_wrap.h>
#include <base_object.h>
@@ -524,5 +523,4 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
} // namespace node::quic
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+25 -28
View File
@@ -45,10 +45,10 @@ Maybe<SessionTicket> SessionTicket::FromV8Value(Environment* env,
return Nothing<SessionTicket>();
}
auto view = value.As<ArrayBufferView>();
Store content(view->Buffer()->GetBackingStore(),
view->ByteLength(),
view->ByteOffset());
Store content;
if (!Store::From(value.As<ArrayBufferView>()).To(&content)) {
return Nothing<SessionTicket>();
}
ngtcp2_vec vec = content;
ValueDeserializer des(env->isolate(), vec.base, vec.len);
@@ -61,35 +61,32 @@ Maybe<SessionTicket> SessionTicket::FromV8Value(Environment* env,
Local<Value> ticket;
Local<Value> transport_params;
errors::TryCatchScope tryCatch(env);
if (!des.ReadValue(env->context()).ToLocal(&ticket) ||
!des.ReadValue(env->context()).ToLocal(&transport_params) ||
!ticket->IsArrayBufferView() || !transport_params->IsArrayBufferView()) {
if (tryCatch.HasCaught()) {
// Any errors thrown we want to catch and suppress. The only
// error we want to expose to the user is that the ticket format
// is invalid.
if (!tryCatch.HasTerminated()) {
THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid.");
tryCatch.ReThrow();
}
return Nothing<SessionTicket>();
}
THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid.");
!des.ReadValue(env->context()).ToLocal(&transport_params)) {
return Nothing<SessionTicket>();
}
if (!ticket->IsArrayBufferView()) {
THROW_ERR_INVALID_ARG_TYPE(env, "The ticket must be an ArrayBufferView");
return Nothing<SessionTicket>();
}
if (!transport_params->IsArrayBufferView()) {
THROW_ERR_INVALID_ARG_TYPE(
env, "The transport parameters must be an ArrayBufferView");
return Nothing<SessionTicket>();
}
auto ticketview = ticket.As<ArrayBufferView>();
auto transport_params_view = transport_params.As<ArrayBufferView>();
Store ticket_store;
Store transport_params_store;
if (!Store::From(ticket.As<ArrayBufferView>()).To(&ticket_store)) {
return Nothing<SessionTicket>();
}
if (!Store::From(transport_params.As<ArrayBufferView>())
.To(&transport_params_store)) {
return Nothing<SessionTicket>();
}
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())));
return Just(SessionTicket(std::move(ticket_store),
std::move(transport_params_store)));
}
MaybeLocal<Object> SessionTicket::encode(Environment* env) const {
+11 -6
View File
@@ -1,4 +1,6 @@
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#if HAVE_OPENSSL
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include "streams.h"
#include <aliased_struct-inl.h>
#include <async_wrap-inl.h>
@@ -237,7 +239,7 @@ struct Stream::Impl {
static void StopSending(const FunctionCallbackInfo<Value>& args) {
Stream* stream;
ASSIGN_OR_RETURN_UNWRAP(&stream, args.This());
uint64_t code = NGTCP2_APP_NOERROR;
uint64_t code = 0;
CHECK_IMPLIES(!args[0]->IsUndefined(), args[0]->IsBigInt());
if (!args[0]->IsUndefined()) {
bool unused = false; // not used but still necessary.
@@ -262,7 +264,7 @@ struct Stream::Impl {
static void ResetStream(const FunctionCallbackInfo<Value>& args) {
Stream* stream;
ASSIGN_OR_RETURN_UNWRAP(&stream, args.This());
uint64_t code = NGTCP2_APP_NOERROR;
uint64_t code = 0;
CHECK_IMPLIES(!args[0]->IsUndefined(), args[0]->IsBigInt());
if (!args[0]->IsUndefined()) {
bool lossless = false; // not used but still necessary.
@@ -818,7 +820,8 @@ Stream::Stream(BaseObjectWeakPtr<Session> session,
stats_(env()->isolate()),
state_(env()->isolate()),
session_(std::move(session)),
inbound_(DataQueue::Create()) {
inbound_(DataQueue::Create()),
headers_(env()->isolate()) {
MakeWeak();
state_->id = id;
state_->pending = 0;
@@ -853,7 +856,8 @@ Stream::Stream(BaseObjectWeakPtr<Session> session,
session_(std::move(session)),
inbound_(DataQueue::Create()),
maybe_pending_stream_(
std::make_unique<PendingStream>(direction, this, session_)) {
std::make_unique<PendingStream>(direction, this, session_)),
headers_(env()->isolate()) {
MakeWeak();
state_->id = -1;
state_->pending = 1;
@@ -1310,4 +1314,5 @@ void Stream::Unschedule() {
} // namespace quic
} // namespace node
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // OPENSSL_NO_QUIC
#endif // HAVE_OPENSSL
+2 -4
View File
@@ -1,7 +1,6 @@
#pragma once
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include <aliased_struct.h>
#include <async_wrap.h>
@@ -320,8 +319,8 @@ class Stream final : public AsyncWrap,
std::optional<std::unique_ptr<PendingStream>> maybe_pending_stream_ =
std::nullopt;
std::vector<std::unique_ptr<PendingHeaders>> pending_headers_queue_;
uint64_t pending_close_read_code_ = NGTCP2_APP_NOERROR;
uint64_t pending_close_write_code_ = NGTCP2_APP_NOERROR;
uint64_t pending_close_read_code_ = 0;
uint64_t pending_close_write_code_ = 0;
struct PendingPriority {
StreamPriority priority;
@@ -366,5 +365,4 @@ class Stream final : public AsyncWrap,
} // namespace node::quic
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+259 -79
View File
@@ -1,6 +1,6 @@
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include "tlscontext.h"
#if HAVE_OPENSSL
#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include <async_wrap-inl.h>
#include <base_object-inl.h>
#include <crypto/crypto_util.h>
@@ -9,13 +9,14 @@
#include <memory_tracker-inl.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_quictls.h>
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#include <node_sockaddr-inl.h>
#include <openssl/ssl.h>
#include <v8.h>
#include "bindingdata.h"
#include "defs.h"
#include "session.h"
#include "tlscontext.h"
#include "transportparams.h"
namespace node {
@@ -28,6 +29,8 @@ using ncrypto::SSLPointer;
using ncrypto::SSLSessionPointer;
using ncrypto::X509Pointer;
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::Just;
using v8::Local;
using v8::Maybe;
@@ -42,15 +45,34 @@ namespace quic {
// ============================================================================
namespace {
// TODO(@jasnell): One time initialization. ngtcp2 says this is optional but
// highly recommended to deal with some perf regression. Unfortunately doing
// this breaks some existing tests and we need to understand the potential
// impact of calling this.
// Performance optimization recommended by ngtcp2. Need to investigate why
// this causes some tests to fail.
// auto _ = []() {
// CHECK_EQ(ngtcp2_crypto_quictls_init(), 0);
// if (ngtcp2_crypto_ossl_init() != 0) {
// assert(0);
// abort();
// }
// return 0;
// }();
// Temporarily wraps an SSL pointer but does not take ownership.
// Use by a few of the TLSSession methods that need access to the SSL*
// pointer held by the OSSLContext but cannot take ownership of it.
class SSLPointerRef final {
public:
inline SSLPointerRef(SSL* ssl) : temp_(ssl) { CHECK(temp_); }
inline ~SSLPointerRef() { release(); }
DISALLOW_COPY_AND_MOVE(SSLPointerRef)
inline operator const SSLPointer&() const { return temp_; }
inline const SSLPointer* operator->() const { return &temp_; }
inline const SSLPointer& operator*() const { return temp_; }
inline void release() { temp_.release(); }
private:
SSLPointer temp_;
};
void EnableTrace(Environment* env, BIOPointer* bio, SSL* ssl) {
#if HAVE_SSL_TRACE
static bool warn_trace_tls = true;
@@ -113,13 +135,23 @@ bool SetOption(Environment* env,
}
} else if constexpr (std::is_same<T, Store>::value) {
if (item->IsArrayBufferView()) {
(options->*member).emplace_back(item.As<v8::ArrayBufferView>());
Store store;
if (!Store::From(item.As<v8::ArrayBufferView>()).To(&store)) {
return false;
}
(options->*member).push_back(std::move(store));
} else if (item->IsArrayBuffer()) {
(options->*member).emplace_back(item.As<ArrayBuffer>());
Store store;
if (!Store::From(item.As<ArrayBuffer>()).To(&store)) {
return false;
}
(options->*member).push_back(std::move(store));
} else {
Utf8Value namestr(env->isolate(), name);
THROW_ERR_INVALID_ARG_TYPE(
env, "%s value must be an array buffer", *namestr);
env,
"%s value must be an array buffer or array buffer view",
*namestr);
return false;
}
}
@@ -138,13 +170,23 @@ bool SetOption(Environment* env,
}
} else if constexpr (std::is_same<T, Store>::value) {
if (value->IsArrayBufferView()) {
(options->*member).emplace_back(value.As<v8::ArrayBufferView>());
Store store;
if (!Store::From(value.As<v8::ArrayBufferView>()).To(&store)) {
return false;
}
(options->*member).push_back(std::move(store));
} else if (value->IsArrayBuffer()) {
(options->*member).emplace_back(value.As<ArrayBuffer>());
Store store;
if (!Store::From(value.As<ArrayBuffer>()).To(&store)) {
return false;
}
(options->*member).push_back(std::move(store));
} else {
Utf8Value namestr(env->isolate(), name);
THROW_ERR_INVALID_ARG_TYPE(
env, "%s value must be an array buffer", *namestr);
env,
"%s value must be an array buffer or array buffer view",
*namestr);
return false;
}
}
@@ -153,6 +195,106 @@ bool SetOption(Environment* env,
}
} // namespace
OSSLContext::OSSLContext() {
CHECK_EQ(ngtcp2_crypto_ossl_ctx_new(&ctx_, nullptr), 0);
}
OSSLContext::~OSSLContext() {
reset();
}
void OSSLContext::reset() {
if (ctx_) {
SSL_set_app_data(*this, nullptr);
ngtcp2_conn_set_tls_native_handle(connection_, nullptr);
ngtcp2_crypto_ossl_ctx_del(ctx_);
ctx_ = nullptr;
connection_ = nullptr;
}
}
OSSLContext::operator SSL*() const {
return ngtcp2_crypto_ossl_ctx_get_ssl(ctx_);
}
OSSLContext::operator ngtcp2_crypto_ossl_ctx*() const {
return ctx_;
}
void OSSLContext::Initialize(SSL* ssl,
ngtcp2_crypto_conn_ref* ref,
ngtcp2_conn* connection,
SSL_CTX* ssl_ctx) {
CHECK(ssl);
ngtcp2_crypto_ossl_ctx_set_ssl(ctx_, ssl);
SSL_set_app_data(*this, ref);
// TODO(@jasnell): Later when BoringSSL is also supported, the native
// handle will be different. The ngtcp2_crypto_ossl.h impl requires
// that the native handle be set to the ngtcp2_crypto_ossl_ctx. So
// this will need to be updated to support both cases.
ngtcp2_conn_set_tls_native_handle(connection, ctx_);
connection_ = connection;
}
std::string OSSLContext::get_cipher_name() const {
return SSL_get_cipher_name(*this);
}
std::string OSSLContext::get_selected_alpn() const {
const unsigned char* alpn = nullptr;
unsigned int len;
SSL_get0_alpn_selected(*this, &alpn, &len);
return std::string(alpn, alpn + len);
}
std::string_view OSSLContext::get_negotiated_group() const {
auto name = SSL_get0_group_name(*this);
if (name == nullptr) return "";
return name;
}
bool OSSLContext::set_alpn_protocols(std::string_view protocols) const {
return SSL_set_alpn_protos(
*this,
reinterpret_cast<const unsigned char*>(protocols.data()),
protocols.size()) == 0;
}
bool OSSLContext::set_hostname(std::string_view hostname) const {
if (!hostname.empty()) {
SSL_set_tlsext_host_name(*this, hostname.data());
} else {
SSL_set_tlsext_host_name(*this, "localhost");
}
return true;
}
bool OSSLContext::set_early_data_enabled() const {
return SSL_set_quic_tls_early_data_enabled(*this, 1) == 1;
}
bool OSSLContext::set_transport_params(const ngtcp2_vec& tp) const {
return SSL_set_quic_tls_transport_params(*this, tp.base, tp.len) == 1;
}
bool OSSLContext::get_early_data_accepted() const {
return SSL_get_early_data_status(*this) == SSL_EARLY_DATA_ACCEPTED;
}
bool OSSLContext::ConfigureServer() const {
if (ngtcp2_crypto_ossl_configure_server_session(*this) != 0) return false;
SSL_set_accept_state(*this);
return set_early_data_enabled();
}
bool OSSLContext::ConfigureClient() const {
if (ngtcp2_crypto_ossl_configure_client_session(*this) != 0) return false;
SSL_set_connect_state(*this);
return true;
}
// ============================================================================
std::shared_ptr<TLSContext> TLSContext::CreateClient(const Options& options) {
return std::make_shared<TLSContext>(Side::CLIENT, options);
}
@@ -186,9 +328,9 @@ int TLSContext::OnSelectAlpn(SSL* ssl,
// here. Note that this will not fail the TLS handshake, so we have to check
// later if the ALPN matches the expected identifier or not.
//
// We might eventually want to support the ability to negotiate multiple
// possible ALPN's on a single endpoint/session but for now, we only support
// one.
// TODO(@jasnell): We might eventually want to support the ability to
// negotiate multiple possible ALPN's on a single endpoint/session but for
// now, we only support one.
if (SSL_select_next_proto(
const_cast<unsigned char**>(out),
outlen,
@@ -215,11 +357,16 @@ int TLSContext::OnNewSession(SSL* ssl, SSL_SESSION* sess) {
// ticket.
size_t size = i2d_SSL_SESSION(sess, nullptr);
// If size is 0 or the size is greater than our max, let's ignore it
// If size is 0, the size is greater than our max, or there is not
// enough memory to allocate the backing store, then we ignore it
// and continue without emitting the sessionticket event.
if (size > 0 && size <= crypto::SecureContext::kMaxSessionSize) {
auto ticket =
ArrayBuffer::NewBackingStore(session.env()->isolate(), size);
auto ticket = ArrayBuffer::NewBackingStore(
session.env()->isolate(),
size,
BackingStoreInitializationMode::kUninitialized,
BackingStoreOnFailureMode::kReturnNull);
if (!ticket) return 0;
auto data = reinterpret_cast<unsigned char*>(ticket->Data());
if (i2d_SSL_SESSION(sess, &data) > 0) {
session.EmitSessionTicket(Store(std::move(ticket), size));
@@ -254,20 +401,31 @@ SSLCtxPointer TLSContext::Initialize() {
case Side::SERVER: {
static constexpr unsigned char kSidCtx[] = "Node.js QUIC Server";
ctx = SSLCtxPointer::NewServer();
CHECK_EQ(ngtcp2_crypto_quictls_configure_server_context(ctx.get()), 0);
CHECK_EQ(SSL_CTX_set_max_early_data(ctx.get(), UINT32_MAX), 1);
if (!ctx) [[unlikely]] {
validation_error_ = "Failed to create SSL_CTX for server";
return {};
}
if (SSL_CTX_set_max_early_data(ctx.get(), UINT32_MAX) != 1) {
validation_error_ = "Failed to set max early data";
return {};
}
SSL_CTX_set_options(ctx.get(),
(SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
SSL_OP_SINGLE_ECDH_USE |
SSL_OP_CIPHER_SERVER_PREFERENCE |
SSL_OP_NO_ANTI_REPLAY);
SSL_CTX_set_mode(ctx.get(), SSL_MODE_RELEASE_BUFFERS);
SSL_CTX_set_alpn_select_cb(ctx.get(), OnSelectAlpn, this);
CHECK_EQ(SSL_CTX_set_session_id_context(
ctx.get(), kSidCtx, sizeof(kSidCtx) - 1),
1);
if (options_.verify_client) {
if (SSL_CTX_set_session_id_context(
ctx.get(), kSidCtx, sizeof(kSidCtx) - 1) != 1) {
validation_error_ = "Failed to set session ID context";
return {};
}
if (options_.verify_client) [[likely]] {
SSL_CTX_set_verify(ctx.get(),
SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
@@ -285,7 +443,6 @@ SSLCtxPointer TLSContext::Initialize() {
}
case Side::CLIENT: {
ctx = SSLCtxPointer::NewClient();
CHECK_EQ(ngtcp2_crypto_quictls_configure_client_context(ctx.get()), 0);
SSL_CTX_set_session_cache_mode(
ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
@@ -499,61 +656,70 @@ TLSSession::TLSSession(Session* session,
const std::optional<SessionTicket>& maybeSessionTicket)
: ref_({connection, this}),
context_(std::move(context)),
session_(session),
ssl_(Initialize(maybeSessionTicket)) {
session_(session) {
Debug(session_, "Created new TLS session for %s", session->config().dcid);
}
TLSSession::~TLSSession() {
if (ssl_) {
SSL_set_app_data(ssl_.get(), nullptr);
Initialize(maybeSessionTicket);
if (!ossl_context_) [[unlikely]] {
Debug(session_,
"Failed to initialize TLS session: %s",
validation_error_.empty() ? "unknown error" : validation_error_);
}
}
TLSSession::operator SSL*() const {
CHECK(ssl_);
return ssl_.get();
return ossl_context_;
}
bool TLSSession::early_data_was_accepted() const {
CHECK_NE(ngtcp2_conn_get_handshake_completed(*session_), 0);
return SSL_get_early_data_status(*this) == SSL_EARLY_DATA_ACCEPTED;
return ossl_context_.get_early_data_accepted();
}
SSLPointer TLSSession::Initialize(
void TLSSession::Initialize(
const std::optional<SessionTicket>& maybeSessionTicket) {
auto& ctx = context();
auto& options = ctx.options();
SSLPointer ssl(SSL_new(ctx));
SSL_set_app_data(ssl.get(), &ref_);
ngtcp2_conn_set_tls_native_handle(*session_, ssl.get());
auto ssl = SSLPointer::New(ctx);
if (!ssl) [[unlikely]] {
validation_error_ = "Failed to create SSL session";
ossl_context_.reset();
return;
}
// Enable tracing if the `--trace-tls` command line flag is used.
if (session_->env()->options()->trace_tls || options.enable_tls_trace)
[[unlikely]] {
EnableTrace(session_->env(), &bio_trace_, *this);
EnableTrace(session_->env(), &bio_trace_, ssl);
}
ossl_context_.Initialize(ssl.release(), &ref_, session(), ctx);
switch (ctx.side()) {
case Side::SERVER: {
SSL_set_accept_state(ssl.get());
SSL_set_quic_early_data_enabled(ssl.get(), 1);
if (!ossl_context_.ConfigureServer()) [[unlikely]] {
validation_error_ = "Failed to configure server session";
ossl_context_.reset();
return;
}
break;
}
case Side::CLIENT: {
SSL_set_connect_state(ssl.get());
if (SSL_set_alpn_protos(
ssl.get(),
reinterpret_cast<const unsigned char*>(options.protocol.data()),
options.protocol.size()) != 0) {
validation_error_ = "Invalid ALPN";
return SSLPointer();
if (!ossl_context_.ConfigureClient()) [[unlikely]] {
validation_error_ = "Failed to configure client session";
ossl_context_.reset();
return;
};
if (!ossl_context_.set_alpn_protocols(options.protocol)) {
validation_error_ = "Failed to set ALPN protocols";
ossl_context_.reset();
return;
}
if (!options.servername.empty()) {
SSL_set_tlsext_host_name(ssl.get(), options.servername.data());
} else {
SSL_set_tlsext_host_name(ssl.get(), "localhost");
if (!ossl_context_.set_hostname(options.servername)) {
validation_error_ = "Failed to set server name";
ossl_context_.reset();
return;
}
if (maybeSessionTicket.has_value()) {
@@ -568,7 +734,11 @@ SSLPointer TLSSession::Initialize(
ngtcp2_vec rtp = sessionTicket.transport_params();
if (ngtcp2_conn_decode_and_set_0rtt_transport_params(
*session_, rtp.base, rtp.len) == 0) {
SSL_set_quic_early_data_enabled(ssl.get(), 1);
if (!ossl_context_.set_early_data_enabled()) {
validation_error_ = "Failed to enable early data";
ossl_context_.reset();
return;
}
session_->SetStreamOpenAllowed();
}
}
@@ -581,16 +751,19 @@ SSLPointer TLSSession::Initialize(
TransportParams tp(ngtcp2_conn_get_local_transport_params(*session_));
Store store = tp.Encode(session_->env());
if (store && store.length() > 0) {
ngtcp2_vec vec = store;
SSL_set_quic_transport_params(ssl.get(), vec.base, vec.len);
if (!ossl_context_.set_transport_params(store)) {
validation_error_ = "Failed to set transport parameters";
ossl_context_.reset();
return;
}
}
return ssl;
}
std::optional<TLSSession::PeerIdentityValidationError>
TLSSession::VerifyPeerIdentity(Environment* env) {
int err = ssl_.verifyPeerCertificate().value_or(X509_V_ERR_UNSPECIFIED);
// We are just temporarily wrapping the ssl, not taking ownership.
SSLPointerRef ssl(ossl_context_);
int err = ssl->verifyPeerCertificate().value_or(X509_V_ERR_UNSPECIFIED);
if (err == X509_V_OK) return std::nullopt;
Local<Value> reason;
Local<Value> code;
@@ -604,44 +777,47 @@ TLSSession::VerifyPeerIdentity(Environment* env) {
}
MaybeLocal<Object> TLSSession::cert(Environment* env) const {
return crypto::X509Certificate::GetCert(env, ssl_);
SSLPointerRef ssl(ossl_context_);
return crypto::X509Certificate::GetCert(env, ssl);
}
MaybeLocal<Object> TLSSession::peer_cert(Environment* env) const {
// We are just temporarily wrapping the ssl, not taking ownership.
SSLPointerRef ssl(ossl_context_);
crypto::X509Certificate::GetPeerCertificateFlag flag =
context_->side() == Side::SERVER
? crypto::X509Certificate::GetPeerCertificateFlag::SERVER
: crypto::X509Certificate::GetPeerCertificateFlag::NONE;
return crypto::X509Certificate::GetPeerCert(env, ssl_, flag);
return crypto::X509Certificate::GetPeerCert(env, ssl, flag);
}
MaybeLocal<Object> TLSSession::ephemeral_key(Environment* env) const {
return crypto::GetEphemeralKey(env, ssl_);
// We are just temporarily wrapping the ssl, not taking ownership.
SSLPointerRef ssl(ossl_context_);
return crypto::GetEphemeralKey(env, ssl);
}
MaybeLocal<Value> TLSSession::cipher_name(Environment* env) const {
auto name = ssl_.getCipherName();
if (!name.has_value()) return Undefined(env->isolate());
return OneByteString(env->isolate(), name.value());
CHECK(ossl_context_);
auto name = ossl_context_.get_cipher_name();
return OneByteString(env->isolate(), name);
}
MaybeLocal<Value> TLSSession::cipher_version(Environment* env) const {
auto version = ssl_.getCipherVersion();
SSLPointerRef ssl(ossl_context_);
auto version = ssl->getCipherVersion();
if (!version.has_value()) return Undefined(env->isolate());
return OneByteString(env->isolate(), version.value());
}
const std::string_view TLSSession::servername() const {
return ssl_.getServerName().value_or(std::string_view());
SSLPointerRef ssl(ossl_context_);
return ssl->getServerName().value_or(std::string_view());
}
const std::string_view TLSSession::protocol() const {
const unsigned char* alpn_buf = nullptr;
unsigned int alpnlen;
SSL_get0_alpn_selected(ssl_.get(), &alpn_buf, &alpnlen);
return alpnlen ? std::string_view(reinterpret_cast<const char*>(alpn_buf),
alpnlen)
: std::string_view();
const std::string TLSSession::protocol() const {
CHECK(ossl_context_);
return ossl_context_.get_selected_alpn();
}
bool TLSSession::InitiateKeyUpdate() {
@@ -658,7 +834,11 @@ ngtcp2_conn* TLSSession::connection(ngtcp2_crypto_conn_ref* ref) {
return static_cast<TLSSession*>(ref->user_data)->session();
}
void TLSSession::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("context", context_);
}
} // namespace quic
} // namespace node
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // OPENSSL_NO_QUIC
#endif // HAVE_OPENSSL
+67 -13
View File
@@ -1,7 +1,6 @@
#pragma once
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#include <base_object.h>
#include <crypto/crypto_context.h>
@@ -9,6 +8,7 @@
#include <memory_tracker.h>
#include <ncrypto.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <ngtcp2/ngtcp2_crypto_ossl.h>
#include "bindingdata.h"
#include "data.h"
#include "defs.h"
@@ -19,6 +19,53 @@ namespace node::quic {
class Session;
class TLSContext;
#if OPENSSL_IS_BORING
static_assert(false,
"This implementation of tlscontext relies on OpenSSL APIS "
"that are not available in BoringSSL. An alternative impl "
"using the equivalent BoringSSL APIs is possible but not "
"yet implemented.");
#endif
// The OSSLContext is a wrapper around the ngtcp2_crypto_ossl_ctx that
// holds the state for the OpenSSL TLS session. The ngtcp2_crypto_ossl_ctx
// is required by ngtcp2's adapter for OpenSSL.
class OSSLContext final {
public:
OSSLContext();
~OSSLContext();
DISALLOW_COPY_AND_MOVE(OSSLContext)
operator SSL*() const;
operator ngtcp2_crypto_ossl_ctx*() const;
void Initialize(SSL* ssl,
ngtcp2_crypto_conn_ref* ref,
ngtcp2_conn* connection,
SSL_CTX* ssl_ctx);
std::string get_cipher_name() const;
std::string get_selected_alpn() const;
std::string_view get_negotiated_group() const;
bool set_alpn_protocols(std::string_view protocols) const;
bool set_hostname(std::string_view hostname) const;
bool set_early_data_enabled() const;
bool set_transport_params(const ngtcp2_vec& tp) const;
bool get_early_data_accepted() const;
bool ConfigureServer() const;
bool ConfigureClient() const;
void reset();
inline operator bool() const { return ctx_ != nullptr; }
private:
ngtcp2_crypto_ossl_ctx* ctx_;
ngtcp2_conn* connection_ = nullptr;
};
// Every QUIC Session has exactly one TLSSession that maintains the state
// of the TLS handshake and negotiated keys after the handshake has been
// completed. It is separated out from the main Session class only as a
@@ -27,6 +74,15 @@ class TLSContext;
// the context.
class TLSSession final : public MemoryRetainer {
public:
// TODO(@jasnell): In order to support using QUIC with host embedders
// like Electron, we need to abstract the TLSContext and TLSSession
// more so that they can use either the OpenSSL 3.5 or BoringSSL APIs.
// This can be done by creatig an internal TLSSession::Impl and
// TLSContext::Impl abstractions that can be implemented using either
// of the two APIs, keeping the TLSSession and TLSContext APIs the same
// for each.
// Gets the TLSSession from the SSL pointer app data.
static const TLSSession& From(const SSL* ssl);
// The constructor is public in order to satisfy the call to std::make_unique
@@ -35,9 +91,8 @@ class TLSSession final : public MemoryRetainer {
std::shared_ptr<TLSContext> context,
const std::optional<SessionTicket>& maybeSessionTicket);
DISALLOW_COPY_AND_MOVE(TLSSession)
~TLSSession();
inline operator bool() const { return ssl_ != nullptr; }
inline operator bool() const { return ossl_context_; }
inline Session& session() const { return *session_; }
inline TLSContext& context() const { return *context_; }
@@ -56,7 +111,7 @@ class TLSSession final : public MemoryRetainer {
const std::string_view servername() const;
// The ALPN (protocol name) negotiated for the session
const std::string_view protocol() const;
const std::string protocol() const;
// Triggers key update to begin. This will fail and return false if either a
// previous key update is in progress or if the initial handshake has not yet
@@ -71,29 +126,28 @@ class TLSSession final : public MemoryRetainer {
// Checks the peer identity against the configured CA and CRL. If the peer
// certificate is valid, std::nullopt is returned. Otherwise a
// PeerIdentityValidationError is returned with the reason and code for the
// failure.
// failure. If there was an error setting the reason or code, the fields
// will be empty but the PeerIdentityValidationError will still be returned.
// This is an indication that an exception has been scheduled by v8.
std::optional<PeerIdentityValidationError> VerifyPeerIdentity(
Environment* env);
inline const std::string_view validation_error() const {
return validation_error_;
}
inline std::string_view validation_error() const { return validation_error_; }
SET_NO_MEMORY_INFO()
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(TLSSession)
SET_SELF_SIZE(TLSSession)
private:
operator SSL*() const;
ncrypto::SSLPointer Initialize(
const std::optional<SessionTicket>& maybeSessionTicket);
void Initialize(const std::optional<SessionTicket>& maybeSessionTicket);
static ngtcp2_conn* connection(ngtcp2_crypto_conn_ref* ref);
ngtcp2_crypto_conn_ref ref_;
std::shared_ptr<TLSContext> context_;
OSSLContext ossl_context_;
Session* session_;
ncrypto::SSLPointer ssl_;
ncrypto::BIOPointer bio_trace_;
std::string validation_error_ = "";
bool in_key_update_ = false;
@@ -189,6 +243,7 @@ class TLSContext final : public MemoryRetainer,
inline Side side() const { return side_; }
inline const Options& options() const { return options_; }
inline operator bool() const { return ctx_ != nullptr; }
inline operator const ncrypto::SSLCtxPointer&() const { return ctx_; }
inline const std::string_view validation_error() const {
return validation_error_;
@@ -224,5 +279,4 @@ class TLSContext final : public MemoryRetainer,
} // namespace node::quic
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+14 -7
View File
@@ -16,6 +16,8 @@
namespace node {
using v8::ArrayBuffer;
using v8::BackingStoreInitializationMode;
using v8::BackingStoreOnFailureMode;
using v8::Just;
using v8::Local;
using v8::Maybe;
@@ -183,26 +185,31 @@ TransportParams::TransportParams(const ngtcp2_vec& vec, int version)
Store TransportParams::Encode(Environment* env, int version) const {
if (ptr_ == nullptr) {
return Store();
return {};
}
// Preflight to see how much storage we'll need.
ssize_t size =
ngtcp2_transport_params_encode_versioned(nullptr, 0, version, &params_);
if (size == 0) {
return Store();
return {};
}
auto result = ArrayBuffer::NewBackingStore(
env->isolate(), size, v8::BackingStoreInitializationMode::kUninitialized);
env->isolate(),
size,
BackingStoreInitializationMode::kUninitialized,
BackingStoreOnFailureMode::kReturnNull);
if (!result) {
return {};
}
auto ret = ngtcp2_transport_params_encode_versioned(
static_cast<uint8_t*>(result->Data()), size, version, &params_);
if (ret != 0) {
return Store();
}
// The ret is the number of bytes written, or a negative error code.
if (ret < 0) return {};
CHECK_EQ(ret, size);
return Store(std::move(result), static_cast<size_t>(size));
}
+2 -2
View File
@@ -34,7 +34,7 @@ class TransportParams final {
static constexpr uint64_t DEFAULT_MAX_STREAMS_UNI = 3;
static constexpr uint64_t DEFAULT_ACTIVE_CONNECTION_ID_LIMIT = 2;
struct Config {
struct Config final {
Side side;
const CID& ocid;
const CID& retry_scid;
@@ -43,7 +43,7 @@ class TransportParams final {
const CID& retry_scid = CID::kInvalid);
};
struct Options : public MemoryRetainer {
struct Options final : public MemoryRetainer {
int transportParamsVersion = QUIC_TRANSPORT_PARAMS_V1;
// Set only on server Sessions, the preferred address communicates the IP