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: