diff options
Diffstat (limited to 'src/net.cpp')
-rw-r--r-- | src/net.cpp | 82 |
1 files changed, 57 insertions, 25 deletions
diff --git a/src/net.cpp b/src/net.cpp index f9ae67b75c..e629d6a0a5 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -840,6 +840,12 @@ static bool CompareLocalHostTimeConnected(const NodeEvictionCandidate &a, const return a.nTimeConnected > b.nTimeConnected; } +static bool CompareOnionTimeConnected(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) +{ + if (a.m_is_onion != b.m_is_onion) return b.m_is_onion; + return a.nTimeConnected > b.nTimeConnected; +} + static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) { return a.nKeyedNetGroup < b.nKeyedNetGroup; } @@ -870,13 +876,51 @@ static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const return a.nTimeConnected > b.nTimeConnected; } -//! Sort an array by the specified comparator, then erase the last K elements. -template<typename T, typename Comparator> -static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, size_t k) +//! Sort an array by the specified comparator, then erase the last K elements where predicate is true. +template <typename T, typename Comparator> +static void EraseLastKElements( + std::vector<T>& elements, Comparator comparator, size_t k, + std::function<bool(const NodeEvictionCandidate&)> predicate = [](const NodeEvictionCandidate& n) { return true; }) { std::sort(elements.begin(), elements.end(), comparator); size_t eraseSize = std::min(k, elements.size()); - elements.erase(elements.end() - eraseSize, elements.end()); + elements.erase(std::remove_if(elements.end() - eraseSize, elements.end(), predicate), elements.end()); +} + +void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& 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. + // To favorise the diversity of our peer connections, reserve up to (half + 2) of + // these protected spots for onion and localhost peers, if any, even if they're not + // longest uptime overall. This helps protect tor peers, which tend to be otherwise + // disadvantaged under our eviction criteria. + const size_t initial_size = vEvictionCandidates.size(); + size_t total_protect_size = initial_size / 2; + const size_t onion_protect_size = total_protect_size / 2; + + if (onion_protect_size) { + // Pick out up to 1/4 peers connected via our onion service, sorted by longest uptime. + EraseLastKElements(vEvictionCandidates, CompareOnionTimeConnected, onion_protect_size, + [](const NodeEvictionCandidate& n) { return n.m_is_onion; }); + } + + const size_t localhost_min_protect_size{2}; + if (onion_protect_size >= localhost_min_protect_size) { + // Allocate any remaining slots of the 1/4, or minimum 2 additional slots, + // to localhost peers, sorted by longest uptime, as manually configured + // hidden services not using `-bind=addr[:port]=onion` will not be detected + // as inbound onion connections. + const size_t remaining_tor_slots{onion_protect_size - (initial_size - vEvictionCandidates.size())}; + const size_t localhost_protect_size{std::max(remaining_tor_slots, localhost_min_protect_size)}; + EraseLastKElements(vEvictionCandidates, CompareLocalHostTimeConnected, localhost_protect_size, + [](const NodeEvictionCandidate& n) { return n.m_is_local; }); + } + + // 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<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates) @@ -893,30 +937,17 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, // An attacker cannot manipulate this metric without performing useful work. EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4); // Protect up to 8 non-tx-relay peers that have sent us novel blocks. - std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeBlockRelayOnlyTime); - size_t erase_size = std::min(size_t(8), vEvictionCandidates.size()); - vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return !n.fRelayTxes && n.fRelevantServices; }), vEvictionCandidates.end()); + const size_t erase_size = std::min(size_t(8), vEvictionCandidates.size()); + EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, erase_size, + [](const NodeEvictionCandidate& n) { return !n.fRelayTxes && n.fRelevantServices; }); // Protect 4 nodes that most recently sent us novel blocks. // 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; @@ -937,7 +968,7 @@ static void EraseLastKElements(std::vector<T> &elements, Comparator comparator, for (const NodeEvictionCandidate &node : vEvictionCandidates) { std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup]; group.push_back(node); - int64_t grouptime = group[0].nTimeConnected; + const int64_t grouptime = group[0].nTimeConnected; if (group.size() > nMostConnections || (group.size() == nMostConnections && grouptime > nMostConnectionsTime)) { nMostConnections = group.size(); @@ -985,7 +1016,8 @@ bool CConnman::AttemptToEvictConnection() node->nLastBlockTime, node->nLastTXTime, HasAllDesirableServiceFlags(node->nServices), peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup, - node->m_prefer_evict, node->addr.IsLocal()}; + node->m_prefer_evict, node->addr.IsLocal(), + node->m_inbound_onion}; vEvictionCandidates.push_back(candidate); } } |