16
16
#include < compat.h>
17
17
#include < consensus/consensus.h>
18
18
#include < crypto/sha256.h>
19
+ #include < node/eviction.h>
19
20
#include < fs.h>
20
21
#include < i2p.h>
21
22
#include < net_permissions.h>
@@ -576,26 +577,6 @@ void CConnman::AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNet
576
577
}
577
578
}
578
579
579
- std::string ConnectionTypeAsString (ConnectionType conn_type)
580
- {
581
- switch (conn_type) {
582
- case ConnectionType::INBOUND:
583
- return " inbound" ;
584
- case ConnectionType::MANUAL:
585
- return " manual" ;
586
- case ConnectionType::FEELER:
587
- return " feeler" ;
588
- case ConnectionType::OUTBOUND_FULL_RELAY:
589
- return " outbound-full-relay" ;
590
- case ConnectionType::BLOCK_RELAY:
591
- return " block-relay-only" ;
592
- case ConnectionType::ADDR_FETCH:
593
- return " addr-fetch" ;
594
- } // no default case, so the compiler can warn about missing cases
595
-
596
- assert (false );
597
- }
598
-
599
580
CService CNode::GetAddrLocal () const
600
581
{
601
582
AssertLockNotHeld (m_addr_local_mutex);
@@ -877,210 +858,6 @@ size_t CConnman::SocketSendData(CNode& node) const
877
858
return nSentSize;
878
859
}
879
860
880
- static bool ReverseCompareNodeMinPingTime (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
881
- {
882
- return a.m_min_ping_time > b.m_min_ping_time ;
883
- }
884
-
885
- static bool ReverseCompareNodeTimeConnected (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
886
- {
887
- return a.m_connected > b.m_connected ;
888
- }
889
-
890
- static bool CompareNetGroupKeyed (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
891
- return a.nKeyedNetGroup < b.nKeyedNetGroup ;
892
- }
893
-
894
- static bool CompareNodeBlockTime (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
895
- {
896
- // There is a fall-through here because it is common for a node to have many peers which have not yet relayed a block.
897
- if (a.m_last_block_time != b.m_last_block_time ) return a.m_last_block_time < b.m_last_block_time ;
898
- if (a.fRelevantServices != b.fRelevantServices ) return b.fRelevantServices ;
899
- return a.m_connected > b.m_connected ;
900
- }
901
-
902
- static bool CompareNodeTXTime (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
903
- {
904
- // 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.
905
- if (a.m_last_tx_time != b.m_last_tx_time ) return a.m_last_tx_time < b.m_last_tx_time ;
906
- if (a.m_relay_txs != b.m_relay_txs ) return b.m_relay_txs ;
907
- if (a.fBloomFilter != b.fBloomFilter ) return a.fBloomFilter ;
908
- return a.m_connected > b.m_connected ;
909
- }
910
-
911
- // Pick out the potential block-relay only peers, and sort them by last block time.
912
- static bool CompareNodeBlockRelayOnlyTime (const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
913
- {
914
- if (a.m_relay_txs != b.m_relay_txs ) return a.m_relay_txs ;
915
- if (a.m_last_block_time != b.m_last_block_time ) return a.m_last_block_time < b.m_last_block_time ;
916
- if (a.fRelevantServices != b.fRelevantServices ) return b.fRelevantServices ;
917
- return a.m_connected > b.m_connected ;
918
- }
919
-
920
- /* *
921
- * Sort eviction candidates by network/localhost and connection uptime.
922
- * Candidates near the beginning are more likely to be evicted, and those
923
- * near the end are more likely to be protected, e.g. less likely to be evicted.
924
- * - First, nodes that are not `is_local` and that do not belong to `network`,
925
- * sorted by increasing uptime (from most recently connected to connected longer).
926
- * - Then, nodes that are `is_local` or belong to `network`, sorted by increasing uptime.
927
- */
928
- struct CompareNodeNetworkTime {
929
- const bool m_is_local;
930
- const Network m_network;
931
- CompareNodeNetworkTime (bool is_local, Network network) : m_is_local(is_local), m_network(network) {}
932
- bool operator ()(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) const
933
- {
934
- if (m_is_local && a.m_is_local != b.m_is_local ) return b.m_is_local ;
935
- if ((a.m_network == m_network) != (b.m_network == m_network)) return b.m_network == m_network;
936
- return a.m_connected > b.m_connected ;
937
- };
938
- };
939
-
940
- // ! Sort an array by the specified comparator, then erase the last K elements where predicate is true.
941
- template <typename T, typename Comparator>
942
- static void EraseLastKElements (
943
- std::vector<T>& elements, Comparator comparator, size_t k,
944
- std::function<bool (const NodeEvictionCandidate&)> predicate = [](const NodeEvictionCandidate& n) { return true ; })
945
- {
946
- std::sort (elements.begin (), elements.end (), comparator);
947
- size_t eraseSize = std::min (k, elements.size ());
948
- elements.erase (std::remove_if (elements.end () - eraseSize, elements.end (), predicate), elements.end ());
949
- }
950
-
951
- void ProtectEvictionCandidatesByRatio (std::vector<NodeEvictionCandidate>& eviction_candidates)
952
- {
953
- // Protect the half of the remaining nodes which have been connected the longest.
954
- // This replicates the non-eviction implicit behavior, and precludes attacks that start later.
955
- // To favorise the diversity of our peer connections, reserve up to half of these protected
956
- // spots for Tor/onion, localhost, I2P, and CJDNS peers, even if they're not longest uptime
957
- // overall. This helps protect these higher-latency peers that tend to be otherwise
958
- // disadvantaged under our eviction criteria.
959
- const size_t initial_size = eviction_candidates.size ();
960
- const size_t total_protect_size{initial_size / 2 };
961
-
962
- // Disadvantaged networks to protect. In the case of equal counts, earlier array members
963
- // have the first opportunity to recover unused slots from the previous iteration.
964
- struct Net { bool is_local; Network id; size_t count; };
965
- std::array<Net, 4 > networks{
966
- {{false , NET_CJDNS, 0 }, {false , NET_I2P, 0 }, {/* localhost=*/ true , NET_MAX, 0 }, {false , NET_ONION, 0 }}};
967
-
968
- // Count and store the number of eviction candidates per network.
969
- for (Net& n : networks) {
970
- n.count = std::count_if (eviction_candidates.cbegin (), eviction_candidates.cend (),
971
- [&n](const NodeEvictionCandidate& c) {
972
- return n.is_local ? c.m_is_local : c.m_network == n.id ;
973
- });
974
- }
975
- // Sort `networks` by ascending candidate count, to give networks having fewer candidates
976
- // the first opportunity to recover unused protected slots from the previous iteration.
977
- std::stable_sort (networks.begin (), networks.end (), [](Net a, Net b) { return a.count < b.count ; });
978
-
979
- // Protect up to 25% of the eviction candidates by disadvantaged network.
980
- const size_t max_protect_by_network{total_protect_size / 2 };
981
- size_t num_protected{0 };
982
-
983
- while (num_protected < max_protect_by_network) {
984
- // Count the number of disadvantaged networks from which we have peers to protect.
985
- auto num_networks = std::count_if (networks.begin (), networks.end (), [](const Net& n) { return n.count ; });
986
- if (num_networks == 0 ) {
987
- break ;
988
- }
989
- const size_t disadvantaged_to_protect{max_protect_by_network - num_protected};
990
- const size_t protect_per_network{std::max (disadvantaged_to_protect / num_networks, static_cast <size_t >(1 ))};
991
- // Early exit flag if there are no remaining candidates by disadvantaged network.
992
- bool protected_at_least_one{false };
993
-
994
- for (Net& n : networks) {
995
- if (n.count == 0 ) continue ;
996
- const size_t before = eviction_candidates.size ();
997
- EraseLastKElements (eviction_candidates, CompareNodeNetworkTime (n.is_local , n.id ),
998
- protect_per_network, [&n](const NodeEvictionCandidate& c) {
999
- return n.is_local ? c.m_is_local : c.m_network == n.id ;
1000
- });
1001
- const size_t after = eviction_candidates.size ();
1002
- if (before > after) {
1003
- protected_at_least_one = true ;
1004
- const size_t delta{before - after};
1005
- num_protected += delta;
1006
- if (num_protected >= max_protect_by_network) {
1007
- break ;
1008
- }
1009
- n.count -= delta;
1010
- }
1011
- }
1012
- if (!protected_at_least_one) {
1013
- break ;
1014
- }
1015
- }
1016
-
1017
- // Calculate how many we removed, and update our total number of peers that
1018
- // we want to protect based on uptime accordingly.
1019
- assert (num_protected == initial_size - eviction_candidates.size ());
1020
- const size_t remaining_to_protect{total_protect_size - num_protected};
1021
- EraseLastKElements (eviction_candidates, ReverseCompareNodeTimeConnected, remaining_to_protect);
1022
- }
1023
-
1024
- [[nodiscard]] std::optional<NodeId> SelectNodeToEvict (std::vector<NodeEvictionCandidate>&& vEvictionCandidates)
1025
- {
1026
- // Protect connections with certain characteristics
1027
-
1028
- // Deterministically select 4 peers to protect by netgroup.
1029
- // An attacker cannot predict which netgroups will be protected
1030
- EraseLastKElements (vEvictionCandidates, CompareNetGroupKeyed, 4 );
1031
- // Protect the 8 nodes with the lowest minimum ping time.
1032
- // An attacker cannot manipulate this metric without physically moving nodes closer to the target.
1033
- EraseLastKElements (vEvictionCandidates, ReverseCompareNodeMinPingTime, 8 );
1034
- // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool.
1035
- // An attacker cannot manipulate this metric without performing useful work.
1036
- EraseLastKElements (vEvictionCandidates, CompareNodeTXTime, 4 );
1037
- // Protect up to 8 non-tx-relay peers that have sent us novel blocks.
1038
- EraseLastKElements (vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8 ,
1039
- [](const NodeEvictionCandidate& n) { return !n.m_relay_txs && n.fRelevantServices ; });
1040
-
1041
- // Protect 4 nodes that most recently sent us novel blocks.
1042
- // An attacker cannot manipulate this metric without performing useful work.
1043
- EraseLastKElements (vEvictionCandidates, CompareNodeBlockTime, 4 );
1044
-
1045
- // Protect some of the remaining eviction candidates by ratios of desirable
1046
- // or disadvantaged characteristics.
1047
- ProtectEvictionCandidatesByRatio (vEvictionCandidates);
1048
-
1049
- if (vEvictionCandidates.empty ()) return std::nullopt;
1050
-
1051
- // If any remaining peers are preferred for eviction consider only them.
1052
- // This happens after the other preferences since if a peer is really the best by other criteria (esp relaying blocks)
1053
- // then we probably don't want to evict it no matter what.
1054
- if (std::any_of (vEvictionCandidates.begin (),vEvictionCandidates.end (),[](NodeEvictionCandidate const &n){return n.prefer_evict ;})) {
1055
- vEvictionCandidates.erase (std::remove_if (vEvictionCandidates.begin (),vEvictionCandidates.end (),
1056
- [](NodeEvictionCandidate const &n){return !n.prefer_evict ;}),vEvictionCandidates.end ());
1057
- }
1058
-
1059
- // Identify the network group with the most connections and youngest member.
1060
- // (vEvictionCandidates is already sorted by reverse connect time)
1061
- uint64_t naMostConnections;
1062
- unsigned int nMostConnections = 0 ;
1063
- std::chrono::seconds nMostConnectionsTime{0 };
1064
- std::map<uint64_t , std::vector<NodeEvictionCandidate> > mapNetGroupNodes;
1065
- for (const NodeEvictionCandidate &node : vEvictionCandidates) {
1066
- std::vector<NodeEvictionCandidate> &group = mapNetGroupNodes[node.nKeyedNetGroup ];
1067
- group.push_back (node);
1068
- const auto grouptime{group[0 ].m_connected };
1069
-
1070
- if (group.size () > nMostConnections || (group.size () == nMostConnections && grouptime > nMostConnectionsTime)) {
1071
- nMostConnections = group.size ();
1072
- nMostConnectionsTime = grouptime;
1073
- naMostConnections = node.nKeyedNetGroup ;
1074
- }
1075
- }
1076
-
1077
- // Reduce to the network group with the most connections
1078
- vEvictionCandidates = std::move (mapNetGroupNodes[naMostConnections]);
1079
-
1080
- // Disconnect from the network group with the most connections
1081
- return vEvictionCandidates.front ().id ;
1082
- }
1083
-
1084
861
/* * Try to find a connection to evict when the node is full.
1085
862
* Extreme care must be taken to avoid opening the node to attacker
1086
863
* triggered network partitioning.
@@ -1096,10 +873,6 @@ bool CConnman::AttemptToEvictConnection()
1096
873
1097
874
LOCK (m_nodes_mutex);
1098
875
for (const CNode* node : m_nodes) {
1099
- if (node->HasPermission (NetPermissionFlags::NoBan))
1100
- continue ;
1101
- if (!node->IsInboundConn ())
1102
- continue ;
1103
876
if (node->fDisconnect )
1104
877
continue ;
1105
878
NodeEvictionCandidate candidate{
@@ -1115,6 +888,8 @@ bool CConnman::AttemptToEvictConnection()
1115
888
Desig (prefer_evict) node->m_prefer_evict ,
1116
889
Desig (m_is_local) node->addr .IsLocal (),
1117
890
Desig (m_network) node->ConnectedThroughNetwork (),
891
+ Desig (m_noban) node->HasPermission (NetPermissionFlags::NoBan),
892
+ Desig (m_conn_type) node->m_conn_type ,
1118
893
};
1119
894
vEvictionCandidates.push_back (candidate);
1120
895
}
0 commit comments