Skip to content

Commit f75e69a

Browse files
committed
quic: use SocketAddressLRU to track known SocketAddress info
Using the `SocketAddressLRU` utility allows us to put an upper bound on the amount of memory that will be used to track known SocketAddress information (such as current number of connections, validation status, reset and retry counts, etc. The LRU is bounded by both max size and time, with any entry older than 10 seconds dropped whenever another item is accessed or updated. PR-URL: #34618 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 6d1f0ae commit f75e69a

File tree

3 files changed

+45
-60
lines changed

3 files changed

+45
-60
lines changed

src/quic/node_quic_socket-inl.h

+13-29
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
55

66
#include "node_quic_socket.h"
7-
#include "node_sockaddr.h"
7+
#include "node_sockaddr-inl.h"
88
#include "node_quic_session.h"
99
#include "node_crypto.h"
1010
#include "debug_utils-inl.h"
@@ -112,33 +112,27 @@ void QuicSocket::ReportSendError(int error) {
112112
}
113113

114114
void QuicSocket::IncrementStatelessResetCounter(const SocketAddress& addr) {
115-
reset_counts_[addr]++;
115+
addrLRU_.Upsert(addr)->reset_count++;
116116
}
117117

118118
void QuicSocket::IncrementSocketAddressCounter(const SocketAddress& addr) {
119-
addr_counts_[addr]++;
119+
addrLRU_.Upsert(addr)->active_connections++;
120120
}
121121

122122
void QuicSocket::DecrementSocketAddressCounter(const SocketAddress& addr) {
123-
auto it = addr_counts_.find(addr);
124-
if (it == std::end(addr_counts_))
125-
return;
126-
it->second--;
127-
// Remove the address if the counter reaches zero again.
128-
if (it->second == 0) {
129-
addr_counts_.erase(addr);
130-
reset_counts_.erase(addr);
131-
}
123+
SocketAddressInfo* counts = addrLRU_.Peek(addr);
124+
if (counts != nullptr && counts->active_connections > 0)
125+
counts->active_connections--;
132126
}
133127

134128
size_t QuicSocket::GetCurrentSocketAddressCounter(const SocketAddress& addr) {
135-
auto it = addr_counts_.find(addr);
136-
return it == std::end(addr_counts_) ? 0 : it->second;
129+
SocketAddressInfo* counts = addrLRU_.Peek(addr);
130+
return counts != nullptr ? counts->active_connections : 0;
137131
}
138132

139133
size_t QuicSocket::GetCurrentStatelessResetCounter(const SocketAddress& addr) {
140-
auto it = reset_counts_.find(addr);
141-
return it == std::end(reset_counts_) ? 0 : it->second;
134+
SocketAddressInfo* counts = addrLRU_.Peek(addr);
135+
return counts != nullptr ? counts->reset_count : 0;
142136
}
143137

144138
void QuicSocket::ServerBusy(bool on) {
@@ -160,22 +154,12 @@ void QuicSocket::set_diagnostic_packet_loss(double rx, double tx) {
160154
}
161155

162156
void QuicSocket::set_validated_address(const SocketAddress& addr) {
163-
if (has_option_validate_address_lru()) {
164-
// Remove the oldest item if we've hit the LRU limit
165-
validated_addrs_.push_back(SocketAddress::Hash()(addr));
166-
if (validated_addrs_.size() > kMaxValidateAddressLru)
167-
validated_addrs_.pop_front();
168-
}
157+
addrLRU_.Upsert(addr)->validated = true;
169158
}
170159

171160
bool QuicSocket::is_validated_address(const SocketAddress& addr) const {
172-
if (has_option_validate_address_lru()) {
173-
auto res = std::find(std::begin(validated_addrs_),
174-
std::end(validated_addrs_),
175-
SocketAddress::Hash()(addr));
176-
return res != std::end(validated_addrs_);
177-
}
178-
return false;
161+
auto info = addrLRU_.Peek(addr);
162+
return info != nullptr ? info->validated : false;
179163
}
180164

181165
void QuicSocket::AddSession(

src/quic/node_quic_socket.cc

+14-3
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ QuicSocket::QuicSocket(
260260
retry_token_expiration_(retry_token_expiration),
261261
qlog_(qlog),
262262
server_alpn_(NGHTTP3_ALPN_H3),
263+
addrLRU_(DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE),
263264
quic_state_(quic_state) {
264265
MakeWeak();
265266
PushListener(&default_listener_);
@@ -308,10 +309,8 @@ void QuicSocket::MemoryInfo(MemoryTracker* tracker) const {
308309
tracker->TrackField("endpoints", endpoints_);
309310
tracker->TrackField("sessions", sessions_);
310311
tracker->TrackField("dcid_to_scid", dcid_to_scid_);
311-
tracker->TrackField("addr_counts", addr_counts_);
312-
tracker->TrackField("reset_counts", reset_counts_);
312+
tracker->TrackField("address_counts", addrLRU_);
313313
tracker->TrackField("token_map", token_map_);
314-
tracker->TrackField("validated_addrs", validated_addrs_);
315314
StatsBase::StatsMemoryInfo(tracker);
316315
tracker->TrackFieldWithSize(
317316
"current_ngtcp2_memory",
@@ -977,6 +976,18 @@ void QuicSocket::RemoveListener(QuicSocketListener* listener) {
977976
listener->previous_listener_ = nullptr;
978977
}
979978

979+
bool QuicSocket::SocketAddressInfoTraits::CheckExpired(
980+
const SocketAddress& address,
981+
const Type& type) {
982+
return (uv_hrtime() - type.timestamp) > 1e10; // 10 seconds.
983+
}
984+
985+
void QuicSocket::SocketAddressInfoTraits::Touch(
986+
const SocketAddress& address,
987+
Type* type) {
988+
type->timestamp = uv_hrtime();
989+
}
990+
980991
// JavaScript API
981992
namespace {
982993
void NewQuicEndpoint(const FunctionCallbackInfo<Value>& args) {

src/quic/node_quic_socket.h

+18-28
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ namespace quic {
3535
class QuicSocket;
3636
class QuicEndpoint;
3737

38+
constexpr size_t DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE = 1000;
39+
3840
#define QUICSOCKET_OPTIONS(V) \
3941
V(VALIDATE_ADDRESS, validate_address) \
4042
V(VALIDATE_ADDRESS_LRU, validate_address_lru)
@@ -544,36 +546,24 @@ class QuicSocket : public AsyncWrap,
544546
uint8_t token_secret_[kTokenSecretLen];
545547
uint8_t reset_token_secret_[NGTCP2_STATELESS_RESET_TOKENLEN];
546548

547-
// Counts the number of active connections per remote
548-
// address. A custom std::hash specialization for
549-
// sockaddr instances is used. Values are incremented
550-
// when a QuicSession is added to the socket, and
551-
// decremented when the QuicSession is removed. If the
552-
// value reaches the value of max_connections_per_host_,
553-
// attempts to create new connections will be ignored
554-
// until the value falls back below the limit.
555-
SocketAddress::Map<size_t> addr_counts_;
556-
557-
// Counts the number of stateless resets sent per
558-
// remote address.
559-
// TODO(@jasnell): this counter persists through the
560-
// lifetime of the QuicSocket, and therefore can become
561-
// a possible risk. Specifically, a malicious peer could
562-
// attempt the local peer to count an increasingly large
563-
// number of remote addresses. Need to mitigate the
564-
// potential risk.
565-
SocketAddress::Map<size_t> reset_counts_;
566-
567-
// Counts the number of retry attempts sent per
568-
// remote address.
549+
struct SocketAddressInfo {
550+
size_t active_connections;
551+
size_t reset_count;
552+
size_t retry_count;
553+
bool validated;
554+
uint64_t timestamp;
555+
};
569556

570-
StatelessResetToken::Map<QuicSession> token_map_;
557+
struct SocketAddressInfoTraits {
558+
using Type = SocketAddressInfo;
571559

572-
// The validated_addrs_ vector is used as an LRU cache for
573-
// validated addresses only when the VALIDATE_ADDRESS_LRU
574-
// option is set.
575-
typedef size_t SocketAddressHash;
576-
std::deque<SocketAddressHash> validated_addrs_;
560+
static bool CheckExpired(const SocketAddress& address, const Type& type);
561+
static void Touch(const SocketAddress& address, Type* type);
562+
};
563+
564+
SocketAddressLRU<SocketAddressInfoTraits> addrLRU_;
565+
566+
StatelessResetToken::Map<QuicSession> token_map_;
577567

578568
class SendWrap : public ReqWrap<uv_udp_send_t> {
579569
public:

0 commit comments

Comments
 (0)