diff --git a/src/net_permissions.h b/src/net_permissions.h --- a/src/net_permissions.h +++ b/src/net_permissions.h @@ -32,7 +32,8 @@ 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), // Bypass the limit on how many proof INVs are tracked from this peer as // well as the delay penalty when reaching the the in-flight requests limit diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -280,6 +280,17 @@ static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23; /** The maximum number of address records permitted in an ADDR message. */ 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}; inline size_t GetMaxAddrToSend() { return gArgs.GetArg("-maxaddrtosend", MAX_ADDR_TO_SEND); @@ -390,6 +401,14 @@ std::atomic_bool m_wants_addrv2{false}; /** Whether this peer has already sent us a getaddr message. */ 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()}; /** * Set of txids to reconsider once their parent transactions have been @@ -3486,6 +3505,10 @@ m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version) .Make(NetMsgType::GETADDR)); 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 += GetMaxAddrToSend(); } if (!pfrom.IsInboundConn()) { @@ -3648,11 +3671,36 @@ 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, 0us); + const double increment = + CountSecondsDouble(time_diff) * 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. diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py --- a/test/functional/p2p_addr_relay.py +++ b/test/functional/p2p_addr_relay.py @@ -52,6 +52,7 @@ def set_test_params(self): self.num_nodes = 1 + self.extra_args = [["-whitelist=addr@127.0.0.1"]] def run_test(self): self.oversized_addr_test() @@ -213,7 +214,7 @@ def blocksonly_mode_tests(self): 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.log.info('Check that we send getaddr messages') diff --git a/test/functional/p2p_addrv2_relay.py b/test/functional/p2p_addrv2_relay.py --- a/test/functional/p2p_addrv2_relay.py +++ b/test/functional/p2p_addrv2_relay.py @@ -44,6 +44,7 @@ def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 + self.extra_args = [["-whitelist=addr@127.0.0.1"]] def run_test(self): self.log.info('Create connection that sends addrv2 messages') diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -54,6 +54,7 @@ def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True + self.extra_args = [["-whitelist=addr@127.0.0.1"]] def run_test(self): self.test_buffer()