From f126cbd6de6e1a8fee0e900ecfbc14a88e362541 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sun, 21 Feb 2021 21:42:17 +0100 Subject: [PATCH] Extract ProtectEvictionCandidatesByRatio from SelectNodeToEvict to allow deterministic unit testing of the ratio-based peer eviction protection logic, which protects peers having longer connection times and those connected via higher-latency networks. Add documentation. --- src/net.cpp | 39 +++++++++++++++++++++++---------------- src/net.h | 26 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 6a2469f950..b51d03de7b 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -879,6 +879,26 @@ static void EraseLastKElements(std::vector &elements, Comparator comparator, elements.erase(elements.end() - eraseSize, elements.end()); } +void ProtectEvictionCandidatesByRatio(std::vector& vEvictionCandidates) +{ + // Protect the half of the remaining nodes which have been connected the longest. + // This replicates the non-eviction implicit behavior, and precludes attacks that start later. + // Reserve half of these protected spots for localhost peers, even if + // they're not longest-uptime overall. This helps protect tor peers, which + // tend to be otherwise disadvantaged under our eviction criteria. + size_t initial_size = vEvictionCandidates.size(); + size_t total_protect_size = initial_size / 2; + + // Pick out up to 1/4 peers that are localhost, sorted by longest uptime. + std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareLocalHostTimeConnected); + size_t local_erase_size = total_protect_size / 2; + vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_local; }), vEvictionCandidates.end()); + // Calculate how many we removed, and update our total number of peers that + // we want to protect based on uptime accordingly. + total_protect_size -= initial_size - vEvictionCandidates.size(); + EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size); +} + [[nodiscard]] std::optional SelectNodeToEvict(std::vector&& vEvictionCandidates) { // Protect connections with certain characteristics @@ -901,22 +921,9 @@ static void EraseLastKElements(std::vector &elements, Comparator comparator, // An attacker cannot manipulate this metric without performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4); - // Protect the half of the remaining nodes which have been connected the longest. - // This replicates the non-eviction implicit behavior, and precludes attacks that start later. - // Reserve half of these protected spots for localhost peers, even if - // they're not longest-uptime overall. This helps protect tor peers, which - // tend to be otherwise disadvantaged under our eviction criteria. - size_t initial_size = vEvictionCandidates.size(); - size_t total_protect_size = initial_size / 2; - - // Pick out up to 1/4 peers that are localhost, sorted by longest uptime. - std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareLocalHostTimeConnected); - size_t local_erase_size = total_protect_size / 2; - vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_local; }), vEvictionCandidates.end()); - // Calculate how many we removed, and update our total number of peers that - // we want to protect based on uptime accordingly. - total_protect_size -= initial_size - vEvictionCandidates.size(); - EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, total_protect_size); + // Protect some of the remaining eviction candidates by ratios of desirable + // or disadvantaged characteristics. + ProtectEvictionCandidatesByRatio(vEvictionCandidates); if (vEvictionCandidates.empty()) return std::nullopt; diff --git a/src/net.h b/src/net.h index 48d37084a0..c15ca32816 100644 --- a/src/net.h +++ b/src/net.h @@ -1283,6 +1283,32 @@ struct NodeEvictionCandidate bool m_is_local; }; +/** + * Select an inbound peer to evict after filtering out (protecting) peers having + * distinct, difficult-to-forge characteristics. The protection logic picks out + * fixed numbers of desirable peers per various criteria, followed by ratios of + * desirable or disadvantaged peers. If any eviction candidates remain, the + * selection logic chooses a peer to evict. + */ [[nodiscard]] std::optional SelectNodeToEvict(std::vector&& vEvictionCandidates); +/** Protect desirable or disadvantaged inbound peers from eviction by ratio. + * + * This function protects half of the peers which have been connected the + * longest, to replicate the non-eviction implicit behavior and preclude attacks + * that start later. + * + * Half of these protected spots (1/4 of the total) are reserved for localhost + * peers, if any, sorted by longest uptime, even if they're not longest uptime + * overall. + * + * This helps protect onion peers, which tend to be otherwise disadvantaged + * under our eviction criteria for their higher min ping times relative to IPv4 + * and IPv6 peers, and favorise the diversity of peer connections. + * + * This function was extracted from SelectNodeToEvict() to be able to test the + * ratio-based protection logic deterministically. + */ +void ProtectEvictionCandidatesByRatio(std::vector& vEvictionCandidates); + #endif // BITCOIN_NET_H