aboutsummaryrefslogtreecommitdiff
path: root/src/net.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/net.cpp')
-rw-r--r--src/net.cpp162
1 files changed, 105 insertions, 57 deletions
diff --git a/src/net.cpp b/src/net.cpp
index 6f9f17ed4e..70ba875c4b 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -42,6 +42,7 @@
#endif
#include <algorithm>
+#include <array>
#include <cstdint>
#include <functional>
#include <optional>
@@ -401,7 +402,8 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
pszDest ? 0.0 : (double)(GetAdjustedTime() - addrConnect.nTime)/3600.0);
// Resolve
- const uint16_t default_port{Params().GetDefaultPort()};
+ const uint16_t default_port{pszDest != nullptr ? Params().GetDefaultPort(pszDest) :
+ Params().GetDefaultPort()};
if (pszDest) {
std::vector<CService> resolved;
if (Lookup(pszDest, resolved, default_port, fNameLookup && !HaveNameProxy(), 256) && !resolved.empty()) {
@@ -841,18 +843,6 @@ static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, cons
return a.nTimeConnected > b.nTimeConnected;
}
-static bool CompareLocalHostTimeConnected(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
-{
- if (a.m_is_local != b.m_is_local) return b.m_is_local;
- 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;
}
@@ -883,6 +873,26 @@ static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const
return a.nTimeConnected > b.nTimeConnected;
}
+/**
+ * Sort eviction candidates by network/localhost and connection uptime.
+ * Candidates near the beginning are more likely to be evicted, and those
+ * near the end are more likely to be protected, e.g. less likely to be evicted.
+ * - First, nodes that are not `is_local` and that do not belong to `network`,
+ * sorted by increasing uptime (from most recently connected to connected longer).
+ * - Then, nodes that are `is_local` or belong to `network`, sorted by increasing uptime.
+ */
+struct CompareNodeNetworkTime {
+ const bool m_is_local;
+ const Network m_network;
+ CompareNodeNetworkTime(bool is_local, Network network) : m_is_local(is_local), m_network(network) {}
+ bool operator()(const NodeEvictionCandidate& a, const NodeEvictionCandidate& b) const
+ {
+ if (m_is_local && a.m_is_local != b.m_is_local) return b.m_is_local;
+ if ((a.m_network == m_network) != (b.m_network == m_network)) return b.m_network == m_network;
+ return a.nTimeConnected > b.nTimeConnected;
+ };
+};
+
//! 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(
@@ -894,40 +904,77 @@ static void EraseLastKElements(
elements.erase(std::remove_if(elements.end() - eraseSize, elements.end(), predicate), elements.end());
}
-void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvictionCandidates)
+void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& eviction_candidates)
{
// 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
+ // To favorise the diversity of our peer connections, reserve up to half of these protected
+ // spots for Tor/onion, localhost and I2P peers, even if they're not longest uptime overall.
+ // This helps protect these higher-latency peers that 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; });
+ const size_t initial_size = eviction_candidates.size();
+ const size_t total_protect_size{initial_size / 2};
+
+ // Disadvantaged networks to protect: I2P, localhost, Tor/onion. In case of equal counts, earlier
+ // array members have first opportunity to recover unused slots from the previous iteration.
+ struct Net { bool is_local; Network id; size_t count; };
+ std::array<Net, 3> networks{
+ {{false, NET_I2P, 0}, {/* localhost */ true, NET_MAX, 0}, {false, NET_ONION, 0}}};
+
+ // Count and store the number of eviction candidates per network.
+ for (Net& n : networks) {
+ n.count = std::count_if(eviction_candidates.cbegin(), eviction_candidates.cend(),
+ [&n](const NodeEvictionCandidate& c) {
+ return n.is_local ? c.m_is_local : c.m_network == n.id;
+ });
+ }
+ // Sort `networks` by ascending candidate count, to give networks having fewer candidates
+ // the first opportunity to recover unused protected slots from the previous iteration.
+ std::stable_sort(networks.begin(), networks.end(), [](Net a, Net b) { return a.count < b.count; });
+
+ // Protect up to 25% of the eviction candidates by disadvantaged network.
+ const size_t max_protect_by_network{total_protect_size / 2};
+ size_t num_protected{0};
+
+ while (num_protected < max_protect_by_network) {
+ // Count the number of disadvantaged networks from which we have peers to protect.
+ auto num_networks = std::count_if(networks.begin(), networks.end(), [](const Net& n) { return n.count; });
+ if (num_networks == 0) {
+ break;
+ }
+ const size_t disadvantaged_to_protect{max_protect_by_network - num_protected};
+ const size_t protect_per_network{std::max(disadvantaged_to_protect / num_networks, static_cast<size_t>(1))};
+ // Early exit flag if there are no remaining candidates by disadvantaged network.
+ bool protected_at_least_one{false};
+
+ for (Net& n : networks) {
+ if (n.count == 0) continue;
+ const size_t before = eviction_candidates.size();
+ EraseLastKElements(eviction_candidates, CompareNodeNetworkTime(n.is_local, n.id),
+ protect_per_network, [&n](const NodeEvictionCandidate& c) {
+ return n.is_local ? c.m_is_local : c.m_network == n.id;
+ });
+ const size_t after = eviction_candidates.size();
+ if (before > after) {
+ protected_at_least_one = true;
+ const size_t delta{before - after};
+ num_protected += delta;
+ if (num_protected >= max_protect_by_network) {
+ break;
+ }
+ n.count -= delta;
+ }
+ }
+ if (!protected_at_least_one) {
+ break;
+ }
}
// 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);
+ assert(num_protected == initial_size - eviction_candidates.size());
+ const size_t remaining_to_protect{total_protect_size - num_protected};
+ EraseLastKElements(eviction_candidates, ReverseCompareNodeTimeConnected, remaining_to_protect);
}
[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates)
@@ -944,8 +991,7 @@ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& vEvict
// 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.
- const size_t erase_size = std::min(size_t(8), vEvictionCandidates.size());
- EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, erase_size,
+ EraseLastKElements(vEvictionCandidates, CompareNodeBlockRelayOnlyTime, 8,
[](const NodeEvictionCandidate& n) { return !n.fRelayTxes && n.fRelevantServices; });
// Protect 4 nodes that most recently sent us novel blocks.
@@ -1024,7 +1070,7 @@ bool CConnman::AttemptToEvictConnection()
HasAllDesirableServiceFlags(node->nServices),
peer_relay_txes, peer_filter_not_null, node->nKeyedNetGroup,
node->m_prefer_evict, node->addr.IsLocal(),
- node->m_inbound_onion};
+ node->ConnectedThroughNetwork()};
vEvictionCandidates.push_back(candidate);
}
}
@@ -2019,8 +2065,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// from advertising themselves as a service on another host and
// port, causing a DoS attack as nodes around the network attempt
// to connect to it fruitlessly.
- if (addr.GetPort() != Params().GetDefaultPort() && nTries < 50)
+ if (addr.GetPort() != Params().GetDefaultPort(addr.GetNetwork()) && nTries < 50) {
continue;
+ }
addrConnect = addr;
break;
@@ -2083,7 +2130,7 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
}
for (const std::string& strAddNode : lAddresses) {
- CService service(LookupNumeric(strAddNode, Params().GetDefaultPort()));
+ CService service(LookupNumeric(strAddNode, Params().GetDefaultPort(strAddNode)));
AddedNodeInfo addedNode{strAddNode, CService(), false, false};
if (service.IsValid()) {
// strAddNode is an IP:port
@@ -2173,6 +2220,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai
void CConnman::ThreadMessageHandler()
{
+ FastRandomContext rng;
while (!flagInterruptMsgProc)
{
std::vector<CNode*> vNodesCopy;
@@ -2186,6 +2234,11 @@ void CConnman::ThreadMessageHandler()
bool fMoreWork = false;
+ // Randomize the order in which we process messages from/to our peers.
+ // This prevents attacks in which an attacker exploits having multiple
+ // consecutive connections in the vNodes list.
+ Shuffle(vNodesCopy.begin(), vNodesCopy.end(), rng);
+
for (CNode* pnode : vNodesCopy)
{
if (pnode->fDisconnect)
@@ -2419,30 +2472,25 @@ bool CConnman::Bind(const CService &addr, unsigned int flags, NetPermissionFlags
return true;
}
-bool CConnman::InitBinds(
- const std::vector<CService>& binds,
- const std::vector<NetWhitebindPermissions>& whiteBinds,
- const std::vector<CService>& onion_binds)
+bool CConnman::InitBinds(const Options& options)
{
bool fBound = false;
- for (const auto& addrBind : binds) {
+ for (const auto& addrBind : options.vBinds) {
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR), NetPermissionFlags::None);
}
- for (const auto& addrBind : whiteBinds) {
+ for (const auto& addrBind : options.vWhiteBinds) {
fBound |= Bind(addrBind.m_service, (BF_EXPLICIT | BF_REPORT_ERROR), addrBind.m_flags);
}
- if (binds.empty() && whiteBinds.empty()) {
+ for (const auto& addr_bind : options.onion_binds) {
+ fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, NetPermissionFlags::None);
+ }
+ if (options.bind_on_any) {
struct in_addr inaddr_any;
inaddr_any.s_addr = htonl(INADDR_ANY);
struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT;
fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE, NetPermissionFlags::None);
fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE, NetPermissionFlags::None);
}
-
- for (const auto& addr_bind : onion_binds) {
- fBound |= Bind(addr_bind, BF_EXPLICIT | BF_DONT_ADVERTISE, NetPermissionFlags::None);
- }
-
return fBound;
}
@@ -2450,7 +2498,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
{
Init(connOptions);
- if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds, connOptions.onion_binds)) {
+ if (fListen && !InitBinds(connOptions)) {
if (clientInterface) {
clientInterface->ThreadSafeMessageBox(
_("Failed to listen on any port. Use -listen=0 if you want this."),