Skip to content

Commit

Permalink
Rate limit the processing of incoming addr messages
Browse files Browse the repository at this point in the history
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.

Backport of:

- zcash/zcash@7c739e2
- bitcoin/bitcoin@0d64b8f
  • Loading branch information
DeckerSU committed Mar 15, 2023
1 parent d790bbb commit e09a0db
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7360,6 +7360,9 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
{
pfrom->PushMessage("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).
pfrom->m_addr_token_bucket += MAX_ADDR_TO_SEND;
}
addrman.Good(pfrom->addr);
} else {
Expand Down Expand Up @@ -7466,10 +7469,27 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
vector<CAddress> vAddrOk;
int64_t nNow = GetTime();
int64_t nSince = nNow - 10 * 60;

// Update/increment addr rate limiting bucket.
const int64_t current_time = GetTimeMicros();
if (pfrom->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 - pfrom->m_addr_token_timestamp, (int64_t) 0);
const double increment = (time_diff / 1000000) * MAX_ADDR_RATE_PER_SECOND;
pfrom->m_addr_token_bucket = std::min<double>(pfrom->m_addr_token_bucket + increment, MAX_ADDR_PROCESSING_TOKEN_BUCKET);
}
pfrom->m_addr_token_timestamp = current_time;

BOOST_FOREACH(CAddress& addr, vAddr)
{
boost::this_thread::interruption_point();

// Apply rate limiting if the address is not whitelisted
if (!pfrom->IsWhitelistedRange(addr)) {
if (pfrom->m_addr_token_bucket < 1.0) break;
pfrom->m_addr_token_bucket -= 1.0;
}

if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60)
addr.nTime = nNow - 5 * 24 * 60 * 60;
pfrom->AddAddressKnown(addr);
Expand Down
13 changes: 13 additions & 0 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ static const int TIMEOUT_INTERVAL = 20 * 60;
static const unsigned int MAX_INV_SZ = 50000;
/** The maximum number of new addresses to accumulate before announcing. */
static const unsigned int MAX_ADDR_TO_SEND = 1000;
/** 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};
/** Maximum length of incoming protocol messages (no message over 2 MiB is currently acceptable). */
static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = (_MAX_BLOCK_SIZE + 24); // 24 is msgheader size
/** Maximum length of strSubVer in `version` message */
Expand Down Expand Up @@ -370,6 +377,12 @@ class CNode
bool fGetAddr;
std::set<uint256> setKnown;

/** 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 */
int64_t m_addr_token_timestamp{GetTimeMicros()};

// inventory based relay
mruset<CInv> setInventoryKnown;
std::vector<CInv> vInventoryToSend;
Expand Down

0 comments on commit e09a0db

Please sign in to comment.