diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/net_permissions.h | 3 | ||||
-rw-r--r-- | src/net_processing.cpp | 55 | ||||
-rw-r--r-- | src/net_processing.h | 3 | ||||
-rw-r--r-- | src/rpc/misc.cpp | 3 | ||||
-rw-r--r-- | src/rpc/net.cpp | 2 |
5 files changed, 64 insertions, 2 deletions
diff --git a/src/net_permissions.h b/src/net_permissions.h index bba0ea1695..3b841ab138 100644 --- a/src/net_permissions.h +++ b/src/net_permissions.h @@ -30,7 +30,8 @@ enum NetPermissionFlags { PF_NOBAN = (1U << 4) | PF_DOWNLOAD, // Can query the mempool PF_MEMPOOL = (1U << 5), - // Can request addrs without hitting a privacy-preserving cache + // Can request addrs without hitting a privacy-preserving cache, and send us + // unlimited amounts of addrs. PF_ADDR = (1U << 7), // True if the user did not specifically set fine grained permissions diff --git a/src/net_processing.cpp b/src/net_processing.cpp index f29be8d8a3..f926fd652f 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -146,6 +146,13 @@ static constexpr uint32_t MAX_GETCFILTERS_SIZE = 1000; static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000; /** the maximum percentage of addresses from our addrman to return in response to a getaddr message. */ static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23; +/** The maximum rate of address records we're willing to process on average. Can be bypassed using + * the NetPermissionFlags::Addr permission. */ +static constexpr double MAX_ADDR_RATE_PER_SECOND{0.1}; +/** The soft limit of the address processing token bucket (the regular MAX_ADDR_RATE_PER_SECOND + * based increments won't go above this, but the MAX_ADDR_TO_SEND increment following GETADDR + * is exempt from this limit. */ +static constexpr size_t MAX_ADDR_PROCESSING_TOKEN_BUCKET{MAX_ADDR_TO_SEND}; struct COrphanTx { // When modifying, adapt the copy of this definition in tests/DoS_tests. @@ -454,6 +461,16 @@ struct Peer { /** Work queue of items requested by this peer **/ std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex); + /** Number of addr messages that can be processed from this peer. Start at 1 to + * permit self-announcement. */ + double m_addr_token_bucket{1.0}; + /** When m_addr_token_bucket was last updated */ + std::chrono::microseconds m_addr_token_timestamp{GetTime<std::chrono::microseconds>()}; + /** Total number of addresses that were dropped due to rate limiting. */ + std::atomic<uint64_t> m_addr_rate_limited{0}; + /** Total number of addresses that were processed (excludes rate limited ones). */ + std::atomic<uint64_t> m_addr_processed{0}; + Peer(NodeId id) : m_id(id) {} }; @@ -906,6 +923,8 @@ bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) { PeerRef peer = GetPeerRef(nodeid); if (peer == nullptr) return false; stats.m_misbehavior_score = WITH_LOCK(peer->m_misbehavior_mutex, return peer->m_misbehavior_score); + stats.m_addr_processed = peer->m_addr_processed.load(); + stats.m_addr_rate_limited = peer->m_addr_rate_limited.load(); return true; } @@ -2438,6 +2457,9 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // Get recent addresses m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::GETADDR)); pfrom.fGetAddr = true; + // When requesting a getaddr, accept an additional MAX_ADDR_TO_SEND addresses in response + // (bypassing the MAX_ADDR_PROCESSING_TOKEN_BUCKET limit). + peer->m_addr_token_bucket += MAX_ADDR_TO_SEND; } if (!pfrom.IsInboundConn()) { @@ -2591,11 +2613,34 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat std::vector<CAddress> vAddrOk; int64_t nNow = GetAdjustedTime(); int64_t nSince = nNow - 10 * 60; + + // Update/increment addr rate limiting bucket. + const auto current_time = GetTime<std::chrono::microseconds>(); + if (peer->m_addr_token_bucket < MAX_ADDR_PROCESSING_TOKEN_BUCKET) { + // Don't increment bucket if it's already full + const auto time_diff = std::max(current_time - peer->m_addr_token_timestamp, std::chrono::microseconds{0}); + const double increment = std::chrono::duration<double>(time_diff).count() * MAX_ADDR_RATE_PER_SECOND; + peer->m_addr_token_bucket = std::min<double>(peer->m_addr_token_bucket + increment, MAX_ADDR_PROCESSING_TOKEN_BUCKET); + } + peer->m_addr_token_timestamp = current_time; + + const bool rate_limited = !pfrom.HasPermission(NetPermissionFlags::PF_ADDR); + uint64_t num_proc = 0; + uint64_t num_rate_limit = 0; + Shuffle(vAddr.begin(), vAddr.end(), FastRandomContext()); for (CAddress& addr : vAddr) { if (interruptMsgProc) return; + // Apply rate limiting. + if (rate_limited) { + if (peer->m_addr_token_bucket < 1.0) { + ++num_rate_limit; + continue; + } + peer->m_addr_token_bucket -= 1.0; + } // We only bother storing full nodes, though this may include // things which we would not make an outbound connection to, in // part because we may make feeler connections to them. @@ -2609,6 +2654,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // Do not process banned/discouraged addresses beyond remembering we received them continue; } + ++num_proc; bool fReachable = IsReachable(addr); if (addr.nTime > nSince && !pfrom.fGetAddr && vAddr.size() <= 10 && addr.IsRoutable()) { @@ -2619,6 +2665,15 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat if (fReachable) vAddrOk.push_back(addr); } + peer->m_addr_processed += num_proc; + peer->m_addr_rate_limited += num_rate_limit; + LogPrint(BCLog::NET, "Received addr: %u addresses (%u processed, %u rate-limited) from peer=%d%s\n", + vAddr.size(), + num_proc, + num_rate_limit, + pfrom.GetId(), + fLogIPs ? ", peeraddr=" + pfrom.addr.ToString() : ""); + m_connman.AddNewAddresses(vAddrOk, pfrom.addr, 2 * 60 * 60); if (vAddr.size() < 1000) pfrom.fGetAddr = false; diff --git a/src/net_processing.h b/src/net_processing.h index 87eee566de..1982551dd4 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -32,6 +32,7 @@ static const bool DEFAULT_PEERBLOCKFILTERS = false; /** Threshold for marking a node to be discouraged, e.g. disconnected and added to the discouragement filter. */ static const int DISCOURAGEMENT_THRESHOLD{100}; + class PeerManager final : public CValidationInterface, public NetEventsInterface { public: PeerManager(const CChainParams& chainparams, CConnman& connman, BanMan* banman, @@ -150,6 +151,8 @@ struct CNodeStateStats { int nSyncHeight = -1; int nCommonHeight = -1; std::vector<int> vHeightInFlight; + uint64_t m_addr_processed = 0; + uint64_t m_addr_rate_limited = 0; }; /** Get statistics from node state */ diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 00f7445cfa..cca4c6787f 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -387,7 +387,8 @@ static RPCHelpMan setmocktime() } SetMockTime(time); if (request.context.Has<NodeContext>()) { - for (const auto& chain_client : request.context.Get<NodeContext>().chain_clients) { + const auto& chain_clients = request.context.Get<NodeContext>().chain_clients; + for (const auto& chain_client : chain_clients) { chain_client->setMockTime(time); } } diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 298529e4e7..49d480c5f5 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -236,6 +236,8 @@ static RPCHelpMan getpeerinfo() heights.push_back(height); } obj.pushKV("inflight", heights); + obj.pushKV("addr_processed", statestats.m_addr_processed); + obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited); } if (IsDeprecatedRPCEnabled("whitelisted")) { // whitelisted is deprecated in v0.21 for removal in v0.22 |