Skip to content

Commit 0d64b8f

Browse files
committed
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.
1 parent a88fa1a commit 0d64b8f

File tree

5 files changed

+38
-2
lines changed

5 files changed

+38
-2
lines changed

src/net_permissions.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ enum class NetPermissionFlags : uint32_t {
3131
NoBan = (1U << 4) | Download,
3232
// Can query the mempool
3333
Mempool = (1U << 5),
34-
// Can request addrs without hitting a privacy-preserving cache
34+
// Can request addrs without hitting a privacy-preserving cache, and send us
35+
// unlimited amounts of addrs.
3536
Addr = (1U << 7),
3637

3738
// True if the user did not specifically set fine grained permissions

src/net_processing.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,13 @@ static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000;
155155
static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23;
156156
/** The maximum number of address records permitted in an ADDR message. */
157157
static constexpr size_t MAX_ADDR_TO_SEND{1000};
158+
/** The maximum rate of address records we're willing to process on average. Can be bypassed using
159+
* the NetPermissionFlags::Addr permission. */
160+
static constexpr double MAX_ADDR_RATE_PER_SECOND{0.1};
161+
/** The soft limit of the address processing token bucket (the regular MAX_ADDR_RATE_PER_SECOND
162+
* based increments won't go above this, but the MAX_ADDR_TO_SEND increment following GETADDR
163+
* is exempt from this limit. */
164+
static constexpr size_t MAX_ADDR_PROCESSING_TOKEN_BUCKET{MAX_ADDR_TO_SEND};
158165

159166
// Internal stuff
160167
namespace {
@@ -233,6 +240,11 @@ struct Peer {
233240
std::atomic_bool m_wants_addrv2{false};
234241
/** Whether this peer has already sent us a getaddr message. */
235242
bool m_getaddr_recvd{false};
243+
/** Number of addr messages that can be processed from this peer. Start at 1 to
244+
* permit self-announcement. */
245+
double m_addr_token_bucket{1.0};
246+
/** When m_addr_token_bucket was last updated */
247+
std::chrono::microseconds m_addr_token_timestamp{GetTime<std::chrono::microseconds>()};
236248

237249
/** Set of txids to reconsider once their parent transactions have been accepted **/
238250
std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans);
@@ -2583,6 +2595,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
25832595
// Get recent addresses
25842596
m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::GETADDR));
25852597
peer->m_getaddr_sent = true;
2598+
// When requesting a getaddr, accept an additional MAX_ADDR_TO_SEND addresses in response
2599+
// (bypassing the MAX_ADDR_PROCESSING_TOKEN_BUCKET limit).
2600+
peer->m_addr_token_bucket += MAX_ADDR_TO_SEND;
25862601
}
25872602

25882603
if (!pfrom.IsInboundConn()) {
@@ -2777,11 +2792,28 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
27772792
std::vector<CAddress> vAddrOk;
27782793
int64_t nNow = GetAdjustedTime();
27792794
int64_t nSince = nNow - 10 * 60;
2795+
2796+
// Update/increment addr rate limiting bucket.
2797+
const auto current_time = GetTime<std::chrono::microseconds>();
2798+
if (peer->m_addr_token_bucket < MAX_ADDR_PROCESSING_TOKEN_BUCKET) {
2799+
// Don't increment bucket if it's already full
2800+
const auto time_diff = std::max(current_time - peer->m_addr_token_timestamp, 0us);
2801+
const double increment = CountSecondsDouble(time_diff) * MAX_ADDR_RATE_PER_SECOND;
2802+
peer->m_addr_token_bucket = std::min<double>(peer->m_addr_token_bucket + increment, MAX_ADDR_PROCESSING_TOKEN_BUCKET);
2803+
}
2804+
peer->m_addr_token_timestamp = current_time;
2805+
2806+
const bool rate_limited = !pfrom.HasPermission(NetPermissionFlags::Addr);
27802807
for (CAddress& addr : vAddr)
27812808
{
27822809
if (interruptMsgProc)
27832810
return;
27842811

2812+
// Apply rate limiting.
2813+
if (rate_limited) {
2814+
if (peer->m_addr_token_bucket < 1.0) break;
2815+
peer->m_addr_token_bucket -= 1.0;
2816+
}
27852817
// We only bother storing full nodes, though this may include
27862818
// things which we would not make an outbound connection to, in
27872819
// part because we may make feeler connections to them.

test/functional/p2p_addr_relay.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class AddrTest(BitcoinTestFramework):
5353

5454
def set_test_params(self):
5555
self.num_nodes = 1
56+
self.extra_args = [["[email protected]"]]
5657

5758
def run_test(self):
5859
self.oversized_addr_test()
@@ -191,7 +192,7 @@ def getaddr_tests(self):
191192

192193
def blocksonly_mode_tests(self):
193194
self.log.info('Test addr relay in -blocksonly mode')
194-
self.restart_node(0, ["-blocksonly"])
195+
self.restart_node(0, ["-blocksonly", "[email protected]"])
195196
self.mocktime = int(time.time())
196197

197198
self.log.info('Check that we send getaddr messages')

test/functional/p2p_addrv2_relay.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class AddrTest(BitcoinTestFramework):
5353
def set_test_params(self):
5454
self.setup_clean_chain = True
5555
self.num_nodes = 1
56+
self.extra_args = [["[email protected]"]]
5657

5758
def run_test(self):
5859
self.log.info('Create connection that sends addrv2 messages')

test/functional/p2p_invalid_messages.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
5858
def set_test_params(self):
5959
self.num_nodes = 1
6060
self.setup_clean_chain = True
61+
self.extra_args = [["[email protected]"]]
6162

6263
def run_test(self):
6364
self.test_buffer()

0 commit comments

Comments
 (0)