Skip to content

Commit 09a4bb1

Browse files
committed
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: #47263 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Stephen Belanger <[email protected]> Reviewed-By: Tobias Nießen <[email protected]>
1 parent da2210e commit 09a4bb1

File tree

8 files changed

+1006
-0
lines changed

8 files changed

+1006
-0
lines changed

node.gyp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,14 @@
335335
'src/node_crypto.cc',
336336
'src/node_crypto.h',
337337
],
338+
'node_quic_sources': [
339+
'src/quic/cid.cc',
340+
'src/quic/data.cc',
341+
'src/quic/preferredaddress.cc',
342+
'src/quic/cid.h',
343+
'src/quic/data.h',
344+
'src/quic/preferredaddress.h',
345+
],
338346
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
339347
'conditions': [
340348
['GENERATOR == "ninja"', {
@@ -836,6 +844,7 @@
836844
[ 'node_use_openssl=="true"', {
837845
'sources': [
838846
'<@(node_crypto_sources)',
847+
'<@(node_quic_sources)',
839848
],
840849
}],
841850
[ 'OS in "linux freebsd mac solaris" and '
@@ -1023,6 +1032,7 @@
10231032
'sources': [
10241033
'test/cctest/test_crypto_clienthello.cc',
10251034
'test/cctest/test_node_crypto.cc',
1035+
'test/cctest/test_quic_cid.cc',
10261036
]
10271037
}],
10281038
['v8_enable_inspector==1', {

src/quic/cid.cc

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
2+
#include "cid.h"
3+
#include <crypto/crypto_util.h>
4+
#include <memory_tracker-inl.h>
5+
#include <node_mutex.h>
6+
#include <string_bytes.h>
7+
8+
namespace node {
9+
namespace quic {
10+
11+
// ============================================================================
12+
// CID
13+
14+
CID::CID() : ptr_(&cid_) {
15+
cid_.datalen = 0;
16+
}
17+
18+
CID::CID(const ngtcp2_cid& cid) : CID(cid.data, cid.datalen) {}
19+
20+
CID::CID(const uint8_t* data, size_t len) : CID() {
21+
DCHECK_GE(len, kMinLength);
22+
DCHECK_LE(len, kMaxLength);
23+
ngtcp2_cid_init(&cid_, data, len);
24+
}
25+
26+
CID::CID(const ngtcp2_cid* cid) : ptr_(cid) {
27+
CHECK_NOT_NULL(cid);
28+
DCHECK_GE(cid->datalen, kMinLength);
29+
DCHECK_LE(cid->datalen, kMaxLength);
30+
}
31+
32+
CID::CID(const CID& other) : ptr_(&cid_) {
33+
CHECK_NOT_NULL(other.ptr_);
34+
ngtcp2_cid_init(&cid_, other.ptr_->data, other.ptr_->datalen);
35+
}
36+
37+
bool CID::operator==(const CID& other) const noexcept {
38+
if (this == &other || (length() == 0 && other.length() == 0)) return true;
39+
if (length() != other.length()) return false;
40+
return memcmp(ptr_->data, other.ptr_->data, ptr_->datalen) == 0;
41+
}
42+
43+
bool CID::operator!=(const CID& other) const noexcept {
44+
return !(*this == other);
45+
}
46+
47+
CID::operator const uint8_t*() const {
48+
return ptr_->data;
49+
}
50+
CID::operator const ngtcp2_cid&() const {
51+
return *ptr_;
52+
}
53+
CID::operator const ngtcp2_cid*() const {
54+
return ptr_;
55+
}
56+
CID::operator bool() const {
57+
return ptr_->datalen >= kMinLength;
58+
}
59+
60+
size_t CID::length() const {
61+
return ptr_->datalen;
62+
}
63+
64+
std::string CID::ToString() const {
65+
char dest[kMaxLength * 2];
66+
size_t written =
67+
StringBytes::hex_encode(reinterpret_cast<const char*>(ptr_->data),
68+
ptr_->datalen,
69+
dest,
70+
arraysize(dest));
71+
return std::string(dest, written);
72+
}
73+
74+
CID CID::kInvalid{};
75+
76+
// ============================================================================
77+
// CID::Hash
78+
79+
size_t CID::Hash::operator()(const CID& cid) const {
80+
size_t hash = 0;
81+
for (size_t n = 0; n < cid.length(); n++) {
82+
hash ^= std::hash<uint8_t>{}(cid.ptr_->data[n] + 0x9e3779b9 + (hash << 6) +
83+
(hash >> 2));
84+
}
85+
return hash;
86+
}
87+
88+
// ============================================================================
89+
// CID::Factory
90+
91+
namespace {
92+
class RandomCIDFactory : public CID::Factory {
93+
public:
94+
RandomCIDFactory() = default;
95+
RandomCIDFactory(const RandomCIDFactory&) = delete;
96+
RandomCIDFactory(RandomCIDFactory&&) = delete;
97+
RandomCIDFactory& operator=(const RandomCIDFactory&) = delete;
98+
RandomCIDFactory& operator=(RandomCIDFactory&&) = delete;
99+
100+
CID Generate(size_t length_hint) const override {
101+
DCHECK_GE(length_hint, CID::kMinLength);
102+
DCHECK_LE(length_hint, CID::kMaxLength);
103+
Mutex::ScopedLock lock(mutex_);
104+
maybe_refresh_pool(length_hint);
105+
auto start = pool_ + pos_;
106+
pos_ += length_hint;
107+
return CID(start, length_hint);
108+
}
109+
110+
void GenerateInto(ngtcp2_cid* cid,
111+
size_t length_hint = CID::kMaxLength) const override {
112+
DCHECK_GE(length_hint, CID::kMinLength);
113+
DCHECK_LE(length_hint, CID::kMaxLength);
114+
Mutex::ScopedLock lock(mutex_);
115+
maybe_refresh_pool(length_hint);
116+
auto start = pool_ + pos_;
117+
pos_ += length_hint;
118+
ngtcp2_cid_init(cid, start, length_hint);
119+
}
120+
121+
private:
122+
void maybe_refresh_pool(size_t length_hint) const {
123+
// We generate a pool of random data kPoolSize in length
124+
// and pull our random CID from that. If we don't have
125+
// enough random random remaining in the pool to generate
126+
// a CID of the requested size, we regenerate the pool
127+
// and reset it to zero.
128+
if (pos_ + length_hint > kPoolSize) {
129+
CHECK(crypto::CSPRNG(pool_, kPoolSize).is_ok());
130+
pos_ = 0;
131+
}
132+
}
133+
134+
static constexpr int kPoolSize = 4096;
135+
mutable int pos_ = kPoolSize;
136+
mutable uint8_t pool_[kPoolSize];
137+
mutable Mutex mutex_;
138+
};
139+
} // namespace
140+
141+
const CID::Factory& CID::Factory::random() {
142+
static RandomCIDFactory instance;
143+
return instance;
144+
}
145+
146+
} // namespace quic
147+
} // namespace node
148+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

src/quic/cid.h

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#pragma once
2+
3+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
4+
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
5+
#include <memory_tracker.h>
6+
#include <ngtcp2/ngtcp2.h>
7+
#include <string>
8+
9+
namespace node {
10+
namespace quic {
11+
12+
// CIDS are used to identify endpoints participating in a QUIC session.
13+
// Once created, CID instances are immutable.
14+
//
15+
// CIDs contain between 1 to 20 bytes. Most typically they are selected
16+
// randomly but there is a spec for creating "routable" CIDs that encode
17+
// a specific structure that is meaningful only to the side that creates
18+
// the CID. For most purposes, CIDs should be treated as opaque tokens.
19+
//
20+
// Each peer in a QUIC session generates one or more CIDs that the *other*
21+
// peer will use to identify the session. When a QUIC client initiates a
22+
// brand new session, it will initially generates a CID of its own (its
23+
// source CID) and a random placeholder CID for the server (the original
24+
// destination CID). When the server receives the initial packet, it will
25+
// generate its own source CID and use the clients source CID as the
26+
// server's destination CID.
27+
//
28+
// Client Server
29+
// -------------------------------------------
30+
// Source CID <====> Destination CID
31+
// Destination CID <====> Source CID
32+
//
33+
// While the connection is being established, it is possible for either
34+
// peer to generate additional CIDs that are also associated with the
35+
// connection.
36+
class CID final : public MemoryRetainer {
37+
public:
38+
static constexpr size_t kMinLength = NGTCP2_MIN_CIDLEN;
39+
static constexpr size_t kMaxLength = NGTCP2_MAX_CIDLEN;
40+
41+
// Copy the given ngtcp2_cid.
42+
explicit CID(const ngtcp2_cid& cid);
43+
44+
// Copy the given buffer as a CID. The len must be within
45+
// kMinLength and kMaxLength.
46+
explicit CID(const uint8_t* data, size_t len);
47+
48+
// Wrap the given ngtcp2_cid. The CID does not take ownership
49+
// of the underlying ngtcp2_cid.
50+
explicit CID(const ngtcp2_cid* cid);
51+
52+
CID(const CID& other);
53+
CID(CID&& other) = delete;
54+
55+
struct Hash final {
56+
size_t operator()(const CID& cid) const;
57+
};
58+
59+
bool operator==(const CID& other) const noexcept;
60+
bool operator!=(const CID& other) const noexcept;
61+
62+
operator const uint8_t*() const;
63+
operator const ngtcp2_cid&() const;
64+
operator const ngtcp2_cid*() const;
65+
66+
// True if the CID length is at least kMinLength;
67+
operator bool() const;
68+
size_t length() const;
69+
70+
std::string ToString() const;
71+
72+
SET_NO_MEMORY_INFO()
73+
SET_MEMORY_INFO_NAME(CID)
74+
SET_SELF_SIZE(CID)
75+
76+
template <typename T>
77+
using Map = std::unordered_map<CID, T, CID::Hash>;
78+
79+
// A CID::Factory, as the name suggests, is used to create new CIDs.
80+
// Per https://datatracker.ietf.org/doc/draft-ietf-quic-load-balancers/, QUIC
81+
// implementations MAY use the Connection ID associated with a QUIC session
82+
// as a routing mechanism, with each CID instance securely encoding the
83+
// routing information. By default, our implementation creates CIDs randomly
84+
// but will allow user code to provide their own CID::Factory implementation.
85+
class Factory;
86+
87+
static CID kInvalid;
88+
89+
private:
90+
// The default constructor creates an empty, zero-length CID.
91+
// Zero-length CIDs are not usable. We use them as a placeholder
92+
// for a missing or empty CID value.
93+
CID();
94+
95+
ngtcp2_cid cid_;
96+
const ngtcp2_cid* ptr_;
97+
98+
friend struct Hash;
99+
};
100+
101+
class CID::Factory {
102+
public:
103+
virtual ~Factory() = default;
104+
105+
// Generate a new CID. The length_hint must be between CID::kMinLength
106+
// and CID::kMaxLength. The implementation can choose to ignore the length.
107+
virtual CID Generate(size_t length_hint = CID::kMaxLength) const = 0;
108+
109+
// Generate a new CID into the given ngtcp2_cid. This variation of
110+
// Generate should be used far less commonly. It is provided largely
111+
// for a couple of internal cases.
112+
virtual void GenerateInto(ngtcp2_cid* cid,
113+
size_t length_hint = CID::kMaxLength) const = 0;
114+
115+
// The default random CID generator instance.
116+
static const Factory& random();
117+
118+
// TODO(@jasnell): This will soon also include additional implementations
119+
// of CID::Factory that implement the QUIC Load Balancers spec.
120+
};
121+
122+
} // namespace quic
123+
} // namespace node
124+
125+
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
126+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

0 commit comments

Comments
 (0)