mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
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:
1 parent
a6c5d27739
commit
cd9fd09a27
44 files changed
+646
-360
No files matched your search
+2
-1
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
@@ -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, ¶ms_);
|
||||
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, ¶ms_);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user