From f75e69a94bef9246c24b0494485421c8f4741177 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 3 Aug 2020 11:52:16 -0700 Subject: [PATCH] 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: https://github.com/nodejs/node/pull/34618 Reviewed-By: Anna Henningsen Reviewed-By: Rich Trott --- src/quic/node_quic_socket-inl.h | 42 ++++++++++-------------------- src/quic/node_quic_socket.cc | 17 +++++++++--- src/quic/node_quic_socket.h | 46 +++++++++++++-------------------- 3 files changed, 45 insertions(+), 60 deletions(-) diff --git a/src/quic/node_quic_socket-inl.h b/src/quic/node_quic_socket-inl.h index 4d334a19f5b665..8e7bc65d7848f0 100644 --- a/src/quic/node_quic_socket-inl.h +++ b/src/quic/node_quic_socket-inl.h @@ -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" @@ -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) { @@ -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( diff --git a/src/quic/node_quic_socket.cc b/src/quic/node_quic_socket.cc index 8005b5d8099e5f..a0c137998a3483 100644 --- a/src/quic/node_quic_socket.cc +++ b/src/quic/node_quic_socket.cc @@ -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_); @@ -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", @@ -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& args) { diff --git a/src/quic/node_quic_socket.h b/src/quic/node_quic_socket.h index 5b071c41b898df..9df0235050f15a 100644 --- a/src/quic/node_quic_socket.h +++ b/src/quic/node_quic_socket.h @@ -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) @@ -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 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 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 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 validated_addrs_; + static bool CheckExpired(const SocketAddress& address, const Type& type); + static void Touch(const SocketAddress& address, Type* type); + }; + + SocketAddressLRU addrLRU_; + + StatelessResetToken::Map token_map_; class SendWrap : public ReqWrap { public: