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 #3008

Merged
merged 2 commits into from
Jul 15, 2022

Conversation

patricklodder
Copy link
Member

On the p2p network, sometimes custom nodes are sending us lots of peer addresses, the processing of which costs us CPU time and some data transfer (we try to announce every address we receive to 1-2 peers to keep network discovery going.) This was recently addressed on Bitcoin Core 22.0 by limiting the rate at which a node processes addresses received, helping to keep resource usage in check a little bit better.

This PR backports the relevant Bitcoin Core code (from 0d64b8f7, 5648138f, f424d601 and d930c7f5) with the following:

  1. Introduce a token bucket that keeps track of how many addresses our node will process for each peer. When a peer sends us too many addresses, we simply don't process the additional ones and move on.
  2. The maximum amount of tokens to accumulate is 1000; the same as the maximum entries in an addr message.
  3. The rate, 1-on-1 copied from Bitcoin Core, is currently set to 1 per 10 seconds, which is approximately 5x the amount that we see honest nodes do in the wild.
  4. Because an addr message can contain up to 1000 addresses, we randomize the list order before processing, to make it unpredictable which addresses will go through and which won't.
  5. Because our node can explicitly ask a peer for addresses using the getaddr message (i.e. when we are actively in search of peers to connect to), we assign these peers that we've requested the data from an additional 1000 tokens on top of the already allocated tokens once, temporarily increasing past the limit of 1000. Since our node controls when we send out getaddr, this is fully controlled by us, not the peer.
  6. The number of processed and rate limited addresses are added to the output of getpeerinfo, allowing node operators to identify misbehaving peers.

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 messages. Every connection
gets an associated token bucket. Processing an address in an ADDR
message from non-whitelisted peers consumes a token from the bucket.
If the bucket is empty, the address is ignored (it is not processed,
stored or forwarded). 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 message).

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). This is
similar to allowing bursts.

The rate limit of 0.1 addr/s was copied from Bitcoin Core.

Backported from: 0d64b8f, 5648138, f424d60 and d930c7f
Original authors: Pieter Wuille <pieter@wuille.net>, and
                  Jon Atack <jon@atack.com>
@patricklodder patricklodder added this to the 1.14.6 milestone Jun 22, 2022
@patricklodder patricklodder requested a review from a team June 22, 2022 15:51
@xanimo
Copy link
Member

xanimo commented Jul 4, 2022

built and tested on x86_64-pc-linux-gnu running jammy lts. this time around the timing is much more consistent at 11-12 seconds avg after running about 10 times in a row. that aside i think i've identified the pertinent logs indicating this enhancement is working as intended, most notably:

2022-07-04 08:48:26 received: addr (30303 bytes) peer=0
2022-07-04 08:48:26 Misbehaving: 127.0.0.1:51286 peer=0 (0 -> 20)
2022-07-04 08:48:26 ERROR: message addr size() = 1010
2022-07-04 08:48:26 ProcessMessages(addr, 30303 bytes) FAILED peer=0
2022-07-04 08:48:26 trying connection 123.123.0.56:10056 lastseen=5.3hrs

but have included the last test log i ran just in case:
https://gist.github.com/xanimo/b5c6906e11a50ebd185a3f91f2195bcc

lgtm despite my uncertainty regarding the 'error'.

@patricklodder
Copy link
Member Author

this time around the timing is much more consistent at 11-12 seconds avg after running about 10 times in a row

That was fixed with #2986, not with this PR

i think i've identified the pertinent logs indicating this enhancement is working as intended, most notably: [..] ERROR: message addr size() = 1010 [..]

That is actually not part of this change, but has always been part of the addr processing logic - the test for this particular error was introduced with #2976. Running this test on current 1.14.6-dev (4674dc1) should give you the same error in the log (and test success.)

The actual result of this PR can be seen in the log you posted a little bit above that:

2022-07-04 05:31:36 received: addr (2071 bytes) peer=0
2022-07-04 05:31:36 Received addr: 69 addresses (59 processed, 10 rate-limited) peer=0

@xanimo
Copy link
Member

xanimo commented Jul 8, 2022

this time around the timing is much more consistent at 11-12 seconds avg after running about 10 times in a row

That was fixed with #2986, not with this PR

yeah i remember 👍

i think i've identified the pertinent logs indicating this enhancement is working as intended, most notably: [..] ERROR: message addr size() = 1010 [..]

That is actually not part of this change, but has always been part of the addr processing logic - the test for this particular error was introduced with #2976. Running this test on current 1.14.6-dev (4674dc1) should give you the same error in the log (and test success.)

The actual result of this PR can be seen in the log you posted a little bit above that:

2022-07-04 05:31:36 received: addr (2071 bytes) peer=0
2022-07-04 05:31:36 Received addr: 69 addresses (59 processed, 10 rate-limited) peer=0

gotcha ok great 👍 thx 🙏

const uint64_t nCurrentTime = GetMockableTimeMicros();
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed this could be 0, thanks to the previous line. That seems fine; I just want to ask if there's any reason it wouldn't be.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 will only happen when we run something like an ntp client that automatically adjusts forward drift in our system clock. In that case, we assign 0 credits because 0 or less time expired. I think this is ok and in fact rather defensive - worst case we will always allow only an additional 1000 addresses to be processed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me!

Copy link
Member

@chromatic chromatic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks fine (with one question open). Built and tested successfully on 64-bit x86 Linux. ACK from me, pending the open question.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants