mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-02 09:46:52 -05:00
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.
This commit is contained in:
parent
a88fa1a555
commit
0d64b8f709
5 changed files with 38 additions and 2 deletions
|
@ -31,7 +31,8 @@ enum class NetPermissionFlags : uint32_t {
|
||||||
NoBan = (1U << 4) | Download,
|
NoBan = (1U << 4) | Download,
|
||||||
// Can query the mempool
|
// Can query the mempool
|
||||||
Mempool = (1U << 5),
|
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.
|
||||||
Addr = (1U << 7),
|
Addr = (1U << 7),
|
||||||
|
|
||||||
// True if the user did not specifically set fine grained permissions
|
// True if the user did not specifically set fine grained permissions
|
||||||
|
|
|
@ -155,6 +155,13 @@ static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000;
|
||||||
static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23;
|
static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23;
|
||||||
/** The maximum number of address records permitted in an ADDR message. */
|
/** The maximum number of address records permitted in an ADDR message. */
|
||||||
static constexpr size_t MAX_ADDR_TO_SEND{1000};
|
static constexpr size_t 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};
|
||||||
|
|
||||||
// Internal stuff
|
// Internal stuff
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -233,6 +240,11 @@ struct Peer {
|
||||||
std::atomic_bool m_wants_addrv2{false};
|
std::atomic_bool m_wants_addrv2{false};
|
||||||
/** Whether this peer has already sent us a getaddr message. */
|
/** Whether this peer has already sent us a getaddr message. */
|
||||||
bool m_getaddr_recvd{false};
|
bool m_getaddr_recvd{false};
|
||||||
|
/** 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>()};
|
||||||
|
|
||||||
/** Set of txids to reconsider once their parent transactions have been accepted **/
|
/** Set of txids to reconsider once their parent transactions have been accepted **/
|
||||||
std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans);
|
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,
|
||||||
// Get recent addresses
|
// Get recent addresses
|
||||||
m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::GETADDR));
|
m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::GETADDR));
|
||||||
peer->m_getaddr_sent = true;
|
peer->m_getaddr_sent = 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()) {
|
if (!pfrom.IsInboundConn()) {
|
||||||
|
@ -2777,11 +2792,28 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
||||||
std::vector<CAddress> vAddrOk;
|
std::vector<CAddress> vAddrOk;
|
||||||
int64_t nNow = GetAdjustedTime();
|
int64_t nNow = GetAdjustedTime();
|
||||||
int64_t nSince = nNow - 10 * 60;
|
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, 0us);
|
||||||
|
const double increment = CountSecondsDouble(time_diff) * 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::Addr);
|
||||||
for (CAddress& addr : vAddr)
|
for (CAddress& addr : vAddr)
|
||||||
{
|
{
|
||||||
if (interruptMsgProc)
|
if (interruptMsgProc)
|
||||||
return;
|
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
|
// We only bother storing full nodes, though this may include
|
||||||
// things which we would not make an outbound connection to, in
|
// things which we would not make an outbound connection to, in
|
||||||
// part because we may make feeler connections to them.
|
// part because we may make feeler connections to them.
|
||||||
|
|
|
@ -53,6 +53,7 @@ class AddrTest(BitcoinTestFramework):
|
||||||
|
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.num_nodes = 1
|
self.num_nodes = 1
|
||||||
|
self.extra_args = [["-whitelist=addr@127.0.0.1"]]
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
self.oversized_addr_test()
|
self.oversized_addr_test()
|
||||||
|
@ -191,7 +192,7 @@ class AddrTest(BitcoinTestFramework):
|
||||||
|
|
||||||
def blocksonly_mode_tests(self):
|
def blocksonly_mode_tests(self):
|
||||||
self.log.info('Test addr relay in -blocksonly mode')
|
self.log.info('Test addr relay in -blocksonly mode')
|
||||||
self.restart_node(0, ["-blocksonly"])
|
self.restart_node(0, ["-blocksonly", "-whitelist=addr@127.0.0.1"])
|
||||||
self.mocktime = int(time.time())
|
self.mocktime = int(time.time())
|
||||||
|
|
||||||
self.log.info('Check that we send getaddr messages')
|
self.log.info('Check that we send getaddr messages')
|
||||||
|
|
|
@ -53,6 +53,7 @@ class AddrTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
self.num_nodes = 1
|
self.num_nodes = 1
|
||||||
|
self.extra_args = [["-whitelist=addr@127.0.0.1"]]
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
self.log.info('Create connection that sends addrv2 messages')
|
self.log.info('Create connection that sends addrv2 messages')
|
||||||
|
|
|
@ -58,6 +58,7 @@ class InvalidMessagesTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.num_nodes = 1
|
self.num_nodes = 1
|
||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
|
self.extra_args = [["-whitelist=addr@127.0.0.1"]]
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
self.test_buffer()
|
self.test_buffer()
|
||||||
|
|
Loading…
Add table
Reference in a new issue