Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

quic: implement various utilities classes to be used by the quic impl #47263

Closed
wants to merge 11 commits into from
10 changes: 10 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
jasnell marked this conversation as resolved.
Show resolved Hide resolved

// 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