1717#include < compat.h>
1818#include < consensus/consensus.h>
1919#include < crypto/sha256.h>
20+ #include < node/eviction.h>
2021#include < fs.h>
2122#include < i2p.h>
2223#include < memusage.h>
@@ -680,26 +681,6 @@ bool CNode::IsBlockRelayOnly() const {
680681 return (ignores_incoming_txs && !HasPermission (NetPermissionFlags::Relay)) || IsBlockOnlyConn ();
681682}
682683
683- std::string ConnectionTypeAsString (ConnectionType conn_type)
684- {
685- switch (conn_type) {
686- case ConnectionType::INBOUND:
687- return " inbound" ;
688- case ConnectionType::MANUAL:
689- return " manual" ;
690- case ConnectionType::FEELER:
691- return " feeler" ;
692- case ConnectionType::OUTBOUND_FULL_RELAY:
693- return " outbound-full-relay" ;
694- case ConnectionType::BLOCK_RELAY:
695- return " block-relay-only" ;
696- case ConnectionType::ADDR_FETCH:
697- return " addr-fetch" ;
698- } // no default case, so the compiler can warn about missing cases
699-
700- assert (false );
701- }
702-
703684CService CNode::GetAddrLocal () const
704685{
705686 AssertLockNotHeld (m_addr_local_mutex);
@@ -1085,210 +1066,6 @@ std::pair<size_t, bool> CConnman::SocketSendData(CNode& node) const
10851066 return {nSentSize, data_left};
10861067}
10871068
1088- static bool ReverseCompareNodeMinPingTime (const NodeEvictionCandidate& a, const NodeEvictionCandidate& b)
1089- {
1090- return a.m_min_ping_time > b.m_min_ping_time ;
1091- }
1092-
1093- static bool ReverseCompareNodeTimeConnected (const NodeEvictionCandidate& a, const NodeEvictionCandidate& b)
1094- {
1095- return a.m_connected > b.m_connected ;
1096- }
1097-
1098- static bool CompareNetGroupKeyed (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
1099- return a.nKeyedNetGroup < b.nKeyedNetGroup ;
1100- }
1101-
1102- static bool CompareNodeBlockTime (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
1103- {
1104- // There is a fall-through here because it is common for a node to have many peers which have not yet relayed a block.
1105- if (a.m_last_block_time != b.m_last_block_time ) return a.m_last_block_time < b.m_last_block_time ;
1106- if (a.fRelevantServices != b.fRelevantServices ) return b.fRelevantServices ;
1107- return a.m_connected > b.m_connected ;
1108- }
1109-
1110- static bool CompareNodeTXTime (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
1111- {
1112- // There is a fall-through here because it is common for a node to have more than a few peers that have not yet relayed txn.
1113- if (a.m_last_tx_time != b.m_last_tx_time ) return a.m_last_tx_time < b.m_last_tx_time ;
1114- if (a.m_relay_txs != b.m_relay_txs ) return b.m_relay_txs ;
1115- if (a.fBloomFilter != b.fBloomFilter ) return a.fBloomFilter ;
1116- return a.m_connected > b.m_connected ;
1117- }
1118-
1119- // Pick out the potential block-relay only peers, and sort them by last block time.
1120- static bool CompareNodeBlockRelayOnlyTime (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
1121- {
1122- if (a.m_relay_txs != b.m_relay_txs ) return a.m_relay_txs ;
1123- if (a.m_last_block_time != b.m_last_block_time ) return a.m_last_block_time < b.m_last_block_time ;
1124- if (a.fRelevantServices != b.fRelevantServices ) return b.fRelevantServices ;
1125- return a.m_connected > b.m_connected ;
1126- }
1127-
1128- /* *
1129- * Sort eviction candidates by network/localhost and connection uptime.
1130- * Candidates near the beginning are more likely to be evicted, and those
1131- * near the end are more likely to be protected, e.g. less likely to be evicted.
1132- * - First, nodes that are not `is_local` and that do not belong to `network`,
1133- * sorted by increasing uptime (from most recently connected to connected longer).
1134- * - Then, nodes that are `is_local` or belong to `network`, sorted by increasing uptime.
1135- */
1136- struct CompareNodeNetworkTime {
1137- const bool m_is_local;
1138- const Network m_network;
1139- CompareNodeNetworkTime (bool is_local, Network network) : m_is_local(is_local), m_network(network) {}
1140- bool operator ()(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) const
1141- {
1142- if (m_is_local && a.m_is_local != b.m_is_local ) return b.m_is_local ;
1143- if ((a.m_network == m_network) != (b.m_network == m_network)) return b.m_network == m_network;
1144- return a.m_connected > b.m_connected ;
1145- };
1146- };
1147-
1148- // ! Sort an array by the specified comparator, then erase the last K elements where predicate is true.
1149- template <typename T, typename Comparator>
1150- static void EraseLastKElements (
1151- std::vector<T>& elements, Comparator comparator, size_t k,
1152- std::function<bool (const NodeEvictionCandidate&)> predicate = [](const NodeEvictionCandidate& n) { return true ; })
1153- {
1154- std::sort (elements.begin (), elements.end (), comparator);
1155- size_t eraseSize = std::min (k, elements.size ());
1156- elements.erase (std::remove_if (elements.end () - eraseSize, elements.end (), predicate), elements.end ());
1157- }
1158-
1159- void ProtectEvictionCandidatesByRatio (std::vector<NodeEvictionCandidate>& eviction_candidates)
1160- {
1161- // Protect the half of the remaining nodes which have been connected the longest.
1162- // This replicates the non-eviction implicit behavior, and precludes attacks that start later.
1163- // To favorise the diversity of our peer connections, reserve up to half of these protected
1164- // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime
1165- // overall. This helps protect these higher-latency peers that tend to be otherwise
1166- // disadvantaged under our eviction criteria.
1167- const size_t initial_size = eviction_candidates.size ();
1168- const size_t total_protect_size{initial_size / 2 };
1169-
1170- // Disadvantaged networks to protect. In the case of equal counts, earlier array members
1171- // have the first opportunity to recover unused slots from the previous iteration.
1172- struct Net { bool is_local; Network id; size_t count; };
1173- std::array<Net, 4 > networks{
1174- {{false , NET_CJDNS, 0 }, {false , NET_I2P, 0 }, {/* localhost=*/ true , NET_MAX, 0 }, {false , NET_ONION, 0 }}};
1175-
1176- // Count and store the number of eviction candidates per network.
1177- for (Net& n : networks) {
1178- n.count = std::count_if (eviction_candidates.cbegin (), eviction_candidates.cend (),
1179- [&n](const NodeEvictionCandidate& c) {
1180- return n.is_local ? c.m_is_local : c.m_network == n.id ;
1181- });
1182- }
1183- // Sort `networks` by ascending candidate count, to give networks having fewer candidates
1184- // the first opportunity to recover unused protected slots from the previous iteration.
1185- std::stable_sort (networks.begin (), networks.end (), [](Net a, Net b) { return a.count < b.count ; });
1186-
1187- // Protect up to 25% of the eviction candidates by disadvantaged network.
1188- const size_t max_protect_by_network{total_protect_size / 2 };
1189- size_t num_protected{0 };
1190-
1191- while (num_protected < max_protect_by_network) {
1192- // Count the number of disadvantaged networks from which we have peers to protect.
1193- auto num_networks = std::count_if (networks.begin (), networks.end (), [](const Net& n) { return n.count ; });
1194- if (num_networks == 0 ) {
1195- break ;
1196- }
1197- const size_t disadvantaged_to_protect{max_protect_by_network - num_protected};
1198- const size_t protect_per_network{std::max (disadvantaged_to_protect / num_networks, static_cast <size_t >(1 ))};
1199- // Early exit flag if there are no remaining candidates by disadvantaged network.
1200- bool protected_at_least_one{false };
1201-
1202- for (Net& n : networks) {
1203- if (n.count == 0 ) continue ;
1204- const size_t before = eviction_candidates.size ();
1205- EraseLastKElements (eviction_candidates, CompareNodeNetworkTime (n.is_local , n.id ),
1206- protect_per_network, [&n](const NodeEvictionCandidate& c) {
1207- return n.is_local ? c.m_is_local : c.m_network == n.id ;
1208- });
1209- const size_t after = eviction_candidates.size ();
1210- if (before > after) {
1211- protected_at_least_one = true ;
1212- const size_t delta{before - after};
1213- num_protected += delta;
1214- if (num_protected >= max_protect_by_network) {
1215- break ;
1216- }
1217- n.count -= delta;
1218- }
1219- }
1220- if (!protected_at_least_one) {
1221- break ;
1222- }
1223- }
1224-
1225- // Calculate how many we removed, and update our total number of peers that
1226- // we want to protect based on uptime accordingly.
1227- assert (num_protected == initial_size - eviction_candidates.size ());
1228- const size_t remaining_to_protect{total_protect_size - num_protected};
1229- EraseLastKElements (eviction_candidates, ReverseCompareNodeTimeConnected, remaining_to_protect);
1230- }
1231-
1232- [[nodiscard]] std::optional<NodeId> SelectNodeToEvict (std::vector<NodeEvictionCandidate>&& vEvictionCandidates)
1233- {
1234- // Protect connections with certain characteristics
1235-
1236- // Deterministically select 4 peers to protect by netgroup.
1237- // An attacker cannot predict which netgroups will be protected
1238- EraseLastKElements (vEvictionCandidates, CompareNetGroupKeyed, 4 );
1239- // Protect the 8 nodes with the lowest minimum ping time.
1240- // An attacker cannot manipulate this metric without physically moving nodes closer to the target.
1241- EraseLastKElements (vEvictionCandidates, ReverseCompareNodeMinPingTime, 8 );
1242- // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool.
1243- // An attacker cannot manipulate this metric without performing useful work.
1244- EraseLastKElements (vEvictionCandidates, CompareNodeTXTime, 4 );
1245- // Protect up to 8 non-tx-relay peers that have sent us novel blocks.
1246- EraseLastKElements (vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8 ,
1247- [](const NodeEvictionCandidate& n) { return !n.m_relay_txs && n.fRelevantServices ; });
1248-
1249- // Protect 4 nodes that most recently sent us novel blocks.
1250- // An attacker cannot manipulate this metric without performing useful work.
1251- EraseLastKElements (vEvictionCandidates, CompareNodeBlockTime, 4 );
1252-
1253- // Protect some of the remaining eviction candidates by ratios of desirable
1254- // or disadvantaged characteristics.
1255- ProtectEvictionCandidatesByRatio (vEvictionCandidates);
1256-
1257- if (vEvictionCandidates.empty ()) return std::nullopt ;
1258-
1259- // If any remaining peers are preferred for eviction consider only them.
1260- // This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks)
1261- // then we probably don't want to evict it no matter what.
1262- if (std::any_of (vEvictionCandidates.begin (),vEvictionCandidates.end (),[](NodeEvictionCandidate const &n){return n.prefer_evict ;})) {
1263- vEvictionCandidates.erase (std::remove_if (vEvictionCandidates.begin (),vEvictionCandidates.end (),
1264- [](NodeEvictionCandidate const &n){return !n.prefer_evict ;}),vEvictionCandidates.end ());
1265- }
1266-
1267- // Identify the network group with the most connections and youngest member.
1268- // (vEvictionCandidates is already sorted by reverse connect time)
1269- uint64_t naMostConnections;
1270- unsigned int nMostConnections = 0 ;
1271- std::chrono::seconds nMostConnectionsTime{0 };
1272- std::map<uint64_t , std::vector<NodeEvictionCandidate> > mapNetGroupNodes;
1273- for (const NodeEvictionCandidate &node : vEvictionCandidates) {
1274- std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup ];
1275- group.push_back (node);
1276- const auto grouptime{group[0 ].m_connected };
1277-
1278- if (group.size () > nMostConnections || (group.size () == nMostConnections && grouptime > nMostConnectionsTime)) {
1279- nMostConnections = group.size ();
1280- nMostConnectionsTime = grouptime;
1281- naMostConnections = node.nKeyedNetGroup ;
1282- }
1283- }
1284-
1285- // Reduce to the network group with the most connections
1286- vEvictionCandidates = std::move (mapNetGroupNodes[naMostConnections]);
1287-
1288- // Disconnect from the network group with the most connections
1289- return vEvictionCandidates.front ().id ;
1290- }
1291-
12921069/* * Try to find a connection to evict when the node is full.
12931070 * Extreme care must be taken to avoid opening the node to attacker
12941071 * triggered network partitioning.
@@ -1304,10 +1081,6 @@ bool CConnman::AttemptToEvictConnection()
13041081 LOCK (m_nodes_mutex);
13051082
13061083 for (const CNode* node : m_nodes) {
1307- if (node->HasPermission (NetPermissionFlags::NoBan))
1308- continue ;
1309- if (!node->IsInboundConn ())
1310- continue ;
13111084 if (node->fDisconnect )
13121085 continue ;
13131086
@@ -1343,6 +1116,8 @@ bool CConnman::AttemptToEvictConnection()
13431116 Desig (prefer_evict) node->m_prefer_evict ,
13441117 Desig (m_is_local) node->addr .IsLocal (),
13451118 Desig (m_network) node->ConnectedThroughNetwork (),
1119+ Desig (m_noban) node->HasPermission (NetPermissionFlags::NoBan),
1120+ Desig (m_conn_type) node->m_conn_type ,
13461121 };
13471122 vEvictionCandidates.push_back (candidate);
13481123 }
0 commit comments