[net] De-duplicate connection eviction logic

This commit is contained in:
Thomas Snider 2017-10-19 00:53:01 -07:00
parent 5a9da37fb3
commit 5ce7cb9518

View file

@ -962,6 +962,16 @@ static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEviction
return a.nTimeConnected > b.nTimeConnected; 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. /** Try to find a connection to evict when the node is full.
* Extreme care must be taken to avoid opening the node to attacker * Extreme care must be taken to avoid opening the node to attacker
* triggered network partitioning. * triggered network partitioning.
@ -991,42 +1001,23 @@ bool CConnman::AttemptToEvictConnection()
} }
} }
if (vEvictionCandidates.empty()) return false;
// Protect connections with certain characteristics // Protect connections with certain characteristics
// Deterministically select 4 peers to protect by netgroup. // Deterministically select 4 peers to protect by netgroup.
// An attacker cannot predict which netgroups will be protected // An attacker cannot predict which netgroups will be protected
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNetGroupKeyed); EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4);
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
if (vEvictionCandidates.empty()) return false;
// Protect the 8 nodes with the lowest minimum ping time. // Protect the 8 nodes with the lowest minimum ping time.
// An attacker cannot manipulate this metric without physically moving nodes closer to the target. // An attacker cannot manipulate this metric without physically moving nodes closer to the target.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeMinPingTime); EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8);
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(8, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
if (vEvictionCandidates.empty()) return false;
// Protect 4 nodes that most recently sent us transactions. // Protect 4 nodes that most recently sent us transactions.
// An attacker cannot manipulate this metric without performing useful work. // An attacker cannot manipulate this metric without performing useful work.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeTXTime); EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
if (vEvictionCandidates.empty()) return false;
// Protect 4 nodes that most recently sent us blocks. // Protect 4 nodes that most recently sent us blocks.
// An attacker cannot manipulate this metric without performing useful work. // An attacker cannot manipulate this metric without performing useful work.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeBlockTime); EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4);
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
if (vEvictionCandidates.empty()) return false;
// Protect the half of the remaining nodes which have been connected the longest. // 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. // This replicates the non-eviction implicit behavior, and precludes attacks that start later.
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), ReverseCompareNodeTimeConnected); EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, vEvictionCandidates.size() / 2);
vEvictionCandidates.erase(vEvictionCandidates.end() - static_cast<int>(vEvictionCandidates.size() / 2), vEvictionCandidates.end());
if (vEvictionCandidates.empty()) return false; if (vEvictionCandidates.empty()) return false;
@ -1037,12 +1028,12 @@ bool CConnman::AttemptToEvictConnection()
int64_t nMostConnectionsTime = 0; int64_t nMostConnectionsTime = 0;
std::map<uint64_t, std::vector<NodeEvictionCandidate> > mapNetGroupNodes; std::map<uint64_t, std::vector<NodeEvictionCandidate> > mapNetGroupNodes;
for (const NodeEvictionCandidate &node : vEvictionCandidates) { for (const NodeEvictionCandidate &node : vEvictionCandidates) {
mapNetGroupNodes[node.nKeyedNetGroup].push_back(node); std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup];
int64_t grouptime = mapNetGroupNodes[node.nKeyedNetGroup][0].nTimeConnected; group.push_back(node);
size_t groupsize = mapNetGroupNodes[node.nKeyedNetGroup].size(); int64_t grouptime = group[0].nTimeConnected;
if (groupsize > nMostConnections || (groupsize == nMostConnections && grouptime > nMostConnectionsTime)) { if (group.size() > nMostConnections || (group.size() == nMostConnections && grouptime > nMostConnectionsTime)) {
nMostConnections = groupsize; nMostConnections = group.size();
nMostConnectionsTime = grouptime; nMostConnectionsTime = grouptime;
naMostConnections = node.nKeyedNetGroup; naMostConnections = node.nKeyedNetGroup;
} }