aboutsummaryrefslogtreecommitdiff
path: root/src/net.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/net.cpp')
-rw-r--r--src/net.cpp157
1 files changed, 107 insertions, 50 deletions
diff --git a/src/net.cpp b/src/net.cpp
index 883e57bdf0..0fd95dfe3f 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -94,6 +94,7 @@ const std::string NET_MESSAGE_COMMAND_OTHER = "*other*";
static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("netgroup")[0:8]
static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[0:8]
+static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256("addrcache")[0:8]
//
// Global state variables
//
@@ -816,6 +817,7 @@ struct NodeEvictionCandidate
CAddress addr;
uint64_t nKeyedNetGroup;
bool prefer_evict;
+ bool m_is_local;
};
static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
@@ -828,6 +830,12 @@ 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 CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
return a.nKeyedNetGroup < b.nKeyedNetGroup;
}
@@ -849,6 +857,14 @@ static bool CompareNodeTXTime(const NodeEvictionCandidate &a, const NodeEviction
return a.nTimeConnected > b.nTimeConnected;
}
+// Pick out the potential block-relay only peers, and sort them by last block time.
+static bool CompareNodeBlockRelayOnlyTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
+{
+ if (a.fRelayTxes != b.fRelayTxes) return a.fRelayTxes;
+ if (a.nLastBlockTime != b.nLastBlockTime) return a.nLastBlockTime < b.nLastBlockTime;
+ if (a.fRelevantServices != b.fRelevantServices) return b.fRelevantServices;
+ return a.nTimeConnected > b.nTimeConnected;
+}
//! Sort an array by the specified comparator, then erase the last K elements.
template<typename T, typename Comparator>
@@ -891,7 +907,7 @@ bool CConnman::AttemptToEvictConnection()
node->nLastBlockTime, node->nLastTXTime,
HasAllDesirableServiceFlags(node->nServices),
peer_relay_txes, peer_filter_not_null, node->addr, node->nKeyedNetGroup,
- node->m_prefer_evict};
+ node->m_prefer_evict, node->addr.IsLocal()};
vEvictionCandidates.push_back(candidate);
}
}
@@ -904,15 +920,34 @@ bool CConnman::AttemptToEvictConnection()
// Protect the 8 nodes with the lowest minimum ping time.
// An attacker cannot manipulate this metric without physically moving nodes closer to the target.
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8);
- // Protect 4 nodes that most recently sent us transactions.
+ // Protect 4 nodes that most recently sent us novel transactions accepted into our mempool.
// An attacker cannot manipulate this metric without performing useful work.
EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
- // Protect 4 nodes that most recently sent us blocks.
+ // Protect up to 8 non-tx-relay peers that have sent us novel blocks.
+ std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNodeBlockRelayOnlyTime);
+ size_t erase_size = std::min(size_t(8), vEvictionCandidates.size());
+ vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return !n.fRelayTxes && n.fRelevantServices; }), vEvictionCandidates.end());
+
+ // Protect 4 nodes that most recently sent us novel blocks.
// An attacker cannot manipulate this metric without performing useful work.
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.
- EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected, vEvictionCandidates.size() / 2);
+ // Reserve half of these protected spots for localhost peers, even if
+ // they're not longest-uptime overall. This helps protect tor peers, which
+ // tend to be otherwise disadvantaged under our eviction criteria.
+ size_t initial_size = vEvictionCandidates.size();
+ size_t total_protect_size = initial_size / 2;
+
+ // Pick out up to 1/4 peers that are localhost, sorted by longest uptime.
+ std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareLocalHostTimeConnected);
+ size_t local_erase_size = total_protect_size / 2;
+ vEvictionCandidates.erase(std::remove_if(vEvictionCandidates.end() - local_erase_size, vEvictionCandidates.end(), [](NodeEvictionCandidate const &n) { return n.m_is_local; }), vEvictionCandidates.end());
+ // 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);
if (vEvictionCandidates.empty()) return false;
@@ -1843,41 +1878,45 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// but inbound and manual peers do not use our outbound slots. Inbound peers
// also have the added issue that they could be attacker controlled and used
// to prevent us from connecting to particular hosts if we used them here.
- switch(pnode->m_conn_type){
+ switch (pnode->m_conn_type) {
case ConnectionType::INBOUND:
case ConnectionType::MANUAL:
break;
- case ConnectionType::OUTBOUND:
+ case ConnectionType::OUTBOUND_FULL_RELAY:
case ConnectionType::BLOCK_RELAY:
case ConnectionType::ADDR_FETCH:
case ConnectionType::FEELER:
setConnected.insert(pnode->addr.GetGroup(addrman.m_asmap));
- }
+ } // no default case, so the compiler can warn about missing cases
}
}
- // Feeler Connections
- //
- // Design goals:
- // * Increase the number of connectable addresses in the tried table.
- //
- // Method:
- // * Choose a random address from new and attempt to connect to it if we can connect
- // successfully it is added to tried.
- // * Start attempting feeler connections only after node finishes making outbound
- // connections.
- // * Only make a feeler connection once every few minutes.
- //
+ ConnectionType conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
+ int64_t nTime = GetTimeMicros();
bool fFeeler = false;
- if (nOutboundFullRelay >= m_max_outbound_full_relay && nOutboundBlockRelay >= m_max_outbound_block_relay && !GetTryNewOutboundPeer()) {
- int64_t nTime = GetTimeMicros(); // The current time right now (in microseconds).
- if (nTime > nNextFeeler) {
- nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
- fFeeler = true;
- } else {
- continue;
- }
+ // Determine what type of connection to open. Opening
+ // OUTBOUND_FULL_RELAY connections gets the highest priority until we
+ // meet our full-relay capacity. Then we open BLOCK_RELAY connection
+ // until we hit our block-relay-only peer limit.
+ // GetTryNewOutboundPeer() gets set when a stale tip is detected, so we
+ // try opening an additional OUTBOUND_FULL_RELAY connection. If none of
+ // these conditions are met, check the nNextFeeler timer to decide if
+ // we should open a FEELER.
+
+ if (nOutboundFullRelay < m_max_outbound_full_relay) {
+ // OUTBOUND_FULL_RELAY
+ } else if (nOutboundBlockRelay < m_max_outbound_block_relay) {
+ conn_type = ConnectionType::BLOCK_RELAY;
+ } else if (GetTryNewOutboundPeer()) {
+ // OUTBOUND_FULL_RELAY
+ } else if (nTime > nNextFeeler) {
+ nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
+ conn_type = ConnectionType::FEELER;
+ fFeeler = true;
+ } else {
+ // skip to next iteration of while loop
+ continue;
}
addrman.ResolveCollisions();
@@ -1944,23 +1983,6 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString());
}
- ConnectionType conn_type;
- // Determine what type of connection to open. If fFeeler is not
- // set, open OUTBOUND connections until we meet our full-relay
- // capacity. Then open BLOCK_RELAY connections until we hit our
- // block-relay peer limit. Otherwise, default to opening an
- // OUTBOUND connection.
- if (fFeeler) {
- conn_type = ConnectionType::FEELER;
- } else if (nOutboundFullRelay < m_max_outbound_full_relay) {
- conn_type = ConnectionType::OUTBOUND;
- } else if (nOutboundBlockRelay < m_max_outbound_block_relay) {
- conn_type = ConnectionType::BLOCK_RELAY;
- } else {
- // GetTryNewOutboundPeer() is true
- conn_type = ConnectionType::OUTBOUND;
- }
-
OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, conn_type);
}
}
@@ -2539,15 +2561,47 @@ std::vector<CAddress> CConnman::GetAddresses(size_t max_addresses, size_t max_pc
return addresses;
}
-std::vector<CAddress> CConnman::GetAddresses(Network requestor_network, size_t max_addresses, size_t max_pct)
+std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addresses, size_t max_pct)
{
+ SOCKET socket;
+ WITH_LOCK(requestor.cs_hSocket, socket = requestor.hSocket);
+ auto local_socket_bytes = GetBindAddress(socket).GetAddrBytes();
+ uint64_t cache_id = GetDeterministicRandomizer(RANDOMIZER_ID_ADDRCACHE)
+ .Write(requestor.addr.GetNetwork())
+ .Write(local_socket_bytes.data(), local_socket_bytes.size())
+ .Finalize();
const auto current_time = GetTime<std::chrono::microseconds>();
- if (m_addr_response_caches.find(requestor_network) == m_addr_response_caches.end() ||
- m_addr_response_caches[requestor_network].m_update_addr_response < current_time) {
- m_addr_response_caches[requestor_network].m_addrs_response_cache = GetAddresses(max_addresses, max_pct);
- m_addr_response_caches[requestor_network].m_update_addr_response = current_time + std::chrono::hours(21) + GetRandMillis(std::chrono::hours(6));
+ auto r = m_addr_response_caches.emplace(cache_id, CachedAddrResponse{});
+ CachedAddrResponse& cache_entry = r.first->second;
+ if (cache_entry.m_cache_entry_expiration < current_time) { // If emplace() added new one it has expiration 0.
+ cache_entry.m_addrs_response_cache = GetAddresses(max_addresses, max_pct);
+ // Choosing a proper cache lifetime is a trade-off between the privacy leak minimization
+ // and the usefulness of ADDR responses to honest users.
+ //
+ // Longer cache lifetime makes it more difficult for an attacker to scrape
+ // enough AddrMan data to maliciously infer something useful.
+ // By the time an attacker scraped enough AddrMan records, most of
+ // the records should be old enough to not leak topology info by
+ // e.g. analyzing real-time changes in timestamps.
+ //
+ // It takes only several hundred requests to scrape everything from an AddrMan containing 100,000 nodes,
+ // so ~24 hours of cache lifetime indeed makes the data less inferable by the time
+ // most of it could be scraped (considering that timestamps are updated via
+ // ADDR self-announcements and when nodes communicate).
+ // We also should be robust to those attacks which may not require scraping *full* victim's AddrMan
+ // (because even several timestamps of the same handful of nodes may leak privacy).
+ //
+ // On the other hand, longer cache lifetime makes ADDR responses
+ // outdated and less useful for an honest requestor, e.g. if most nodes
+ // in the ADDR response are no longer active.
+ //
+ // However, the churn in the network is known to be rather low. Since we consider
+ // nodes to be "terrible" (see IsTerrible()) if the timestamps are older than 30 days,
+ // max. 24 hours of "penalty" due to cache shouldn't make any meaningful difference
+ // in terms of the freshness of the response.
+ cache_entry.m_cache_entry_expiration = current_time + std::chrono::hours(21) + GetRandMillis(std::chrono::hours(6));
}
- return m_addr_response_caches[requestor_network].m_addrs_response_cache;
+ return cache_entry.m_addrs_response_cache;
}
bool CConnman::AddNode(const std::string& strNode)
@@ -2784,6 +2838,9 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn
hashContinue = uint256();
if (conn_type_in != ConnectionType::BLOCK_RELAY) {
m_tx_relay = MakeUnique<TxRelay>();
+ }
+
+ if (RelayAddrsWithConn()) {
m_addr_known = MakeUnique<CRollingBloomFilter>(5000, 0.001);
}