mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
quic: add multiple internal utilities
* add the CID implementation * add the PreferredAddress implementation * add Path and PathStorage implementations * add Store implementation * add QuicError implementation PR-URL: https://github.com/nodejs/node/pull/47263 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
10
node.gyp
10
node.gyp
@@ -335,6 +335,14 @@
|
||||
'src/node_crypto.cc',
|
||||
'src/node_crypto.h',
|
||||
],
|
||||
'node_quic_sources': [
|
||||
'src/quic/cid.cc',
|
||||
'src/quic/data.cc',
|
||||
'src/quic/preferredaddress.cc',
|
||||
'src/quic/cid.h',
|
||||
'src/quic/data.h',
|
||||
'src/quic/preferredaddress.h',
|
||||
],
|
||||
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
|
||||
'conditions': [
|
||||
['GENERATOR == "ninja"', {
|
||||
@@ -836,6 +844,7 @@
|
||||
[ 'node_use_openssl=="true"', {
|
||||
'sources': [
|
||||
'<@(node_crypto_sources)',
|
||||
'<@(node_quic_sources)',
|
||||
],
|
||||
}],
|
||||
[ 'OS in "linux freebsd mac solaris" and '
|
||||
@@ -1023,6 +1032,7 @@
|
||||
'sources': [
|
||||
'test/cctest/test_crypto_clienthello.cc',
|
||||
'test/cctest/test_node_crypto.cc',
|
||||
'test/cctest/test_quic_cid.cc',
|
||||
]
|
||||
}],
|
||||
['v8_enable_inspector==1', {
|
||||
|
||||
148
src/quic/cid.cc
Normal file
148
src/quic/cid.cc
Normal file
@@ -0,0 +1,148 @@
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
#include "cid.h"
|
||||
#include <crypto/crypto_util.h>
|
||||
#include <memory_tracker-inl.h>
|
||||
#include <node_mutex.h>
|
||||
#include <string_bytes.h>
|
||||
|
||||
namespace node {
|
||||
namespace quic {
|
||||
|
||||
// ============================================================================
|
||||
// CID
|
||||
|
||||
CID::CID() : ptr_(&cid_) {
|
||||
cid_.datalen = 0;
|
||||
}
|
||||
|
||||
CID::CID(const ngtcp2_cid& cid) : CID(cid.data, cid.datalen) {}
|
||||
|
||||
CID::CID(const uint8_t* data, size_t len) : CID() {
|
||||
DCHECK_GE(len, kMinLength);
|
||||
DCHECK_LE(len, kMaxLength);
|
||||
ngtcp2_cid_init(&cid_, data, len);
|
||||
}
|
||||
|
||||
CID::CID(const ngtcp2_cid* cid) : ptr_(cid) {
|
||||
CHECK_NOT_NULL(cid);
|
||||
DCHECK_GE(cid->datalen, kMinLength);
|
||||
DCHECK_LE(cid->datalen, kMaxLength);
|
||||
}
|
||||
|
||||
CID::CID(const CID& other) : ptr_(&cid_) {
|
||||
CHECK_NOT_NULL(other.ptr_);
|
||||
ngtcp2_cid_init(&cid_, other.ptr_->data, other.ptr_->datalen);
|
||||
}
|
||||
|
||||
bool CID::operator==(const CID& other) const noexcept {
|
||||
if (this == &other || (length() == 0 && other.length() == 0)) return true;
|
||||
if (length() != other.length()) return false;
|
||||
return memcmp(ptr_->data, other.ptr_->data, ptr_->datalen) == 0;
|
||||
}
|
||||
|
||||
bool CID::operator!=(const CID& other) const noexcept {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
CID::operator const uint8_t*() const {
|
||||
return ptr_->data;
|
||||
}
|
||||
CID::operator const ngtcp2_cid&() const {
|
||||
return *ptr_;
|
||||
}
|
||||
CID::operator const ngtcp2_cid*() const {
|
||||
return ptr_;
|
||||
}
|
||||
CID::operator bool() const {
|
||||
return ptr_->datalen >= kMinLength;
|
||||
}
|
||||
|
||||
size_t CID::length() const {
|
||||
return ptr_->datalen;
|
||||
}
|
||||
|
||||
std::string CID::ToString() const {
|
||||
char dest[kMaxLength * 2];
|
||||
size_t written =
|
||||
StringBytes::hex_encode(reinterpret_cast<const char*>(ptr_->data),
|
||||
ptr_->datalen,
|
||||
dest,
|
||||
arraysize(dest));
|
||||
return std::string(dest, written);
|
||||
}
|
||||
|
||||
CID CID::kInvalid{};
|
||||
|
||||
// ============================================================================
|
||||
// CID::Hash
|
||||
|
||||
size_t CID::Hash::operator()(const CID& cid) const {
|
||||
size_t hash = 0;
|
||||
for (size_t n = 0; n < cid.length(); n++) {
|
||||
hash ^= std::hash<uint8_t>{}(cid.ptr_->data[n] + 0x9e3779b9 + (hash << 6) +
|
||||
(hash >> 2));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CID::Factory
|
||||
|
||||
namespace {
|
||||
class RandomCIDFactory : public CID::Factory {
|
||||
public:
|
||||
RandomCIDFactory() = default;
|
||||
RandomCIDFactory(const RandomCIDFactory&) = delete;
|
||||
RandomCIDFactory(RandomCIDFactory&&) = delete;
|
||||
RandomCIDFactory& operator=(const RandomCIDFactory&) = delete;
|
||||
RandomCIDFactory& operator=(RandomCIDFactory&&) = delete;
|
||||
|
||||
CID Generate(size_t length_hint) const override {
|
||||
DCHECK_GE(length_hint, CID::kMinLength);
|
||||
DCHECK_LE(length_hint, CID::kMaxLength);
|
||||
Mutex::ScopedLock lock(mutex_);
|
||||
maybe_refresh_pool(length_hint);
|
||||
auto start = pool_ + pos_;
|
||||
pos_ += length_hint;
|
||||
return CID(start, length_hint);
|
||||
}
|
||||
|
||||
void GenerateInto(ngtcp2_cid* cid,
|
||||
size_t length_hint = CID::kMaxLength) const override {
|
||||
DCHECK_GE(length_hint, CID::kMinLength);
|
||||
DCHECK_LE(length_hint, CID::kMaxLength);
|
||||
Mutex::ScopedLock lock(mutex_);
|
||||
maybe_refresh_pool(length_hint);
|
||||
auto start = pool_ + pos_;
|
||||
pos_ += length_hint;
|
||||
ngtcp2_cid_init(cid, start, length_hint);
|
||||
}
|
||||
|
||||
private:
|
||||
void maybe_refresh_pool(size_t length_hint) const {
|
||||
// We generate a pool of random data kPoolSize in length
|
||||
// and pull our random CID from that. If we don't have
|
||||
// enough random random remaining in the pool to generate
|
||||
// a CID of the requested size, we regenerate the pool
|
||||
// and reset it to zero.
|
||||
if (pos_ + length_hint > kPoolSize) {
|
||||
CHECK(crypto::CSPRNG(pool_, kPoolSize).is_ok());
|
||||
pos_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr int kPoolSize = 4096;
|
||||
mutable int pos_ = kPoolSize;
|
||||
mutable uint8_t pool_[kPoolSize];
|
||||
mutable Mutex mutex_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
const CID::Factory& CID::Factory::random() {
|
||||
static RandomCIDFactory instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace quic
|
||||
} // namespace node
|
||||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
126
src/quic/cid.h
Normal file
126
src/quic/cid.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
#include <memory_tracker.h>
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
#include <string>
|
||||
|
||||
namespace node {
|
||||
namespace quic {
|
||||
|
||||
// CIDS are used to identify endpoints participating in a QUIC session.
|
||||
// Once created, CID instances are immutable.
|
||||
//
|
||||
// CIDs contain between 1 to 20 bytes. Most typically they are selected
|
||||
// randomly but there is a spec for creating "routable" CIDs that encode
|
||||
// a specific structure that is meaningful only to the side that creates
|
||||
// the CID. For most purposes, CIDs should be treated as opaque tokens.
|
||||
//
|
||||
// Each peer in a QUIC session generates one or more CIDs that the *other*
|
||||
// peer will use to identify the session. When a QUIC client initiates a
|
||||
// brand new session, it will initially generates a CID of its own (its
|
||||
// source CID) and a random placeholder CID for the server (the original
|
||||
// destination CID). When the server receives the initial packet, it will
|
||||
// generate its own source CID and use the clients source CID as the
|
||||
// server's destination CID.
|
||||
//
|
||||
// Client Server
|
||||
// -------------------------------------------
|
||||
// Source CID <====> Destination CID
|
||||
// Destination CID <====> Source CID
|
||||
//
|
||||
// While the connection is being established, it is possible for either
|
||||
// peer to generate additional CIDs that are also associated with the
|
||||
// connection.
|
||||
class CID final : public MemoryRetainer {
|
||||
public:
|
||||
static constexpr size_t kMinLength = NGTCP2_MIN_CIDLEN;
|
||||
static constexpr size_t kMaxLength = NGTCP2_MAX_CIDLEN;
|
||||
|
||||
// Copy the given ngtcp2_cid.
|
||||
explicit CID(const ngtcp2_cid& cid);
|
||||
|
||||
// Copy the given buffer as a CID. The len must be within
|
||||
// kMinLength and kMaxLength.
|
||||
explicit CID(const uint8_t* data, size_t len);
|
||||
|
||||
// Wrap the given ngtcp2_cid. The CID does not take ownership
|
||||
// of the underlying ngtcp2_cid.
|
||||
explicit CID(const ngtcp2_cid* cid);
|
||||
|
||||
CID(const CID& other);
|
||||
CID(CID&& other) = delete;
|
||||
|
||||
struct Hash final {
|
||||
size_t operator()(const CID& cid) const;
|
||||
};
|
||||
|
||||
bool operator==(const CID& other) const noexcept;
|
||||
bool operator!=(const CID& other) const noexcept;
|
||||
|
||||
operator const uint8_t*() const;
|
||||
operator const ngtcp2_cid&() const;
|
||||
operator const ngtcp2_cid*() const;
|
||||
|
||||
// True if the CID length is at least kMinLength;
|
||||
operator bool() const;
|
||||
size_t length() const;
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
SET_NO_MEMORY_INFO()
|
||||
SET_MEMORY_INFO_NAME(CID)
|
||||
SET_SELF_SIZE(CID)
|
||||
|
||||
template <typename T>
|
||||
using Map = std::unordered_map<CID, T, CID::Hash>;
|
||||
|
||||
// A CID::Factory, as the name suggests, is used to create new CIDs.
|
||||
// Per https://datatracker.ietf.org/doc/draft-ietf-quic-load-balancers/, QUIC
|
||||
// implementations MAY use the Connection ID associated with a QUIC session
|
||||
// as a routing mechanism, with each CID instance securely encoding the
|
||||
// routing information. By default, our implementation creates CIDs randomly
|
||||
// but will allow user code to provide their own CID::Factory implementation.
|
||||
class Factory;
|
||||
|
||||
static CID kInvalid;
|
||||
|
||||
private:
|
||||
// The default constructor creates an empty, zero-length CID.
|
||||
// Zero-length CIDs are not usable. We use them as a placeholder
|
||||
// for a missing or empty CID value.
|
||||
CID();
|
||||
|
||||
ngtcp2_cid cid_;
|
||||
const ngtcp2_cid* ptr_;
|
||||
|
||||
friend struct Hash;
|
||||
};
|
||||
|
||||
class CID::Factory {
|
||||
public:
|
||||
virtual ~Factory() = default;
|
||||
|
||||
// Generate a new CID. The length_hint must be between CID::kMinLength
|
||||
// and CID::kMaxLength. The implementation can choose to ignore the length.
|
||||
virtual CID Generate(size_t length_hint = CID::kMaxLength) const = 0;
|
||||
|
||||
// Generate a new CID into the given ngtcp2_cid. This variation of
|
||||
// Generate should be used far less commonly. It is provided largely
|
||||
// for a couple of internal cases.
|
||||
virtual void GenerateInto(ngtcp2_cid* cid,
|
||||
size_t length_hint = CID::kMaxLength) const = 0;
|
||||
|
||||
// The default random CID generator instance.
|
||||
static const Factory& random();
|
||||
|
||||
// TODO(@jasnell): This will soon also include additional implementations
|
||||
// of CID::Factory that implement the QUIC Load Balancers spec.
|
||||
};
|
||||
|
||||
} // namespace quic
|
||||
} // namespace node
|
||||
|
||||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
259
src/quic/data.cc
Normal file
259
src/quic/data.cc
Normal file
@@ -0,0 +1,259 @@
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
|
||||
#include "data.h"
|
||||
#include <env-inl.h>
|
||||
#include <memory_tracker-inl.h>
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
#include <node_sockaddr-inl.h>
|
||||
#include <v8.h>
|
||||
#include "util.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
using v8::Array;
|
||||
using v8::BigInt;
|
||||
using v8::Integer;
|
||||
using v8::Local;
|
||||
using v8::MaybeLocal;
|
||||
using v8::Undefined;
|
||||
using v8::Value;
|
||||
|
||||
namespace quic {
|
||||
|
||||
Path::Path(const SocketAddress& local, const SocketAddress& remote) {
|
||||
ngtcp2_addr_init(&this->local, local.data(), local.length());
|
||||
ngtcp2_addr_init(&this->remote, remote.data(), remote.length());
|
||||
}
|
||||
|
||||
PathStorage::PathStorage() {
|
||||
ngtcp2_path_storage_zero(this);
|
||||
}
|
||||
PathStorage::operator ngtcp2_path() {
|
||||
return path;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Store::Store(std::shared_ptr<v8::BackingStore> store,
|
||||
size_t length,
|
||||
size_t offset)
|
||||
: store_(std::move(store)), length_(length), offset_(offset) {
|
||||
CHECK_LE(offset_, store->ByteLength());
|
||||
CHECK_LE(length_, store->ByteLength() - offset_);
|
||||
}
|
||||
|
||||
Store::Store(std::unique_ptr<v8::BackingStore> store,
|
||||
size_t length,
|
||||
size_t offset)
|
||||
: store_(std::move(store)), length_(length), offset_(offset) {
|
||||
CHECK_LE(offset_, store->ByteLength());
|
||||
CHECK_LE(length_, store->ByteLength() - offset_);
|
||||
}
|
||||
|
||||
Store::Store(v8::Local<v8::ArrayBuffer> buffer, Option option)
|
||||
: Store(buffer->GetBackingStore(), buffer->ByteLength()) {
|
||||
if (option == Option::DETACH) {
|
||||
USE(buffer->Detach(Local<Value>()));
|
||||
}
|
||||
}
|
||||
|
||||
Store::Store(v8::Local<v8::ArrayBufferView> view, Option option)
|
||||
: Store(view->Buffer()->GetBackingStore(),
|
||||
view->ByteLength(),
|
||||
view->ByteOffset()) {
|
||||
if (option == Option::DETACH) {
|
||||
USE(view->Buffer()->Detach(Local<Value>()));
|
||||
}
|
||||
}
|
||||
|
||||
Store::operator bool() const {
|
||||
return store_ != nullptr;
|
||||
}
|
||||
size_t Store::length() const {
|
||||
return length_;
|
||||
}
|
||||
|
||||
template <typename T, typename t>
|
||||
T Store::convert() const {
|
||||
T buf;
|
||||
buf.base =
|
||||
store_ != nullptr ? static_cast<t*>(store_->Data()) + offset_ : nullptr;
|
||||
buf.len = length_;
|
||||
return buf;
|
||||
}
|
||||
|
||||
Store::operator uv_buf_t() const {
|
||||
return convert<uv_buf_t, char>();
|
||||
}
|
||||
|
||||
Store::operator ngtcp2_vec() const {
|
||||
return convert<ngtcp2_vec, uint8_t>();
|
||||
}
|
||||
|
||||
Store::operator nghttp3_vec() const {
|
||||
return convert<nghttp3_vec, uint8_t>();
|
||||
}
|
||||
|
||||
void Store::MemoryInfo(MemoryTracker* tracker) const {
|
||||
tracker->TrackField("store", store_);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
namespace {
|
||||
std::string TypeName(QuicError::Type type) {
|
||||
switch (type) {
|
||||
case QuicError::Type::APPLICATION:
|
||||
return "APPLICATION";
|
||||
case QuicError::Type::TRANSPORT:
|
||||
return "TRANSPORT";
|
||||
case QuicError::Type::VERSION_NEGOTIATION:
|
||||
return "VERSION_NEGOTIATION";
|
||||
case QuicError::Type::IDLE_CLOSE:
|
||||
return "IDLE_CLOSE";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
QuicError::QuicError(const std::string_view reason)
|
||||
: reason_(reason), ptr_(&error_) {}
|
||||
|
||||
QuicError::QuicError(const ngtcp2_connection_close_error* ptr)
|
||||
: reason_(reinterpret_cast<const char*>(ptr->reason), ptr->reasonlen),
|
||||
ptr_(ptr) {}
|
||||
|
||||
QuicError::QuicError(const ngtcp2_connection_close_error& error)
|
||||
: reason_(reinterpret_cast<const char*>(error.reason), error.reasonlen),
|
||||
error_(error),
|
||||
ptr_(&error_) {}
|
||||
|
||||
QuicError::operator bool() const {
|
||||
if ((code() == QUIC_NO_ERROR && type() == Type::TRANSPORT) ||
|
||||
((code() == QUIC_APP_NO_ERROR && type() == Type::APPLICATION))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t* QuicError::reason_c_str() const {
|
||||
return reinterpret_cast<const uint8_t*>(reason_.c_str());
|
||||
}
|
||||
|
||||
bool QuicError::operator!=(const QuicError& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool QuicError::operator==(const QuicError& other) const {
|
||||
if (this == &other) return true;
|
||||
return type() == other.type() && code() == other.code() &&
|
||||
frame_type() == other.frame_type();
|
||||
}
|
||||
|
||||
QuicError::Type QuicError::type() const {
|
||||
return static_cast<Type>(ptr_->type);
|
||||
}
|
||||
|
||||
QuicError::error_code QuicError::code() const {
|
||||
return ptr_->error_code;
|
||||
}
|
||||
|
||||
uint64_t QuicError::frame_type() const {
|
||||
return ptr_->frame_type;
|
||||
}
|
||||
|
||||
const std::string_view QuicError::reason() const {
|
||||
return reason_;
|
||||
}
|
||||
|
||||
QuicError::operator const ngtcp2_connection_close_error&() const {
|
||||
return *ptr_;
|
||||
}
|
||||
|
||||
QuicError::operator const ngtcp2_connection_close_error*() const {
|
||||
return ptr_;
|
||||
}
|
||||
|
||||
MaybeLocal<Value> QuicError::ToV8Value(Environment* env) const {
|
||||
Local<Value> argv[] = {
|
||||
Integer::New(env->isolate(), static_cast<int>(type())),
|
||||
BigInt::NewFromUnsigned(env->isolate(), code()),
|
||||
Undefined(env->isolate()),
|
||||
};
|
||||
|
||||
if (reason_.length() > 0 &&
|
||||
!node::ToV8Value(env->context(), reason()).ToLocal(&argv[2])) {
|
||||
return MaybeLocal<Value>();
|
||||
}
|
||||
return Array::New(env->isolate(), argv, arraysize(argv)).As<Value>();
|
||||
}
|
||||
|
||||
std::string QuicError::ToString() const {
|
||||
std::string str = "QuicError(";
|
||||
str += TypeName(type()) + ") ";
|
||||
str += std::to_string(code());
|
||||
if (!reason_.empty()) str += ": " + reason_;
|
||||
return str;
|
||||
}
|
||||
|
||||
void QuicError::MemoryInfo(MemoryTracker* tracker) const {
|
||||
tracker->TrackField("reason", reason_.length());
|
||||
}
|
||||
|
||||
QuicError QuicError::ForTransport(error_code code,
|
||||
const std::string_view reason) {
|
||||
QuicError error(reason);
|
||||
ngtcp2_connection_close_error_set_transport_error(
|
||||
&error.error_, code, error.reason_c_str(), reason.length());
|
||||
return error;
|
||||
}
|
||||
|
||||
QuicError QuicError::ForApplication(error_code code,
|
||||
const std::string_view reason) {
|
||||
QuicError error(reason);
|
||||
ngtcp2_connection_close_error_set_application_error(
|
||||
&error.error_, code, error.reason_c_str(), reason.length());
|
||||
return error;
|
||||
}
|
||||
|
||||
QuicError QuicError::ForVersionNegotiation(const std::string_view reason) {
|
||||
return ForNgtcp2Error(NGTCP2_ERR_RECV_VERSION_NEGOTIATION, reason);
|
||||
}
|
||||
|
||||
QuicError QuicError::ForIdleClose(const std::string_view reason) {
|
||||
return ForNgtcp2Error(NGTCP2_ERR_IDLE_CLOSE, reason);
|
||||
}
|
||||
|
||||
QuicError QuicError::ForNgtcp2Error(int code, const std::string_view reason) {
|
||||
QuicError error(reason);
|
||||
ngtcp2_connection_close_error_set_transport_error_liberr(
|
||||
&error.error_, code, error.reason_c_str(), reason.length());
|
||||
return error;
|
||||
}
|
||||
|
||||
QuicError QuicError::ForTlsAlert(int code, const std::string_view reason) {
|
||||
QuicError error(reason);
|
||||
ngtcp2_connection_close_error_set_transport_error_tls_alert(
|
||||
&error.error_, code, error.reason_c_str(), reason.length());
|
||||
return error;
|
||||
}
|
||||
|
||||
QuicError QuicError::FromConnectionClose(ngtcp2_conn* session) {
|
||||
QuicError error;
|
||||
ngtcp2_conn_get_connection_close_error(session, &error.error_);
|
||||
return error;
|
||||
}
|
||||
|
||||
QuicError QuicError::TRANSPORT_NO_ERROR =
|
||||
QuicError::ForTransport(QuicError::QUIC_NO_ERROR);
|
||||
QuicError QuicError::APPLICATION_NO_ERROR =
|
||||
QuicError::ForApplication(QuicError::QUIC_APP_NO_ERROR);
|
||||
QuicError QuicError::VERSION_NEGOTIATION = QuicError::ForVersionNegotiation();
|
||||
QuicError QuicError::IDLE_CLOSE = QuicError::ForIdleClose();
|
||||
QuicError QuicError::INTERNAL_ERROR =
|
||||
QuicError::ForNgtcp2Error(NGTCP2_ERR_INTERNAL);
|
||||
|
||||
} // namespace quic
|
||||
} // namespace node
|
||||
|
||||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
137
src/quic/data.h
Normal file
137
src/quic/data.h
Normal file
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
|
||||
#include <memory_tracker.h>
|
||||
#include <nghttp3/nghttp3.h>
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
#include <node_internals.h>
|
||||
#include <node_sockaddr.h>
|
||||
#include <v8.h>
|
||||
|
||||
namespace node {
|
||||
namespace quic {
|
||||
|
||||
struct Path final : public ngtcp2_path {
|
||||
Path(const SocketAddress& local, const SocketAddress& remote);
|
||||
};
|
||||
|
||||
struct PathStorage final : public ngtcp2_path_storage {
|
||||
PathStorage();
|
||||
operator ngtcp2_path();
|
||||
};
|
||||
|
||||
class Store final : public MemoryRetainer {
|
||||
public:
|
||||
Store() = default;
|
||||
|
||||
Store(std::shared_ptr<v8::BackingStore> store,
|
||||
size_t length,
|
||||
size_t offset = 0);
|
||||
Store(std::unique_ptr<v8::BackingStore> store,
|
||||
size_t length,
|
||||
size_t offset = 0);
|
||||
|
||||
enum class Option {
|
||||
NONE,
|
||||
DETACH,
|
||||
};
|
||||
|
||||
Store(v8::Local<v8::ArrayBuffer> buffer, Option option = Option::NONE);
|
||||
Store(v8::Local<v8::ArrayBufferView> view, Option option = Option::NONE);
|
||||
|
||||
operator uv_buf_t() const;
|
||||
operator ngtcp2_vec() const;
|
||||
operator nghttp3_vec() const;
|
||||
operator bool() const;
|
||||
size_t length() const;
|
||||
|
||||
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||
SET_MEMORY_INFO_NAME(Store)
|
||||
SET_SELF_SIZE(Store)
|
||||
|
||||
private:
|
||||
template <typename T, typename t>
|
||||
T convert() const;
|
||||
std::shared_ptr<v8::BackingStore> store_;
|
||||
size_t length_ = 0;
|
||||
size_t offset_ = 0;
|
||||
};
|
||||
|
||||
class QuicError final : public MemoryRetainer {
|
||||
public:
|
||||
using error_code = uint64_t;
|
||||
|
||||
static constexpr error_code QUIC_NO_ERROR = NGTCP2_NO_ERROR;
|
||||
static constexpr error_code QUIC_APP_NO_ERROR = 65280;
|
||||
|
||||
enum class Type {
|
||||
TRANSPORT = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT,
|
||||
APPLICATION = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION,
|
||||
VERSION_NEGOTIATION =
|
||||
NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_VERSION_NEGOTIATION,
|
||||
IDLE_CLOSE = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE,
|
||||
};
|
||||
|
||||
static constexpr error_code QUIC_ERROR_TYPE_TRANSPORT =
|
||||
NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT;
|
||||
static constexpr error_code QUIC_ERROR_TYPE_APPLICATION =
|
||||
NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION;
|
||||
|
||||
explicit QuicError(const std::string_view reason = "");
|
||||
explicit QuicError(const ngtcp2_connection_close_error* ptr);
|
||||
explicit QuicError(const ngtcp2_connection_close_error& error);
|
||||
|
||||
Type type() const;
|
||||
error_code code() const;
|
||||
const std::string_view reason() const;
|
||||
uint64_t frame_type() const;
|
||||
|
||||
operator const ngtcp2_connection_close_error&() const;
|
||||
operator const ngtcp2_connection_close_error*() const;
|
||||
|
||||
// Returns false if the QuicError uses a no_error code with type
|
||||
// transport or application.
|
||||
operator bool() const;
|
||||
|
||||
bool operator==(const QuicError& other) const;
|
||||
bool operator!=(const QuicError& other) const;
|
||||
|
||||
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||
SET_MEMORY_INFO_NAME(QuicError)
|
||||
SET_SELF_SIZE(QuicError)
|
||||
|
||||
std::string ToString() const;
|
||||
v8::MaybeLocal<v8::Value> ToV8Value(Environment* env) const;
|
||||
|
||||
static QuicError ForTransport(error_code code,
|
||||
const std::string_view reason = "");
|
||||
static QuicError ForApplication(error_code code,
|
||||
const std::string_view reason = "");
|
||||
static QuicError ForVersionNegotiation(const std::string_view reason = "");
|
||||
static QuicError ForIdleClose(const std::string_view reason = "");
|
||||
static QuicError ForNgtcp2Error(int code, const std::string_view reason = "");
|
||||
static QuicError ForTlsAlert(int code, const std::string_view reason = "");
|
||||
|
||||
static QuicError FromConnectionClose(ngtcp2_conn* session);
|
||||
|
||||
static QuicError TRANSPORT_NO_ERROR;
|
||||
static QuicError APPLICATION_NO_ERROR;
|
||||
static QuicError VERSION_NEGOTIATION;
|
||||
static QuicError IDLE_CLOSE;
|
||||
static QuicError INTERNAL_ERROR;
|
||||
|
||||
private:
|
||||
const uint8_t* reason_c_str() const;
|
||||
|
||||
std::string reason_;
|
||||
ngtcp2_connection_close_error error_;
|
||||
const ngtcp2_connection_close_error* ptr_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace quic
|
||||
} // namespace node
|
||||
|
||||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
159
src/quic/preferredaddress.cc
Normal file
159
src/quic/preferredaddress.cc
Normal file
@@ -0,0 +1,159 @@
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
|
||||
#include "preferredaddress.h"
|
||||
#include <env-inl.h>
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
#include <node_errors.h>
|
||||
#include <node_sockaddr-inl.h>
|
||||
#include <util-inl.h>
|
||||
#include <uv.h>
|
||||
#include <v8.h>
|
||||
|
||||
namespace node {
|
||||
|
||||
using v8::Just;
|
||||
using v8::Local;
|
||||
using v8::Maybe;
|
||||
using v8::Nothing;
|
||||
using v8::Value;
|
||||
|
||||
namespace quic {
|
||||
|
||||
namespace {
|
||||
template <int FAMILY>
|
||||
std::optional<const PreferredAddress::AddressInfo> get_address_info(
|
||||
const ngtcp2_preferred_addr& paddr) {
|
||||
if constexpr (FAMILY == AF_INET) {
|
||||
if (!paddr.ipv4_present) return std::nullopt;
|
||||
PreferredAddress::AddressInfo address;
|
||||
address.family = FAMILY;
|
||||
address.port = paddr.ipv4_port;
|
||||
if (uv_inet_ntop(
|
||||
FAMILY, paddr.ipv4_addr, address.host, sizeof(address.host)) == 0) {
|
||||
address.address = address.host;
|
||||
}
|
||||
return address;
|
||||
} else {
|
||||
if (!paddr.ipv6_present) return std::nullopt;
|
||||
PreferredAddress::AddressInfo address;
|
||||
address.family = FAMILY;
|
||||
address.port = paddr.ipv6_port;
|
||||
if (uv_inet_ntop(
|
||||
FAMILY, paddr.ipv6_addr, address.host, sizeof(address.host)) == 0) {
|
||||
address.address = address.host;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
template <int FAMILY>
|
||||
void copy_to_transport_params(ngtcp2_transport_params* params,
|
||||
const sockaddr* addr) {
|
||||
params->preferred_address_present = true;
|
||||
if constexpr (FAMILY == AF_INET) {
|
||||
const sockaddr_in* src = reinterpret_cast<const sockaddr_in*>(addr);
|
||||
params->preferred_address.ipv4_port = SocketAddress::GetPort(addr);
|
||||
memcpy(params->preferred_address.ipv4_addr,
|
||||
&src->sin_addr,
|
||||
sizeof(params->preferred_address.ipv4_addr));
|
||||
} else {
|
||||
DCHECK_EQ(FAMILY, AF_INET6);
|
||||
const sockaddr_in6* src = reinterpret_cast<const sockaddr_in6*>(addr);
|
||||
params->preferred_address.ipv6_port = SocketAddress::GetPort(addr);
|
||||
memcpy(params->preferred_address.ipv6_addr,
|
||||
&src->sin6_addr,
|
||||
sizeof(params->preferred_address.ipv4_addr));
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
bool resolve(const PreferredAddress::AddressInfo& address,
|
||||
uv_getaddrinfo_t* req) {
|
||||
addrinfo hints{};
|
||||
hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
|
||||
hints.ai_family = address.family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
// ngtcp2 requires the selection of the preferred address
|
||||
// to be synchronous, which means we have to do a sync resolve
|
||||
// using uv_getaddrinfo here.
|
||||
return uv_getaddrinfo(nullptr,
|
||||
req,
|
||||
nullptr,
|
||||
address.host,
|
||||
// TODO(@jasnell): The to_string here is not really
|
||||
// the most performant way of converting the uint16_t
|
||||
// port into a string. Depending on execution count,
|
||||
// the potential cost here could be mitigated with a
|
||||
// more efficient conversion. For now, however, this
|
||||
// works.
|
||||
std::to_string(address.port).c_str(),
|
||||
&hints) == 0 &&
|
||||
req->addrinfo != nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Maybe<PreferredAddress::Policy> PreferredAddress::GetPolicy(
|
||||
Environment* env, Local<Value> value) {
|
||||
CHECK(value->IsUint32());
|
||||
uint32_t val = 0;
|
||||
if (value->Uint32Value(env->context()).To(&val)) {
|
||||
switch (val) {
|
||||
case QUIC_PREFERRED_ADDRESS_USE:
|
||||
return Just(Policy::USE_PREFERRED_ADDRESS);
|
||||
case QUIC_PREFERRED_ADDRESS_IGNORE:
|
||||
return Just(Policy::IGNORE_PREFERRED_ADDRESS);
|
||||
}
|
||||
}
|
||||
THROW_ERR_INVALID_ARG_VALUE(
|
||||
env, "%d is not a valid preferred address policy", val);
|
||||
return Nothing<Policy>();
|
||||
}
|
||||
|
||||
PreferredAddress::PreferredAddress(ngtcp2_path* dest,
|
||||
const ngtcp2_preferred_addr* paddr)
|
||||
: dest_(dest), paddr_(paddr) {
|
||||
DCHECK_NOT_NULL(paddr);
|
||||
DCHECK_NOT_NULL(dest);
|
||||
}
|
||||
|
||||
std::optional<const PreferredAddress::AddressInfo> PreferredAddress::ipv4()
|
||||
const {
|
||||
return get_address_info<AF_INET>(*paddr_);
|
||||
}
|
||||
|
||||
std::optional<const PreferredAddress::AddressInfo> PreferredAddress::ipv6()
|
||||
const {
|
||||
return get_address_info<AF_INET6>(*paddr_);
|
||||
}
|
||||
|
||||
void PreferredAddress::Use(const AddressInfo& address) {
|
||||
uv_getaddrinfo_t req;
|
||||
auto on_exit = OnScopeLeave([&] {
|
||||
if (req.addrinfo != nullptr) uv_freeaddrinfo(req.addrinfo);
|
||||
});
|
||||
|
||||
if (resolve(address, &req)) {
|
||||
DCHECK_NOT_NULL(req.addrinfo);
|
||||
dest_->remote.addrlen = req.addrinfo->ai_addrlen;
|
||||
memcpy(dest_->remote.addr, req.addrinfo->ai_addr, req.addrinfo->ai_addrlen);
|
||||
}
|
||||
}
|
||||
|
||||
void PreferredAddress::Set(ngtcp2_transport_params* params,
|
||||
const sockaddr* addr) {
|
||||
DCHECK_NOT_NULL(params);
|
||||
DCHECK_NOT_NULL(addr);
|
||||
switch (addr->sa_family) {
|
||||
case AF_INET:
|
||||
return copy_to_transport_params<AF_INET>(params, addr);
|
||||
case AF_INET6:
|
||||
return copy_to_transport_params<AF_INET6>(params, addr);
|
||||
}
|
||||
// Any other value is just ignored.
|
||||
}
|
||||
|
||||
} // namespace quic
|
||||
} // namespace node
|
||||
|
||||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
72
src/quic/preferredaddress.h
Normal file
72
src/quic/preferredaddress.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
|
||||
#include <env.h>
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
#include <node_internals.h>
|
||||
#include <v8.h>
|
||||
#include <string>
|
||||
|
||||
namespace node {
|
||||
namespace quic {
|
||||
|
||||
// PreferredAddress is a helper class used only when a client Session receives
|
||||
// an advertised preferred address from a server. The helper provides
|
||||
// information about the server advertised preferred address and allows
|
||||
// the preferred address to be selected.
|
||||
class PreferredAddress final {
|
||||
public:
|
||||
enum class Policy {
|
||||
// Ignore the server-advertised preferred address.
|
||||
IGNORE_PREFERRED_ADDRESS,
|
||||
// Use the server-advertised preferred address.
|
||||
USE_PREFERRED_ADDRESS,
|
||||
};
|
||||
|
||||
// The QUIC_* constants are expected to be exported out to be used on
|
||||
// the JavaScript side of the API.
|
||||
static constexpr uint32_t QUIC_PREFERRED_ADDRESS_USE =
|
||||
static_cast<uint32_t>(Policy::USE_PREFERRED_ADDRESS);
|
||||
static constexpr uint32_t QUIC_PREFERRED_ADDRESS_IGNORE =
|
||||
static_cast<uint32_t>(Policy::IGNORE_PREFERRED_ADDRESS);
|
||||
|
||||
static v8::Maybe<Policy> GetPolicy(Environment* env,
|
||||
v8::Local<v8::Value> value);
|
||||
|
||||
struct AddressInfo final {
|
||||
char host[NI_MAXHOST];
|
||||
int family;
|
||||
uint16_t port;
|
||||
std::string_view address;
|
||||
};
|
||||
|
||||
explicit PreferredAddress(ngtcp2_path* dest,
|
||||
const ngtcp2_preferred_addr* paddr);
|
||||
PreferredAddress(const PreferredAddress&) = delete;
|
||||
PreferredAddress(PreferredAddress&&) = delete;
|
||||
PreferredAddress& operator=(const PreferredAddress&) = delete;
|
||||
PreferredAddress& operator=(PreferredAddress&&) = delete;
|
||||
|
||||
void Use(const AddressInfo& address);
|
||||
|
||||
std::optional<const AddressInfo> ipv4() const;
|
||||
std::optional<const AddressInfo> ipv6() const;
|
||||
|
||||
// Set the preferred address in the transport params.
|
||||
// The address family (ipv4 or ipv6) will be automatically
|
||||
// detected from the given addr. Any other address family
|
||||
// will be ignored.
|
||||
static void Set(ngtcp2_transport_params* params, const sockaddr* addr);
|
||||
|
||||
private:
|
||||
ngtcp2_path* dest_;
|
||||
const ngtcp2_preferred_addr* paddr_;
|
||||
};
|
||||
|
||||
} // namespace quic
|
||||
} // namespace node
|
||||
|
||||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
95
test/cctest/test_quic_cid.cc
Normal file
95
test/cctest/test_quic_cid.cc
Normal file
@@ -0,0 +1,95 @@
|
||||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
#include <gtest/gtest.h>
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
#include <quic/cid.h>
|
||||
#include <util-inl.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
using node::quic::CID;
|
||||
|
||||
TEST(CID, Basic) {
|
||||
auto& random = CID::Factory::random();
|
||||
{
|
||||
auto cid = random.Generate();
|
||||
CHECK_EQ(cid.length(), CID::kMaxLength);
|
||||
CHECK(cid);
|
||||
CHECK_EQ(cid, cid);
|
||||
}
|
||||
{
|
||||
auto cid = random.Generate(5);
|
||||
CHECK_EQ(cid.length(), 5);
|
||||
CHECK(cid);
|
||||
}
|
||||
{
|
||||
auto cid1 = random.Generate();
|
||||
auto cid2 = random.Generate();
|
||||
CHECK_NE(cid1, cid2);
|
||||
}
|
||||
{
|
||||
auto cid1 = random.Generate(5);
|
||||
auto cid2 = random.Generate();
|
||||
CHECK_NE(cid1, cid2);
|
||||
}
|
||||
{
|
||||
auto cid1 = random.Generate();
|
||||
auto cid2 = random.Generate(5);
|
||||
CHECK_NE(cid1, cid2);
|
||||
}
|
||||
{
|
||||
auto cid = CID::kInvalid;
|
||||
// They are copy constructible...
|
||||
auto cid2 = cid;
|
||||
CHECK(!cid);
|
||||
CHECK_EQ(cid.length(), 0);
|
||||
CHECK_EQ(cid, cid2);
|
||||
}
|
||||
{
|
||||
auto cid1 = random.Generate();
|
||||
auto cid2 = random.Generate();
|
||||
CID::Map<std::string> map;
|
||||
map[cid1] = "hello";
|
||||
map[cid2] = "there";
|
||||
CHECK_EQ(map[cid1], "hello");
|
||||
CHECK_EQ(map[cid2], "there");
|
||||
CHECK_NE(map[cid2], "hello");
|
||||
CHECK_NE(map[cid1], "there");
|
||||
}
|
||||
{
|
||||
ngtcp2_cid cid_;
|
||||
uint8_t data[] = {1, 2, 3, 4, 5};
|
||||
ngtcp2_cid_init(&cid_, data, 5);
|
||||
auto cid = CID(cid_);
|
||||
// This variation of the constructor copies the cid_, so if we
|
||||
// modify the original data it doesn't change in the CID.
|
||||
cid_.data[0] = 9;
|
||||
CHECK_EQ(cid.length(), 5);
|
||||
CHECK_EQ(cid.ToString(), "0102030405");
|
||||
}
|
||||
{
|
||||
ngtcp2_cid cid_;
|
||||
uint8_t data[] = {1, 2, 3, 4, 5};
|
||||
ngtcp2_cid_init(&cid_, data, 5);
|
||||
auto cid = CID(&cid_);
|
||||
// This variation of the constructor wraps the cid_, so if we
|
||||
// modify the original data it does change in the CID.
|
||||
cid_.data[0] = 9;
|
||||
CHECK_EQ(cid.length(), 5);
|
||||
CHECK_EQ(cid.ToString(), "0902030405");
|
||||
}
|
||||
{
|
||||
// Generate a bunch to ensure that the pool is regenerated.
|
||||
for (int n = 0; n < 1000; n++) {
|
||||
random.Generate();
|
||||
}
|
||||
}
|
||||
{
|
||||
ngtcp2_cid cid_;
|
||||
// Generate a bunch to ensure that the pool is regenerated.
|
||||
for (int n = 0; n < 1000; n++) {
|
||||
random.GenerateInto(&cid_, 10);
|
||||
CHECK_EQ(cid_.datalen, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
||||
Reference in New Issue
Block a user