-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
quic: implement various utilities classes to be used by the quic impl #47263
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
Closed
Closed
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
9d95723
quic: add the CID implementation
jasnell 9c6c3b0
quic: add the PreferredAddress implementation
jasnell d092604
quic: add Path and PathStorage implementations
jasnell e762f99
quic: add Store implementation
jasnell a2b787f
quic: add QuicError implementation
jasnell 6e1b1b8
quic: fixup lining issues in multiple files
jasnell abdf755
quic: apply format-cpp to multiple files
jasnell 35556f8
quic: fixup silly compile errors
jasnell 70695c3
quic: fixup silly windows build errors
jasnell 7d8e349
quic: add shared openssl build guard
jasnell dde6f5d
fixup! quic: add the CID implementation
jasnell File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
||
// 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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.