Skip to content

Commit d3474b8

Browse files
committed
Merge bitcoin/bitcoin#22387: Rate limit the processing of rumoured addresses
a4bcd68 Improve tests using statistics (John Newbery) f424d60 Add logging and addr rate limiting statistics (Pieter Wuille) b4ece8a Functional tests for addr rate limiting (Pieter Wuille) 5648138 Randomize the order of addr processing (Pieter Wuille) 0d64b8f Rate limit the processing of incoming addr messages (Pieter Wuille) Pull request description: The rate at which IP addresses are rumoured (through ADDR and ADDRV2 messages) on the network seems to vary from 0 for some non-participating nodes, to 0.005-0.025 addr/s for recent Bitcoin Core nodes. However, the current codebase will happily accept and process an effectively unbounded rate from attackers. There are measures to limit the influence attackers can have on the addrman database (bucket restrictions based on source IPs), but still - there is no need to permit them to feed us addresses at a rate that's orders of magnitude larger than what is common on the network today, especially as it will cause us to spam our peers too. This PR implements a [token bucket](https://en.wikipedia.org/wiki/Token_bucket) based rate limiter, allowing an average of 0.1 addr/s per connection, with bursts up to 1000 addresses at once. Whitelisted peers as well as responses to GETADDR requests are exempt from the limit. New connections start with 1 token, so as to not interfere with the common practice of peers' self-announcement. ACKs for top commit: laanwj: ACK a4bcd68 vasild: ACK a4bcd68 jnewbery: ACK a4bcd68 jonatack: ACK a4bcd68 Tree-SHA512: b757de76ad78a53035b622944c4213b29b3b55d3d98bf23585afa84bfba10808299d858649f92269a16abfa75eb4366ea047eae3216f7e2f6d3c455782a16bea
2 parents e8f85e0 + a4bcd68 commit d3474b8

7 files changed

+159
-7
lines changed

src/net_permissions.h

+2-1
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

+54
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,15 @@ 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>()};
248+
/** Total number of addresses that were dropped due to rate limiting. */
249+
std::atomic<uint64_t> m_addr_rate_limited{0};
250+
/** Total number of addresses that were processed (excludes rate limited ones). */
251+
std::atomic<uint64_t> m_addr_processed{0};
236252

237253
/** Set of txids to reconsider once their parent transactions have been accepted **/
238254
std::set<uint256> m_orphan_work_set GUARDED_BY(g_cs_orphans);
@@ -1239,6 +1255,8 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c
12391255
}
12401256

12411257
stats.m_ping_wait = ping_wait;
1258+
stats.m_addr_processed = peer->m_addr_processed.load();
1259+
stats.m_addr_rate_limited = peer->m_addr_rate_limited.load();
12421260

12431261
return true;
12441262
}
@@ -2583,6 +2601,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
25832601
// Get recent addresses
25842602
m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::GETADDR));
25852603
peer->m_getaddr_sent = true;
2604+
// When requesting a getaddr, accept an additional MAX_ADDR_TO_SEND addresses in response
2605+
// (bypassing the MAX_ADDR_PROCESSING_TOKEN_BUCKET limit).
2606+
peer->m_addr_token_bucket += MAX_ADDR_TO_SEND;
25862607
}
25872608

25882609
if (!pfrom.IsInboundConn()) {
@@ -2777,11 +2798,34 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
27772798
std::vector<CAddress> vAddrOk;
27782799
int64_t nNow = GetAdjustedTime();
27792800
int64_t nSince = nNow - 10 * 60;
2801+
2802+
// Update/increment addr rate limiting bucket.
2803+
const auto current_time = GetTime<std::chrono::microseconds>();
2804+
if (peer->m_addr_token_bucket < MAX_ADDR_PROCESSING_TOKEN_BUCKET) {
2805+
// Don't increment bucket if it's already full
2806+
const auto time_diff = std::max(current_time - peer->m_addr_token_timestamp, 0us);
2807+
const double increment = CountSecondsDouble(time_diff) * MAX_ADDR_RATE_PER_SECOND;
2808+
peer->m_addr_token_bucket = std::min<double>(peer->m_addr_token_bucket + increment, MAX_ADDR_PROCESSING_TOKEN_BUCKET);
2809+
}
2810+
peer->m_addr_token_timestamp = current_time;
2811+
2812+
const bool rate_limited = !pfrom.HasPermission(NetPermissionFlags::Addr);
2813+
uint64_t num_proc = 0;
2814+
uint64_t num_rate_limit = 0;
2815+
Shuffle(vAddr.begin(), vAddr.end(), FastRandomContext());
27802816
for (CAddress& addr : vAddr)
27812817
{
27822818
if (interruptMsgProc)
27832819
return;
27842820

2821+
// Apply rate limiting.
2822+
if (rate_limited) {
2823+
if (peer->m_addr_token_bucket < 1.0) {
2824+
++num_rate_limit;
2825+
continue;
2826+
}
2827+
peer->m_addr_token_bucket -= 1.0;
2828+
}
27852829
// We only bother storing full nodes, though this may include
27862830
// things which we would not make an outbound connection to, in
27872831
// part because we may make feeler connections to them.
@@ -2795,6 +2839,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
27952839
// Do not process banned/discouraged addresses beyond remembering we received them
27962840
continue;
27972841
}
2842+
++num_proc;
27982843
bool fReachable = IsReachable(addr);
27992844
if (addr.nTime > nSince && !peer->m_getaddr_sent && vAddr.size() <= 10 && addr.IsRoutable()) {
28002845
// Relay to a limited number of other nodes
@@ -2804,6 +2849,15 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
28042849
if (fReachable)
28052850
vAddrOk.push_back(addr);
28062851
}
2852+
peer->m_addr_processed += num_proc;
2853+
peer->m_addr_rate_limited += num_rate_limit;
2854+
LogPrint(BCLog::NET, "Received addr: %u addresses (%u processed, %u rate-limited) from peer=%d%s\n",
2855+
vAddr.size(),
2856+
num_proc,
2857+
num_rate_limit,
2858+
pfrom.GetId(),
2859+
fLogIPs ? ", peeraddr=" + pfrom.addr.ToString() : "");
2860+
28072861
m_addrman.Add(vAddrOk, pfrom.addr, 2 * 60 * 60);
28082862
if (vAddr.size() < 1000) peer->m_getaddr_sent = false;
28092863
if (pfrom.IsAddrFetchConn()) {

src/net_processing.h

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ struct CNodeStateStats {
2929
int m_starting_height = -1;
3030
std::chrono::microseconds m_ping_wait;
3131
std::vector<int> vHeightInFlight;
32+
uint64_t m_addr_processed = 0;
33+
uint64_t m_addr_rate_limited = 0;
3234
};
3335

3436
class PeerManager : public CValidationInterface, public NetEventsInterface

src/rpc/net.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ static RPCHelpMan getpeerinfo()
242242
heights.push_back(height);
243243
}
244244
obj.pushKV("inflight", heights);
245+
obj.pushKV("addr_processed", statestats.m_addr_processed);
246+
obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited);
245247
}
246248
UniValue permissions(UniValue::VARR);
247249
for (const auto& permission : NetPermissions::ToStrings(stats.m_permissionFlags)) {

test/functional/p2p_addr_relay.py

+94-5
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,20 @@
1313
msg_addr,
1414
msg_getaddr
1515
)
16-
from test_framework.p2p import P2PInterface
17-
from test_framework.test_framework import BitcoinTestFramework
18-
from test_framework.util import (
19-
assert_equal,
16+
from test_framework.p2p import (
17+
P2PInterface,
18+
p2p_lock,
2019
)
20+
from test_framework.test_framework import BitcoinTestFramework
21+
from test_framework.util import assert_equal
22+
import random
2123
import time
2224

2325

2426
class AddrReceiver(P2PInterface):
2527
num_ipv4_received = 0
2628
test_addr_contents = False
29+
_tokens = 1
2730

2831
def __init__(self, test_addr_contents=False):
2932
super().__init__()
@@ -40,6 +43,20 @@ def on_addr(self, message):
4043
raise AssertionError("Invalid addr.port of {} (8333-8342 expected)".format(addr.port))
4144
assert addr.ip.startswith('123.123.123.')
4245

46+
def on_getaddr(self, message):
47+
# When the node sends us a getaddr, it increments the addr relay tokens for the connection by 1000
48+
self._tokens += 1000
49+
50+
@property
51+
def tokens(self):
52+
with p2p_lock:
53+
return self._tokens
54+
55+
def increment_tokens(self, n):
56+
# When we move mocktime forward, the node increments the addr relay tokens for its peers
57+
with p2p_lock:
58+
self._tokens += n
59+
4360
def addr_received(self):
4461
return self.num_ipv4_received != 0
4562

@@ -53,12 +70,14 @@ class AddrTest(BitcoinTestFramework):
5370

5471
def set_test_params(self):
5572
self.num_nodes = 1
73+
self.extra_args = [["[email protected]"]]
5674

5775
def run_test(self):
5876
self.oversized_addr_test()
5977
self.relay_tests()
6078
self.getaddr_tests()
6179
self.blocksonly_mode_tests()
80+
self.rate_limit_tests()
6281

6382
def setup_addr_msg(self, num):
6483
addrs = []
@@ -75,6 +94,19 @@ def setup_addr_msg(self, num):
7594
msg.addrs = addrs
7695
return msg
7796

97+
def setup_rand_addr_msg(self, num):
98+
addrs = []
99+
for i in range(num):
100+
addr = CAddress()
101+
addr.time = self.mocktime + i
102+
addr.nServices = NODE_NETWORK | NODE_WITNESS
103+
addr.ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}"
104+
addr.port = 8333
105+
addrs.append(addr)
106+
msg = msg_addr()
107+
msg.addrs = addrs
108+
return msg
109+
78110
def send_addr_msg(self, source, msg, receivers):
79111
source.send_and_ping(msg)
80112
# pop m_next_addr_send timer
@@ -191,7 +223,7 @@ def getaddr_tests(self):
191223

192224
def blocksonly_mode_tests(self):
193225
self.log.info('Test addr relay in -blocksonly mode')
194-
self.restart_node(0, ["-blocksonly"])
226+
self.restart_node(0, ["-blocksonly", "[email protected]"])
195227
self.mocktime = int(time.time())
196228

197229
self.log.info('Check that we send getaddr messages')
@@ -207,6 +239,63 @@ def blocksonly_mode_tests(self):
207239

208240
self.nodes[0].disconnect_p2ps()
209241

242+
def send_addrs_and_test_rate_limiting(self, peer, no_relay, new_addrs, total_addrs):
243+
"""Send an addr message and check that the number of addresses processed and rate-limited is as expected"""
244+
245+
peer.send_and_ping(self.setup_rand_addr_msg(new_addrs))
246+
247+
peerinfo = self.nodes[0].getpeerinfo()[0]
248+
addrs_processed = peerinfo['addr_processed']
249+
addrs_rate_limited = peerinfo['addr_rate_limited']
250+
self.log.debug(f"addrs_processed = {addrs_processed}, addrs_rate_limited = {addrs_rate_limited}")
251+
252+
if no_relay:
253+
assert_equal(addrs_processed, 0)
254+
assert_equal(addrs_rate_limited, 0)
255+
else:
256+
assert_equal(addrs_processed, min(total_addrs, peer.tokens))
257+
assert_equal(addrs_rate_limited, max(0, total_addrs - peer.tokens))
258+
259+
def rate_limit_tests(self):
260+
261+
self.mocktime = int(time.time())
262+
self.restart_node(0, [])
263+
self.nodes[0].setmocktime(self.mocktime)
264+
265+
for contype, no_relay in [("outbound-full-relay", False), ("block-relay-only", True), ("inbound", False)]:
266+
self.log.info(f'Test rate limiting of addr processing for {contype} peers')
267+
if contype == "inbound":
268+
peer = self.nodes[0].add_p2p_connection(AddrReceiver())
269+
else:
270+
peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type=contype)
271+
272+
# Send 600 addresses. For all but the block-relay-only peer this should result in addresses being processed.
273+
self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 600)
274+
275+
# Send 600 more addresses. For the outbound-full-relay peer (which we send a GETADDR, and thus will
276+
# process up to 1001 incoming addresses), this means more addresses will be processed.
277+
self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 1200)
278+
279+
# Send 10 more. As we reached the processing limit for all nodes, no more addresses should be procesesd.
280+
self.send_addrs_and_test_rate_limiting(peer, no_relay, 10, 1210)
281+
282+
# Advance the time by 100 seconds, permitting the processing of 10 more addresses.
283+
# Send 200 and verify that 10 are processed.
284+
self.mocktime += 100
285+
self.nodes[0].setmocktime(self.mocktime)
286+
peer.increment_tokens(10)
287+
288+
self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1410)
289+
290+
# Advance the time by 1000 seconds, permitting the processing of 100 more addresses.
291+
# Send 200 and verify that 100 are processed.
292+
self.mocktime += 1000
293+
self.nodes[0].setmocktime(self.mocktime)
294+
peer.increment_tokens(100)
295+
296+
self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1610)
297+
298+
self.nodes[0].disconnect_p2ps()
210299

211300
if __name__ == '__main__':
212301
AddrTest().main()

test/functional/p2p_addrv2_relay.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ def __init__(self):
4242
super().__init__(support_addrv2 = True)
4343

4444
def on_addrv2(self, message):
45-
if ADDRS == message.addrs:
45+
expected_set = set((addr.ip, addr.port) for addr in ADDRS)
46+
received_set = set((addr.ip, addr.port) for addr in message.addrs)
47+
if expected_set == received_set:
4648
self.addrv2_received_and_checked = True
4749

4850
def wait_for_addrv2(self):
@@ -53,6 +55,7 @@ class AddrTest(BitcoinTestFramework):
5355
def set_test_params(self):
5456
self.setup_clean_chain = True
5557
self.num_nodes = 1
58+
self.extra_args = [["[email protected]"]]
5659

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

test/functional/p2p_invalid_messages.py

+1
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)