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:
James M Snell
2023-03-26 15:05:44 -07:00
parent da2210ef3f
commit 09a4bb152f
8 changed files with 1006 additions and 0 deletions

View File

@@ -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
View 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
View 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
View 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
View 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

View 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

View 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

View 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