Skip to content

Commit

Permalink
quic: add multiple internal utilities
Browse files Browse the repository at this point in the history
* add the CID implementation
* add the PreferredAddress implementation
* add Path and PathStorage implementations
* add Store implementation
* add QuicError implementation

PR-URL: #47263
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
  • Loading branch information
jasnell committed Mar 29, 2023
1 parent da2210e commit 09a4bb1
Show file tree
Hide file tree
Showing 8 changed files with 1,006 additions and 0 deletions.
10 changes: 10 additions & 0 deletions node.gyp
Expand Up @@ -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"', {
Expand Down Expand Up @@ -836,6 +844,7 @@
[ 'node_use_openssl=="true"', {
'sources': [
'<@(node_crypto_sources)',
'<@(node_quic_sources)',
],
}],
[ 'OS in "linux freebsd mac solaris" and '
Expand Down Expand Up @@ -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', {
Expand Down
148 changes: 148 additions & 0 deletions src/quic/cid.cc
@@ -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 changes: 126 additions & 0 deletions src/quic/cid.h
@@ -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

0 comments on commit 09a4bb1

Please sign in to comment.