Skip to content

Commit

Permalink
quic: use SocketAddressLRU to track known SocketAddress info
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
jasnell committed Aug 10, 2020
1 parent 6d1f0ae commit f75e69a
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 60 deletions.
42 changes: 13 additions & 29 deletions src/quic/node_quic_socket-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "node_quic_socket.h"
#include "node_sockaddr.h"
#include "node_sockaddr-inl.h"
#include "node_quic_session.h"
#include "node_crypto.h"
#include "debug_utils-inl.h"
Expand Down Expand Up @@ -112,33 +112,27 @@ void QuicSocket::ReportSendError(int error) {
}

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

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

void QuicSocket::DecrementSocketAddressCounter(const SocketAddress& addr) {
auto it = addr_counts_.find(addr);
if (it == std::end(addr_counts_))
return;
it->second--;
// Remove the address if the counter reaches zero again.
if (it->second == 0) {
addr_counts_.erase(addr);
reset_counts_.erase(addr);
}
SocketAddressInfo* counts = addrLRU_.Peek(addr);
if (counts != nullptr && counts->active_connections > 0)
counts->active_connections--;
}

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

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

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

void QuicSocket::set_validated_address(const SocketAddress& addr) {
if (has_option_validate_address_lru()) {
// Remove the oldest item if we've hit the LRU limit
validated_addrs_.push_back(SocketAddress::Hash()(addr));
if (validated_addrs_.size() > kMaxValidateAddressLru)
validated_addrs_.pop_front();
}
addrLRU_.Upsert(addr)->validated = true;
}

bool QuicSocket::is_validated_address(const SocketAddress& addr) const {
if (has_option_validate_address_lru()) {
auto res = std::find(std::begin(validated_addrs_),
std::end(validated_addrs_),
SocketAddress::Hash()(addr));
return res != std::end(validated_addrs_);
}
return false;
auto info = addrLRU_.Peek(addr);
return info != nullptr ? info->validated : false;
}

void QuicSocket::AddSession(
Expand Down
17 changes: 14 additions & 3 deletions src/quic/node_quic_socket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ QuicSocket::QuicSocket(
retry_token_expiration_(retry_token_expiration),
qlog_(qlog),
server_alpn_(NGHTTP3_ALPN_H3),
addrLRU_(DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE),
quic_state_(quic_state) {
MakeWeak();
PushListener(&default_listener_);
Expand Down Expand Up @@ -308,10 +309,8 @@ void QuicSocket::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("endpoints", endpoints_);
tracker->TrackField("sessions", sessions_);
tracker->TrackField("dcid_to_scid", dcid_to_scid_);
tracker->TrackField("addr_counts", addr_counts_);
tracker->TrackField("reset_counts", reset_counts_);
tracker->TrackField("address_counts", addrLRU_);
tracker->TrackField("token_map", token_map_);
tracker->TrackField("validated_addrs", validated_addrs_);
StatsBase::StatsMemoryInfo(tracker);
tracker->TrackFieldWithSize(
"current_ngtcp2_memory",
Expand Down Expand Up @@ -977,6 +976,18 @@ void QuicSocket::RemoveListener(QuicSocketListener* listener) {
listener->previous_listener_ = nullptr;
}

bool QuicSocket::SocketAddressInfoTraits::CheckExpired(
const SocketAddress& address,
const Type& type) {
return (uv_hrtime() - type.timestamp) > 1e10; // 10 seconds.
}

void QuicSocket::SocketAddressInfoTraits::Touch(
const SocketAddress& address,
Type* type) {
type->timestamp = uv_hrtime();
}

// JavaScript API
namespace {
void NewQuicEndpoint(const FunctionCallbackInfo<Value>& args) {
Expand Down
46 changes: 18 additions & 28 deletions src/quic/node_quic_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ namespace quic {
class QuicSocket;
class QuicEndpoint;

constexpr size_t DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE = 1000;

#define QUICSOCKET_OPTIONS(V) \
V(VALIDATE_ADDRESS, validate_address) \
V(VALIDATE_ADDRESS_LRU, validate_address_lru)
Expand Down Expand Up @@ -544,36 +546,24 @@ class QuicSocket : public AsyncWrap,
uint8_t token_secret_[kTokenSecretLen];
uint8_t reset_token_secret_[NGTCP2_STATELESS_RESET_TOKENLEN];

// Counts the number of active connections per remote
// address. A custom std::hash specialization for
// sockaddr instances is used. Values are incremented
// when a QuicSession is added to the socket, and
// decremented when the QuicSession is removed. If the
// value reaches the value of max_connections_per_host_,
// attempts to create new connections will be ignored
// until the value falls back below the limit.
SocketAddress::Map<size_t> addr_counts_;

// Counts the number of stateless resets sent per
// remote address.
// TODO(@jasnell): this counter persists through the
// lifetime of the QuicSocket, and therefore can become
// a possible risk. Specifically, a malicious peer could
// attempt the local peer to count an increasingly large
// number of remote addresses. Need to mitigate the
// potential risk.
SocketAddress::Map<size_t> reset_counts_;

// Counts the number of retry attempts sent per
// remote address.
struct SocketAddressInfo {
size_t active_connections;
size_t reset_count;
size_t retry_count;
bool validated;
uint64_t timestamp;
};

StatelessResetToken::Map<QuicSession> token_map_;
struct SocketAddressInfoTraits {
using Type = SocketAddressInfo;

// The validated_addrs_ vector is used as an LRU cache for
// validated addresses only when the VALIDATE_ADDRESS_LRU
// option is set.
typedef size_t SocketAddressHash;
std::deque<SocketAddressHash> validated_addrs_;
static bool CheckExpired(const SocketAddress& address, const Type& type);
static void Touch(const SocketAddress& address, Type* type);
};

SocketAddressLRU<SocketAddressInfoTraits> addrLRU_;

StatelessResetToken::Map<QuicSession> token_map_;

class SendWrap : public ReqWrap<uv_udp_send_t> {
public:
Expand Down

0 comments on commit f75e69a

Please sign in to comment.