Merge #11524: [net] De-duplicate connection eviction logic
5ce7cb9
[net] De-duplicate connection eviction logic (Thomas Snider)
Pull request description:
While reviewing the safeguards against deliberate node isolation on the network by malicious actors, I found a good de-duplication candidate.
I think this form is much more legible (the type of `cutoffs` notwithstanding). ReverseCompareNodeTimeConnected is not included in the list since the cutoff size is a function of the remaining number of nodes in the candidate eviction set.
Tree-SHA512: ed17999fa9250dcf8448329219324477117e4ecd2d41dedd72ad253e44630eef50b3232c420f1862ebbfb9b8c94efbba1a235b519e39ff5946865c7d69a75280
This commit is contained in:
commit
5ef3b6967b
1 changed files with 20 additions and 29 deletions
49
src/net.cpp
49
src/net.cpp
|
@ -961,6 +961,16 @@ static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEviction
|
|||
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)
|
||||
{
|
||||
std::sort(elements.begin(), elements.end(), comparator);
|
||||
size_t eraseSize = std::min(k, elements.size());
|
||||
elements.erase(elements.end() - eraseSize, elements.end());
|
||||
}
|
||||
|
||||
/** Try to find a connection to evict when the node is full.
|
||||
* Extreme care must be taken to avoid opening the node to attacker
|
||||
* triggered network partitioning.
|
||||
|
@ -990,42 +1000,23 @@ bool CConnman::AttemptToEvictConnection()
|
|||
}
|
||||
}
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
// Protect connections with certain characteristics
|
||||
|
||||
// Deterministically select 4 peers to protect by netgroup.
|
||||
// An attacker cannot predict which netgroups will be protected
|
||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNetGroupKeyed);
|
||||
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4);
|
||||
// Protect the 8 nodes with the lowest minimum ping time.
|
||||
// An attacker cannot manipulate this metric without physically moving nodes closer to the target.
|
||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeMinPingTime);
|
||||
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(8, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8);
|
||||
// Protect 4 nodes that most recently sent us transactions.
|
||||
// An attacker cannot manipulate this metric without performing useful work.
|
||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeTXTime);
|
||||
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
|
||||
// Protect 4 nodes that most recently sent us blocks.
|
||||
// An attacker cannot manipulate this metric without performing useful work.
|
||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeBlockTime);
|
||||
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
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.
|
||||
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected);
|
||||
vEvictionCandidates.erase(vEvictionCandidates.end() - static_cast<int>(vEvictionCandidates.size() / 2), vEvictionCandidates.end());
|
||||
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, vEvictionCandidates.size() / 2);
|
||||
|
||||
if (vEvictionCandidates.empty()) return false;
|
||||
|
||||
|
@ -1036,12 +1027,12 @@ bool CConnman::AttemptToEvictConnection()
|
|||
int64_t nMostConnectionsTime = 0;
|
||||
std::map<uint64_t, std::vector<NodeEvictionCandidate> > mapNetGroupNodes;
|
||||
for (const NodeEvictionCandidate &node : vEvictionCandidates) {
|
||||
mapNetGroupNodes[node.nKeyedNetGroup].push_back(node);
|
||||
int64_t grouptime = mapNetGroupNodes[node.nKeyedNetGroup][0].nTimeConnected;
|
||||
size_t groupsize = mapNetGroupNodes[node.nKeyedNetGroup].size();
|
||||
std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup];
|
||||
group.push_back(node);
|
||||
int64_t grouptime = group[0].nTimeConnected;
|
||||
|
||||
if (groupsize > nMostConnections || (groupsize == nMostConnections && grouptime > nMostConnectionsTime)) {
|
||||
nMostConnections = groupsize;
|
||||
if (group.size() > nMostConnections || (group.size() == nMostConnections && grouptime > nMostConnectionsTime)) {
|
||||
nMostConnections = group.size();
|
||||
nMostConnectionsTime = grouptime;
|
||||
naMostConnections = node.nKeyedNetGroup;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue