Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

net: rate limit the processing of incoming addr messages #1818

Merged
merged 6 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,8 @@ void CNode::copyStats(CNodeStats &stats)
LOCK(cs_feeFilter);
X(minFeeFilter);
}
X(nProcessedAddrs);
X(nRatelimitedAddrs);

// It is common for nodes with good ping times to suddenly become lagged,
// due to a new block arriving or other large transfer.
Expand Down Expand Up @@ -2648,6 +2650,8 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn
} else {
LogPrint(BCLog::NET, "Added connection peer=%d\n", id);
}

nAddrTokenTimestamp = GetTimeMicros();
}

CNode::~CNode()
Expand Down
12 changes: 12 additions & 0 deletions src/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,8 @@ class CNodeStats
CAddress addr;
// Bind address of our side of the connection
CAddress addrBind;
uint64_t nProcessedAddrs;
uint64_t nRatelimitedAddrs;
};


Expand Down Expand Up @@ -709,6 +711,16 @@ class CNode
std::vector<CAddress> vAddrToSend;
CRollingBloomFilter addrKnown;
bool fGetAddr{false};

/** Number of addresses that can be processed from this peer. */
double nAddrTokenBucket{1}; // initialize to 1 to allow self-announcement

/** When nAddrTokenBucket was last updated, in microseconds */
int64_t nAddrTokenTimestamp;

std::atomic<uint64_t> nProcessedAddrs{};
std::atomic<uint64_t> nRatelimitedAddrs{};

std::set<uint256> setKnown;
int64_t nNextAddrSend GUARDED_BY(cs_sendProcessing){0};
int64_t nNextLocalAddrSend GUARDED_BY(cs_sendProcessing){0};
Expand Down
48 changes: 48 additions & 0 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ static_assert(INBOUND_PEER_TX_DELAY >= MAX_GETDATA_RANDOM_DELAY,
/** Limit to avoid sending big packets. Not used in processing incoming GETDATA for compatibility */
static const unsigned int MAX_GETDATA_SZ = 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};


struct COrphanTx {
// When modifying, adapt the copy of this definition in tests/DoS_tests.
Expand Down Expand Up @@ -2138,6 +2147,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
{
connman->PushMessage(pfrom, CNetMsgMaker(nSendVersion).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).
pfrom->nAddrTokenBucket += MAX_ADDR_TO_SEND;
}
connman->MarkAddressGood(pfrom->addr);
}
Expand Down Expand Up @@ -2252,11 +2265,40 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
std::vector<CAddress> vAddrOk;
int64_t nNow = GetAdjustedTime();
int64_t nSince = nNow - 10 * 60;

// track rate limiting within this message
uint64_t nProcessedAddrs = 0;
uint64_t nRatelimitedAddrs = 0;

// Update/increment addr rate limiting bucket.
const uint64_t nCurrentTime = GetTimeMicros();
if (pfrom->nAddrTokenBucket < MAX_ADDR_PROCESSING_TOKEN_BUCKET) {
const uint64_t nTimeElapsed = std::max(nCurrentTime - pfrom->nAddrTokenTimestamp, uint64_t(0));
const double nIncrement = nTimeElapsed * MAX_ADDR_RATE_PER_SECOND / 1e6;
pfrom->nAddrTokenBucket = std::min<double>(pfrom->nAddrTokenBucket + nIncrement, MAX_ADDR_PROCESSING_TOKEN_BUCKET);
}
pfrom->nAddrTokenTimestamp = nCurrentTime;

// Randomize entries before processing, to prevent an attacker to
// determine which entries will make it through the rate limit
random_shuffle(vAddr.begin(), vAddr.end(), GetRandInt);

for (CAddress& addr : vAddr)
{
if (interruptMsgProc)
return true;

// Apply rate limiting
if (!pfrom->HasPermission(PF_NOBAN)) {
if (pfrom->nAddrTokenBucket < 1.0) {
nRatelimitedAddrs++;
continue;
}
pfrom->nAddrTokenBucket -= 1.0;
}

nProcessedAddrs++;

// 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.
Expand All @@ -2277,6 +2319,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
if (fReachable)
vAddrOk.push_back(addr);
}
pfrom->nProcessedAddrs += nProcessedAddrs;
pfrom->nRatelimitedAddrs += nRatelimitedAddrs;

LogPrint(BCLog::NET, "Received addr: %u addresses (%u processed, %u rate-limited) peer=%d\n",
vAddr.size(), nProcessedAddrs, nRatelimitedAddrs, pfrom->GetId());

connman->AddNewAddresses(vAddrOk, pfrom->addr, 2 * 60 * 60);
if (vAddr.size() < 1000)
pfrom->fGetAddr = false;
Expand Down
4 changes: 4 additions & 0 deletions src/rpc/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
" n, (numeric) The heights of blocks we're currently asking from this peer\n"
" ...\n"
" ],\n"
" \"addr_processed\": n, (numeric) The total number of addresses processed, excluding those dropped due to rate limiting\n"
" \"addr_rate_limited\": n, (numeric) The total number of addresses dropped due to rate limiting\n"
" \"whitelisted\": true|false, (boolean) Whether the peer is whitelisted\n"
" \"minfeefilter\": n, (numeric) The minimum fee rate for transactions this peer accepts\n"
" \"bytessent_per_msg\": {\n"
Expand Down Expand Up @@ -179,6 +181,8 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
}
obj.pushKV("inflight", heights);
}
obj.pushKV("addr_processed", stats.nProcessedAddrs);
obj.pushKV("addr_rate_limited", stats.nRatelimitedAddrs);
obj.pushKV("whitelisted", stats.m_legacyWhitelisted);
UniValue permissions(UniValue::VARR);
for (const auto& permission : NetPermissions::ToStrings(stats.m_permissionFlags)) {
Expand Down