From 83dfe6c65ef6c30ca01348ee5059c3d76e03d1d3 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 5 Aug 2021 09:15:44 -0700 Subject: Rate limit the processing of incoming addr messages While limitations on the influence of attackers on addrman already exist (affected buckets are restricted to a subset based on incoming IP / network group), there is no reason to permit them to let them feed us addresses at more than a multiple of the normal network rate. This commit introduces a "token bucket" rate limiter for the processing of addresses in incoming ADDR and ADDRV2 messages. Every connection gets an associated token bucket. Processing an address in an ADDR or ADDRV2 message from non-whitelisted peers consumes a token from the bucket. If the bucket is empty, the address is ignored (it is not forwarded or processed). The token counter increases at a rate of 0.1 tokens per second, and will accrue up to a maximum of 1000 tokens (the maximum we accept in a single ADDR or ADDRV2). When a GETADDR is sent to a peer, it immediately gets 1000 additional tokens, as we actively desire many addresses from such peers (this may temporarily cause the token count to exceed 1000). The rate limit of 0.1 addr/s was chosen based on observation of honest nodes on the network. Activity in general from most nodes is either 0, or up to a maximum around 0.025 addr/s for recent Bitcoin Core nodes. A few (self-identified, through subver) crawler nodes occasionally exceed 0.1 addr/s. Github-Pull: #22387 Rebased-From: 0d64b8f709b4655d8702f810d4876cd8d96ded82 --- src/net_permissions.h | 3 ++- src/net_processing.cpp | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'src') 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..019d00e7d6 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,12 @@ struct Peer { /** Work queue of items requested by this peer **/ std::deque 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()}; + Peer(NodeId id) : m_id(id) {} }; @@ -2438,6 +2451,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 +2607,28 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat std::vector vAddrOk; int64_t nNow = GetAdjustedTime(); int64_t nSince = nNow - 10 * 60; + + // Update/increment addr rate limiting bucket. + const auto current_time = GetTime(); + 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(time_diff).count() * MAX_ADDR_RATE_PER_SECOND; + peer->m_addr_token_bucket = std::min(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); for (CAddress& addr : vAddr) { if (interruptMsgProc) return; + // Apply rate limiting. + if (rate_limited) { + if (peer->m_addr_token_bucket < 1.0) break; + 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. -- cgit v1.2.3 From 8df3e5bd84f2b2b60730034cbd71fe8f3276d434 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 15 Jul 2021 12:59:23 -0700 Subject: Randomize the order of addr processing Github-Pull: #22387 Rebased-From: 5648138f5949013331c017c740646e2f4013bc24 --- src/net_processing.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 019d00e7d6..7ca8c10d85 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -2619,6 +2619,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat peer->m_addr_token_timestamp = current_time; const bool rate_limited = !pfrom.HasPermission(NetPermissionFlags::PF_ADDR); + Shuffle(vAddr.begin(), vAddr.end(), FastRandomContext()); for (CAddress& addr : vAddr) { if (interruptMsgProc) -- cgit v1.2.3 From a653aacbd66a47edd6d14ddc62fec2d4038456b8 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 7 Jul 2021 11:44:40 -0700 Subject: Add logging and addr rate limiting statistics Includes logging improvements by Vasil Dimov and John Newbery. Github-Pull: #22387 Rebased-From: f424d601e1b6870e20bc60f5ccba36d2e210377b --- src/net_processing.cpp | 23 ++++++++++++++++++++++- src/net_processing.h | 3 +++ src/rpc/net.cpp | 2 ++ 3 files changed, 27 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 7ca8c10d85..f926fd652f 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -466,6 +466,10 @@ struct Peer { double m_addr_token_bucket{1.0}; /** When m_addr_token_bucket was last updated */ std::chrono::microseconds m_addr_token_timestamp{GetTime()}; + /** Total number of addresses that were dropped due to rate limiting. */ + std::atomic m_addr_rate_limited{0}; + /** Total number of addresses that were processed (excludes rate limited ones). */ + std::atomic m_addr_processed{0}; Peer(NodeId id) : m_id(id) {} }; @@ -919,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; } @@ -2619,6 +2625,8 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat 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) { @@ -2627,7 +2635,10 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat // Apply rate limiting. if (rate_limited) { - if (peer->m_addr_token_bucket < 1.0) break; + 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 @@ -2643,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()) { @@ -2653,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 vHeightInFlight; + uint64_t m_addr_processed = 0; + uint64_t m_addr_rate_limited = 0; }; /** Get statistics from node state */ 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 -- cgit v1.2.3 From 2a5710805195ca54a02aff3540ceaefb9cb3b3e2 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 29 Jul 2021 16:57:26 -0700 Subject: Avoid Appveyor compilation failure --- src/rpc/misc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') 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()) { - for (const auto& chain_client : request.context.Get().chain_clients) { + const auto& chain_clients = request.context.Get().chain_clients; + for (const auto& chain_client : chain_clients) { chain_client->setMockTime(time); } } -- cgit v1.2.3