Use case: I run a full node that accepts inbound connections and have a whitebind setting so my personal light client can always connect, even when maxconnections (and particularly all inbound slots) is already full.
Currently when connections are full, if we receive an inbound peer request, we look for a current connection to evict so the new peer can have a slot. To find an evict-able peer we go through all our peers and “protect” multiple categories of peers, then we evict the “worst” peer that is left unprotected. If there are no peers left to evict, the inbound connection is denied.
With this PR, if the inbound connection has forceinbound permission we start the eviction process by first protecting all noban and outbound connections, then selecting one of the remaining peers (if any) at random. Then we loop through all our current connections, removing protected peers from the evict-able list. If we end up protecting all our remaining connections, the randomly chosen peer is evicted.
forceinbound implies noban permission.
All outbound and noban connections remain protected from eviction.
DrahtBot
commented at 7:59 pm on May 8, 2023:
contributor
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.
Conflicts
Reviewers, this pull request conflicts with the following ones:
#28463 (p2p: Increase inbound capacity for block-relay only connections by mzumsande)
#27581 (net: Continuous ASMap health check by fjahr)
#27114 (p2p: Allow whitelisting outgoing connections by brunoerg)
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
pinheadmz renamed this:
Make peer eviction slightly more aggresive to make room for whitelisted inbound connections
Allow inbound whitebind connections to more aggresivey evict peers when slots are full
on May 8, 2023
DrahtBot renamed this:
Allow inbound whitebind connections to more aggresivey evict peers when slots are full
net: Allow inbound whitebind connections to more aggresivey evict peers when slots are full
on May 9, 2023
DrahtBot added the label
P2P
on May 9, 2023
in
src/node/eviction.cpp:234
in
e71d495ffboutdated
240+ if (vEvictionCandidates.empty()) {
241+ if (force) {
242+ return last_out.id;
243+ } else {
244+ return std::nullopt;
245+ }
Does RVO even apply here? I thought it applies to complex structures, a large vector being the canonical example, where we don’t want to copy every element. But here we’re just returning a NodeId which is a int64_t, wrapped by std::optional. I don’t think there’s any savings by moving instead of copying one of these.
Oh, good point @LarryRuane. I didn’t realize NodeId was just a typedef for int64_t. I think that entirely resolves it indeed.
stickies-v
commented at 9:58 pm on May 11, 2023:
contributor
Concept ACK, makes sense to prioritize whitelisted peers. Will review more in-depth soon.
pinheadmz renamed this:
net: Allow inbound whitebind connections to more aggresivey evict peers when slots are full
net: Allow inbound whitebind connections to more aggressively evict peers when slots are full
on May 12, 2023
in
src/node/eviction.cpp:87
in
e71d495ffboutdated
86 std::sort(elements.begin(), elements.end(), comparator);
87 size_t eraseSize = std::min(k, elements.size());
88+
89+ // Return the last erased element
90+ std::optional<NodeEvictionCandidate> last_out{std::nullopt};
91+ if (eraseSize > 0) last_out = elements.at(elements.size() - eraseSize);
I’m not sure this is correct, it ignores the checking of predicate?
in
src/node/eviction.cpp:205
in
e71d495ffboutdated
203+
204 // Deterministically select 4 peers to protect by netgroup.
205 // An attacker cannot predict which netgroups will be protected
206- EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4);
207+ auto last = EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4);
208+ if (last.has_value()) last_out = last.value();
77-template <typename T, typename Comparator>
78-static void EraseLastKElements(
79- std::vector<T>& elements, Comparator comparator, size_t k,
80+//! Sort an array of NodeEvictionCandidates by the specified comparator, then erase the last K elements where predicate is true.
81+//! Return the last element removed if any.
82+static std::optional<NodeEvictionCandidate> EraseLastKElements(
Returning just the last removed element feels like a very bespoke and perhaps unintuitive interface to me. I know we don’t currently need it, but perhaps it’s worth generalizing this a bit more and returning all the removed elements instead of just the last one? For the callsite, it’s trivial to keep just the last one? Just thinking out loud, perhaps this is difficult to do without an unacceptable overhead in terms of allocations etc.
0 // Return the last erased (not-to-be-evicted) element
mzumsande
commented at 6:44 pm on May 17, 2023:
contributor
Concept ACK
As I suggested in the review club, an alternative, more simple approach would be to just pick a random peer after removing NoBan and outbound connections when in force-mode. Then, if at the end of the usual eviction algorithm, we don’t have a evict-able peer, we would evict the random one instead.
This would make the code simpler (no need to change EraseLastKElements or keep track of last), and I don’t really see a major downside:
The EraseLastKElements eviction criteria don’t really seem to be sorted by priority anyway right now (because order doesn’t matter so far), so evicting a peer that would’ve been protected by later calls doesn’t seem better than evicting one that would’ve been protected by earlier ones.
Whitebind No-Ban peers are more trusted to begin with, but if they maliciously wanted to take over all inbound slots by repeatedly connecting to us on the whitebind address, they can do that anyway, whether the eviction is random or whether the current approach of the PR is used. So it’s not clear to me what the extra benefit of the current approach over random eviction is.
pinheadmz force-pushed
on May 17, 2023
pinheadmz
commented at 8:18 pm on May 17, 2023:
member
@stickies-v@mzumsande thanks for your review and feedback, I refactored the PR so we evict a random peer when forced if all other peers are protected. It is MUCH simpler ;-)
in
src/node/eviction.cpp:189
in
c826187b50outdated
185186 ProtectOutboundConnections(vEvictionCandidates);
187188+ // Hang on to one random node to evict if forced
189+ std::optional<NodeId> force_evict;
190+ if (vEvictionCandidates.size() > 0) {
in
src/node/eviction.cpp:191
in
c826187b50outdated
187188+ // Hang on to one random node to evict if forced
189+ std::optional<NodeId> force_evict;
190+ if (vEvictionCandidates.size() > 0) {
191+ FastRandomContext rng;
192+ size_t randpos = rng.randrange(vEvictionCandidates.size());
I’m not sure that’s a strict improvement? Documenting the interface (i.e. pretty much just the first line of what’s in net.cpp) seems more appropriate to be in the header indeed, but all the rest sounds more like implementation details that may best be kept close to the source code?
Also, my main issue is with (having just) the word “find”, which to me sounds like there are no side effects.
in
src/net.h:988
in
c826187b50outdated
981@@ -982,7 +982,13 @@ class CConnman
982 */
983 bool AlreadyConnectedToAddress(const CAddress& addr);
984985- bool AttemptToEvictConnection();
986+ /**
987+ * Attempt to find a connected node to disconnect.
988+ * Used to make room for new inbound connections, returns true if successful.
989+ * @param[in] force Try to evict a random node if all connections are otherwise protected.
suggested rephrasing (although I’m not sure about the “NoBan” qualifier, maybe we have a better established term?)
0 * [@param](/bitcoin-bitcoin/contributor/param/)[in] force If all connections are otherwise protected, still evict a random inbound NoBan node if it exists
stickies-v
commented at 8:57 pm on May 17, 2023:
contributor
I think this random selection approach suggested by mzumsande is a more elegant approach. “Light” in approach ACK because I need to build more confidence that this doesn’t introduce new attack vectors.
pinheadmz force-pushed
on May 18, 2023
pinheadmz
commented at 2:19 pm on May 18, 2023:
member
DrahtBot added the label
CI failed
on May 18, 2023
in
src/node/eviction.cpp:189
in
96b513f605outdated
185186 ProtectOutboundConnections(vEvictionCandidates);
187188+ // Hang on to one random node to evict if forced
189+ std::optional<NodeId> force_evict;
190+ if (vEvictionCandidates.size() > 0 && force) {
nit: if vEvictionCandidates is empty, there’s no point (I think, couldn’t see any side effects in EraseLastKElements or ProtectEvictionCandidatesByRatio) executing the next lines so might as well return early? Unless you think this makes the code less clear?
0 if (vEvictionCandidates.empty()) return std::nullopt;
1 std::optional<NodeId> force_evict;
2 if (force) {
Good point, although “if empty, return now” logic would make sense after each step in this function (why bother calling EraseLastKElements() with an empty array?) even without this PR. One thing we could do is add that logic to the beginning of EraseLastKElements() itself, but I dunno how much time is really wasted in there (sort, min, erase … ?)
As you mentioned in the review club, whitelisting just based on ports enables an attacker that discovered which port(s) are whitelisted to take over all (non-whitelisted) inbound connections slots.
Therefore, I think we have to be careful not to accidentally lead people to this footgun. Binding to 127.0.0.1 seems much more prudent to not set a bad example.
updated test and also included warning in release note
stickies-v
commented at 6:19 pm on May 22, 2023:
contributor
Code review ACK96b513f605fb2df441b66da583056b1c8acd4dbc, but I think the first commit should be removed from this PR, I think it’s an accident?
I think the code is good and suits the intent of the PR well. My only concern is about the potential footgun introduced, even if it is relatively mild. Anyone running bitcoind with whitebind=0.0.0.0:<port> will now be vulnerable to having all their inbound non-whitelisted slots taken over by an attacker that has figured out what the whitelisted port is. It doesn’t seem like a huge concern, given that:
it only affects inbound connections, which we already assume to be more susceptible to these kinds of attacks, and
it does require the user to manually whitebind to 0.0.0.0
but I just wanted to flag it here anyway in case it is actually more serious than I see it to be.
Also, this seems like behaviour change that would benefit from a mention in the release notes?
pinheadmz force-pushed
on May 24, 2023
pinheadmz referenced this in commit
fa78fc543c
on May 24, 2023
pinheadmz requested review from stickies-v
on May 24, 2023
in
doc/release-notes-27600.md:8
in
fa78fc543coutdated
0@@ -0,0 +1,10 @@
1+P2P
2+---
3+
4+- Inbound connections from hosts protected by `whitebind` or `whitelist`
5+ permissions will now be more likely to connect even if `maxconnections` is
6+ reached and the node's inbound slots are full. Because this is achieved by
7+ more aggressively finding a current connection to evict, users should take
8+ care only to set these permissions on local ports that attackers can not
I think “on local ports” is unnecessarily restrictive/scary. I think it’s still completely fine to whitelist remote addresses, you mostly just want to avoid ranges, right?
Not really sure, I’ve never looked into these options much and don’t know about best practices - I think that if you use -whitebind with non-local addresses, you’d at least have to make sure that that address is not self-advertised. I guess that this applies more to -whitebind than -whitelist, so that the preferred approach in case of a non-local connection would be to use -whitelist?
fyi @vasild, do you have an opinion on this?
That’s a good point, whitelist is harder to attack than whitebind because the attacker would have to spoof their origin IP repeatedly to fill up your inbounds. If a user whitebind’s a port and an attacker figures out that port numnber, they can trivially evict all your other inbounds
I agree with what you say above - whitebind on publicly accessible address:port with this new permission sounds bad. Similar with whitelist - if a range is used.
This PR currently expands the semantic of the noban permission. This will affect existent setups that already use it. Would it make sense to introduce a new permisson, separate from noban? I mean - now if somebody is running -whitebind=noban@publicaddr:port then a bad actor could cause harm on master, but even more harm with this PR.
interesting idea, so we could add NetPermissionFlags::NoBanForce or something. The danger will still be present for users of the permission, but existing users of NoBan wouldn’t have to worry. And since NoBan is a default of whitebind it’ll require more user attention anyway to use the more dangerous option.
In that case, the release note should still warn the user about using NoBanForce (or whatever we call it) but that warning can just be general, like, keep an eye on your netinfo if you do this. As opposed to just recommending only setting local addresses with whitebind
This PR currently expands the semantic of the noban permission
Could you please elaborate on this point? I thought that with this PR, noban nodes are still completely protected (they are removed from the eviction candidates list before the random node is chosen). But it’s certain that I’m missing something. Thanks.
@LarryRuane you are correct but what we are changing (in the current state of this branch) is that noban nodes can now force disconnection of other peers. Since many users may already have noban set, maybe even for a large range of IPs, this branch would introduce a new vulnerability they may not be aware of. Because of that I think it does make more sense to specify a new permission so users can narrow the attack surface
DrahtBot removed the label
CI failed
on May 25, 2023
LarryRuane
commented at 4:52 pm on June 10, 2023:
contributor
Please consider editing the description slightly, if my understanding is correct.
With this PR, if the inbound connection is on our whitelist we start the eviction process by selecting a random unprotected peer.
At this point in the process, where we choose a random peer, we’re considering all (inbound, not noban) peers, is that correct? They are all (other than outbound or noban) “unprotected” because we haven’t protected any of them yet. So maybe this should say, “… selecting a random peer” or “… selecting a random inbound peer” – because we may randomly choose a peer that ends up being protected, or not protected. That would make this PR easier for me to understand.
I do think the updated algorithm is very elegant (great suggestion, @mzumsande). It chooses a random peer, which may end up being erased from the eviction candidate list, or may not, but we don’t care either way; we evict it if we need to, all the better if it actually did not get protected (but, again, we don’t care).
By the way, I’m going to highlight this PR in the Optech newsletter next week, that’s why I’m particularly interested in making sure I understand it.
in
src/node/eviction.cpp:218
in
fa78fc543coutdated
214@@ -204,7 +215,7 @@ void ProtectEvictionCandidatesByRatio(std::vector<NodeEvictionCandidate>& evicti
215 // or disadvantaged characteristics.
216 ProtectEvictionCandidatesByRatio(vEvictionCandidates);
217218- if (vEvictionCandidates.empty()) return std::nullopt;
219+ if (vEvictionCandidates.empty()) return force_evict;
naumenkogs
commented at 7:42 am on November 9, 2023:
I’d suggest expanding this comment to explain what exactly happened.
in
src/node/eviction.h:46
in
fa78fc543coutdated
37@@ -38,8 +38,10 @@ struct NodeEvictionCandidate {
38 * fixed numbers of desirable peers per various criteria, followed by (mostly)
39 * ratios of desirable or disadvantaged peers. If any eviction candidates
40 * remain, the selection logic chooses a peer to evict.
41+ * @param[in] force Attempt to evict a random peer if no candidates
42+ * are available. (see CConman::AttemptToEvictConnection())
43 */
44-[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates);
45+[[nodiscard]] std::optional<NodeId> SelectNodeToEvict(std::vector<NodeEvictionCandidate>&& vEvictionCandidates, bool force = false);
I’m not a fan of default arguments; consider making this not have a default. It makes sense if there are many existing calls to a function and you don’t want to touch them all, but there’s only one call to this function in production code. You would have to touch about 6 call sites in test code, but that’s not too bad. The reason I’m not a fan of default arguments is that they hide something that may be helpful to see when just looking at the call site. (You might think, “Oh, it’s possible that eviction can be ‘forced’, what does that mean?) But this is an optional suggestion, fine if you leave it as-is, just something to consider.
naumenkogs
commented at 7:43 am on November 9, 2023:
This still remains default, no?
LarryRuane
commented at 6:20 pm on June 10, 2023:
contributor
utACKfa78fc543cd46ee1c7181ecf6b696c2892b9f8d3
except for possible attack vectors (for example, NoBanForce) that I’m not really qualified to comment on. I may try to contribute to that discussion after I’ve learned more. Nice PR!
DrahtBot requested review from stickies-v
on Jun 10, 2023
LarryRuane
commented at 8:00 pm on June 10, 2023:
contributor
Even though not needed for this PR, you may want to consider adding a commit to remove the unnecessary templating. It’s perfectly reasonable not to make this change in this PR, but I thought I’d document it just in case; I do think it makes the code easier to understand.
0--- a/src/node/eviction.cpp
1+++ b/src/node/eviction.cpp
2@@-74,9+74,10@@ struct CompareNodeNetworkTime {
3 };
4 5//! Sort an array by the specified comparator, then erase the last K elements where predicate is true. 6-template <typename T, typename Comparator> 7static void EraseLastKElements(
8- std::vector<T>& elements, Comparator comparator, size_t k,
9+ std::vector<NodeEvictionCandidate>& elements,
10+ std::function<bool(const NodeEvictionCandidate&, const NodeEvictionCandidate&)> comparator,
11+ size_t k,
12 std::function<bool(const NodeEvictionCandidate&)> predicate = [](const NodeEvictionCandidate& n) { return true; })
13 {
14 std::sort(elements.begin(), elements.end(), comparator);
pinheadmz
commented at 8:57 pm on June 10, 2023:
member
@LarryRuane thanks for reviewing. I actually had removed the templating in the very first version of this branch but after changing the approach I just left it alone. The function is written like a generic utility function, even though we don’t currently use it for any other vectors. So I think the function could go as written into a util.cpp or something, in a future PR ?
pinheadmz
commented at 4:05 pm on June 12, 2023:
member
@LarryRuane updated PR description to clarify where we get the random peer, added comment about the possible nullopt return value, and removed the default force value in SelectNodeToEvict()
pinheadmz referenced this in commit
468df13aa5
on Jun 12, 2023
pinheadmz force-pushed
on Jun 12, 2023
pinheadmz referenced this in commit
09dffe5594
on Jun 12, 2023
pinheadmz referenced this in commit
f21452378c
on Jun 12, 2023
pinheadmz force-pushed
on Jun 12, 2023
pinheadmz
commented at 5:11 pm on June 12, 2023:
member
@vasild@mzumsande Added a commit that restricts the aggressive eviction to new net permission ForceInbound which leaves NoBan unchanged.
in
src/test/fuzz/node_eviction.cpp:43
in
2a6a981e4doutdated
39@@ -40,7 +40,7 @@ FUZZ_TARGET(node_eviction)
40 // Make a copy since eviction_candidates may be in some valid but otherwise
41 // indeterminate state after the SelectNodeToEvict(&&) call.
42 const std::vector<NodeEvictionCandidate> eviction_candidates_copy = eviction_candidates;
43- const std::optional<NodeId> node_to_evict = SelectNodeToEvict(std::move(eviction_candidates));
44+ const std::optional<NodeId> node_to_evict = SelectNodeToEvict(std::move(eviction_candidates), /*force=*/false);
LarryRuane
commented at 5:22 pm on June 12, 2023:
contributor
utACK2a6a981e4d892c3d7196ddce54471350950affc7
These latest changes look good! I’ll test later in the week.
in
doc/release-notes-27600.md:6
in
2a6a981e4doutdated
0@@ -0,0 +1,10 @@
1+P2P
2+---
3+
4+- A new net permission `ForceInbound` (set with `-whitelist=forceinbound@...`
5+ or `-whitebind=forceinbound@...`) is introduced that extends `NoBan` permissions.
6+ Inbound connections from hosts protected by `ForceInbound` will now be more
stickies-v referenced this in commit
456b9ad924
on Jun 14, 2023
stickies-v
commented at 4:36 pm on June 14, 2023:
contributor
Code review ACK2a6a981e4d892c3d7196ddce54471350950affc7 (modulo my commit reordering comment at the end)
By adding the forceinbound (extending noban) permission we prevent existing setups from being exposed to a wider attack surface when they have whitelisted ranges of IP addresses. I find it difficult to judge whether the increased complexity (not so much extra code, but more permissions means potentially more interactions to test for etc; as well as the user having to make more choices) is really necessary, but I can see this being a pretty common use case - and this is an elegant solution.
I think the commits should be reordered though, as in https://github.com/stickies-v/bitcoin/commits/pr/27600: I would first introduce “net: add new permission ForceInbound” so we don’t need to immediately update the logic, test and docs to go from NoBan -> ForceInbound. Doesn’t make things more complex from a re-review pov, imo (range-diff works well for me), and I think it’s bad practice to introduce “vulnerabilities” into master, e.g. in case of revert. The rebase is straightforward too.
pinheadmz referenced this in commit
c8ce23745a
on Jun 16, 2023
pinheadmz force-pushed
on Jun 16, 2023
pinheadmz renamed this:
net: Allow inbound whitebind connections to more aggressively evict peers when slots are full
net: Add new permission `forceinbound` to evict a random unprotected connection if all slots are otherwise full
on Jun 16, 2023
pinheadmz
commented at 3:37 pm on June 16, 2023:
member
@stickies-v and @LarryRuane thanks for your review. I re-ordered the commits so the history makes more sense now. Also edited the title and description of the PR since we are adding an optional feature now, not changing any existing behavior.
git diff 2a6a981e4d c8ce23745a reflects the addressed comments, range-diff is screwed up because of commit reordering
stickies-v
commented at 3:53 pm on June 16, 2023:
contributor
Code Review re-ACKc8ce23745a
No changes but the review comments from LarryRuane and myself.
DrahtBot requested review from LarryRuane
on Jun 16, 2023
pinheadmz requested review from vasild
on Jun 22, 2023
pinheadmz requested review from mzumsande
on Jun 22, 2023
luke-jr
commented at 4:59 pm on June 23, 2023:
member
Would be better to score the peers rather than choose one at random.
DrahtBot removed review request from LarryRuane
on Jun 23, 2023
DrahtBot requested review from LarryRuane
on Jun 23, 2023
pinheadmz
commented at 5:40 pm on June 23, 2023:
member
Would be better to score the peers rather than choose one at random.
DrahtBot removed review request from LarryRuane
on Jun 23, 2023
DrahtBot requested review from LarryRuane
on Jun 23, 2023
mzumsande
commented at 7:37 pm on June 30, 2023:
contributor
Do I understand it correctly that using the new forceinbound permission instead of noban would be useful in the following two cases:
1.) If the user lowered -maxconnections to a value so low that the protection logic (that protects a fixed number between 20 and 28 inbound peers from eviction) could protect all inbound peers, but we still want to make sure to accept an inbound connection from a protected peer.
2) If we have a slightly higher -maxconnections but want to make sure a larger number of protected peers can connect to us at the same time.
So that a user running with the default -maxconnections of 125 would never have a reason to use forceinbound over noban unless they anticipate to have a large number of protected peers (>80 I guess?) simultaneously?
DrahtBot removed review request from LarryRuane
on Jun 30, 2023
DrahtBot requested review from LarryRuane
on Jun 30, 2023
pinheadmz
commented at 4:06 pm on July 5, 2023:
member
@mzumsande This summary looks correct to me. Point (1) is covered by the functional test. You are also right that by default most nodes will not need the flag since there will be enough open slots or evictable peers.
I think the use case highlighted in the issue (as well as my own use case) is more about “personal” nodes or limited-resource nodes, where max connections are more limited but we still always want our light clients to be able to connect.
DrahtBot removed review request from LarryRuane
on Jul 5, 2023
DrahtBot requested review from LarryRuane
on Jul 5, 2023
naumenkogs
commented at 10:02 am on July 25, 2023:
member
Consider when a malicious Internet provider or something can request the forceinbound IP address.
Does this PR open any fundamentally new risk in this case? I’m thinking of disrupting the network by causing many disconnections if the forceinbound practice is broad.
This probably depends on whether it’s possible to take multiple slots from the same forceinbound IP (or, rather, initiate multiple evictions), which I’m not 100% sure about.
If my suspicions are correct, this might better wait for authenticated connections…
DrahtBot removed review request from LarryRuane
on Jul 25, 2023
DrahtBot requested review from LarryRuane
on Jul 25, 2023
pinheadmz
commented at 11:26 am on July 25, 2023:
member
Thanks so much for taking a look Gleb and for your insight, yeah I have the same concerns. That attack vector is not super trivial, the attacker needs to both learn and then spoof the protected IP range. The release notes could recommend only using ranges for local subnets, or we could enforce that in the code. We could also change it soforceinbound does not evict existing peers but may add connections beyond the maximum, or set another internal limit just for forceinbound
DrahtBot removed review request from LarryRuane
on Jul 25, 2023
DrahtBot requested review from LarryRuane
on Jul 25, 2023
naumenkogs
commented at 9:02 am on July 26, 2023:
member
the attacker needs to both learn and then spoof the protected IP range
I think a malicious ISP gets that trivially (at least in the pre-BIP324 world) by simply monitoring the traffic.
In theory, specifying “forceinbound=iprange;n_extra_slots” instead of eviction is probably a good idea, but I’m not sure how ugly the code will look like. I remember max_connections stuff is already sophisticated :(
DrahtBot removed review request from LarryRuane
on Jul 26, 2023
DrahtBot requested review from LarryRuane
on Jul 26, 2023
pinheadmz
commented at 3:51 pm on July 26, 2023:
member
I guess allowing more connections beyond the max is a DoS vector, which could potentially crash a node and break all existing connections anyway… hm.
DrahtBot removed review request from LarryRuane
on Jul 26, 2023
DrahtBot requested review from LarryRuane
on Jul 26, 2023
DrahtBot removed review request from LarryRuane
on Jul 26, 2023
DrahtBot requested review from LarryRuane
on Jul 26, 2023
naumenkogs
commented at 9:36 am on July 27, 2023:
member
@pinheadmz In the example I had above n_extra_slots controls how many extra connections you allow so it should be safe.
DrahtBot removed review request from LarryRuane
on Jul 27, 2023
DrahtBot requested review from LarryRuane
on Jul 27, 2023
pinheadmz
commented at 8:24 pm on July 28, 2023:
member
@naumenkogs I added one more commit that limits the number of simultaneous forced-inbound connections to 8. I didn’t add it as an init option yet but we could if you like that approach. Although I think it should be set by a separate argument like -maxforceinbound instead of adding another syntactic element to the whitebind setting.
A connection only counts as forced-inbound if it succeeded in evicting another peer, and that is marked by a new bool in CNodeOptions
naumenkogs
commented at 9:44 am on July 31, 2023:
member
@pinheadmz yeah this is probably the best attempt at minimizing the problem I mentioned. Whether it’s satisfactory is still at question :) I’m looking forward to seeing what others think.
vasild
commented at 11:04 am on July 31, 2023:
contributor
this might better wait for authenticated connections
I2P and CJDNS connections are already authenticated. Addresses in those networks represent the node’s public key and connections have a source address. Thus to spoof, one would need to steal the private key from its owner.
luke-jr referenced this in commit
f95b5f8840
on Aug 16, 2023
luke-jr referenced this in commit
7f86217b5b
on Aug 16, 2023
luke-jr referenced this in commit
a47f27c183
on Aug 16, 2023
luke-jr referenced this in commit
4a2bbbffe2
on Aug 16, 2023
luke-jr referenced this in commit
812df253be
on Aug 29, 2023
luke-jr referenced this in commit
a88314bccc
on Aug 29, 2023
luke-jr referenced this in commit
df56306e8a
on Aug 29, 2023
luke-jr referenced this in commit
92f93e5559
on Aug 29, 2023
achow101 removed review request from LarryRuane
on Sep 20, 2023
achow101 requested review from willcl-ark
on Sep 20, 2023
achow101 requested review from amitiuttarwar
on Sep 20, 2023
in
test/functional/p2p_eviction.py:147
in
bd64f1fabboutdated
139+
140+ self.log.debug("Default whitebind inbound gets rejected, even when full")
141+ with node.assert_debug_log(["failed to find an eviction candidate - connection dropped (full)"]):
142+ node.add_p2p_connection(RejectedPeer(), wait_for_verack=False, dstport=30201)
143+
144+ self.log.debug("ForceInbound whitebind inbound gets connected, even when full")
willcl-ark
commented at 6:02 pm on September 25, 2023:
I was wondering if it might be nice to add a test which showed a previously-protected connection, e.g. a single Tor connection, being disconnected when a forceinbound connection from the whitebind port arrives. But as our test P2P connections already originate from localhost (127.0.0.1) my understanding is that these already count as a “previously-protected” group (as they might be an inbound Tor peer), so perhaps not necessary…
pinheadmz
commented at 5:13 pm on September 26, 2023:
I’m trying to figure out if
Local peers are protected in any special way (I don’t think they are?)
Inbound onion connections are considered local (They aren’t - inbound onions are identifiable because they bind to a different network port onion_service_target_port)
And then, I don’t think all onion peers are protected per se but their latency is compensated for in the eviction process:
in
src/net_permissions.cpp:19
in
9190383b5eoutdated
14@@ -15,7 +15,8 @@ const std::vector<std::string> NET_PERMISSIONS_DOC{
15 "relay (relay even in -blocksonly mode, and unlimited transaction announcements)",
16 "mempool (allow requesting BIP35 mempool contents)",
17 "download (allow getheaders during IBD, no disconnect after maxuploadtarget limit)",
18- "addr (responses to GETADDR avoid hitting the cache and contain random records with the most up-to-date info)"
19+ "addr (responses to GETADDR avoid hitting the cache and contain random records with the most up-to-date info)",
20+ "forceinbound (when connections are full, attempt to evict a random unprotected peer to open a slot; implies noban)"
willcl-ark
commented at 6:09 pm on September 25, 2023:
nit: if you touch again you could add here that it’s specifically a “random unprotected inbound peer”, as this is what many users will see in the --help (vs the release notes or code comments)
pinheadmz
commented at 2:19 pm on September 26, 2023:
willcl-ark
commented at 7:57 am on September 26, 2023:
style nit: it’s not in our style docs but as I understand it we prefer brace initialization for new code
0bool forced_inbound{false};
pinheadmz
commented at 2:16 pm on September 26, 2023:
ah thanks, will touch this up and in one other place in the top commit
willcl-ark approved
willcl-ark
commented at 8:52 am on September 26, 2023:
contributor
ACK8585fe3f80
Only optional style nits if you touch again.
I’m pleased to see the final commit limiting the max number of forceinbound connections, as otherwise I too would have been a bit apprehensive of a user sharing a whitebind address:port with the world and seeing all their (non-noban) inbounds disconnected.
I’ve experienced the issue this pull should fix myself, usually solving it by having to remotely restart the node and racing other nodes in re-connecting, which is not particularly user-friendly.
Side note: whilst playing with various option combinations I noticed that we cannot set -listen=0 in combinatation with -whitebind=.... On one level this does make sense, but I now wonder whether -whitebind (but not -bind) could be permitted when listen=0, as it could make sense to want to accept only connections of this type?
DrahtBot requested review from LarryRuane
on Sep 26, 2023
DrahtBot requested review from stickies-v
on Sep 26, 2023
DrahtBot removed review request from LarryRuane
on Sep 26, 2023
DrahtBot requested review from LarryRuane
on Sep 26, 2023
DrahtBot removed review request from LarryRuane
on Sep 26, 2023
DrahtBot requested review from LarryRuane
on Sep 26, 2023
DrahtBot removed review request from LarryRuane
on Sep 26, 2023
DrahtBot requested review from LarryRuane
on Sep 26, 2023
DrahtBot removed review request from LarryRuane
on Sep 26, 2023
DrahtBot requested review from LarryRuane
on Sep 26, 2023
pinheadmz referenced this in commit
559e5c0e66
on Sep 26, 2023
pinheadmz force-pushed
on Sep 26, 2023
pinheadmz
commented at 5:36 pm on September 26, 2023:
member
@willcl-ark thanks for reviewing, addressed your feedback except for the new test but always open to writing more tests so lemme know if you think something is uncovered there.
pinheadmz referenced this in commit
514b3bc8f2
on Sep 26, 2023
pinheadmz force-pushed
on Sep 26, 2023
pinheadmz force-pushed
on Sep 27, 2023
pinheadmz
commented at 8:07 pm on September 27, 2023:
member
Rebasing on master hopefully to fix Windows CI failure
pinheadmz referenced this in commit
5e759b79b3
on Sep 27, 2023
DrahtBot added the label
CI failed
on Sep 28, 2023
DrahtBot removed the label
CI failed
on Sep 28, 2023
DrahtBot added the label
Needs rebase
on Oct 3, 2023
pinheadmz force-pushed
on Oct 12, 2023
pinheadmz referenced this in commit
6a6d713524
on Oct 12, 2023
DrahtBot removed the label
Needs rebase
on Oct 12, 2023
pinheadmz
commented at 2:33 pm on October 16, 2023:
member
Rebased on master again, thanks @willcl-ark for your review. Hoping to get some more feedback from @mzumsande and @naumenkogs to proceed, or abandon
luke-jr referenced this in commit
1f92b0816b
on Oct 19, 2023
luke-jr referenced this in commit
ce17c96fff
on Oct 19, 2023
luke-jr referenced this in commit
71ab3e0594
on Oct 19, 2023
luke-jr referenced this in commit
14daeb3a75
on Oct 19, 2023
mzumsande
commented at 7:27 pm on November 3, 2023:
contributor
Hoping to get some more feedback from @mzumsande and @naumenkogs to proceed, or abandon
Personally, I’m not sure if the use case (running a node with -maxconnections < 40 and at the same time wanting certain inbound peers, or wanting a really high number of whitelisted inbounds peers at the same time) is common enough to introduce an extra permission just for that. In more typical cases, noban should be sufficient.
But if others think it is I’m not against it.
A related use case I could see (but I’m also not sure if there is demand for that) is to only be reachable by whitelisted “friends” but no one else. That doesn’t seem to be possible right now, -fListen is either all or none.
DrahtBot added the label
Needs rebase
on Nov 7, 2023
naumenkogs
commented at 9:54 am on November 8, 2023:
member
@pinheadmz Honestly I still struggle to understand what are the practical scenarios of using this. E.g., what could lead to not being able to evict a connection?
pinheadmz
commented at 4:30 pm on November 8, 2023:
member
Quick math 4+8+4+8+4 = 28 protected nodes. Plus 8 full outbound and 2 block only = 38. So if a user runs a full node on limited hardware (like my Raspberry Pi at home) and have something like -maxconnections=30 then even with the existing whitebind permissions, their privileged node may fail to connect.
That being said, it ALSO means that the solution to #8798 may be to just ensure maxconnections is set > 40. It’s been 7 years since that issue was open and I can check with my own (modern) hardware that 40 connections is sane.
in
src/node/eviction.cpp:187
in
c5e7e65632outdated
184 ProtectNoBanConnections(vEvictionCandidates);
185186 ProtectOutboundConnections(vEvictionCandidates);
187188+ // No inbound or unprotected connections left, return early
189+ if (vEvictionCandidates.empty()) return std::nullopt;
naumenkogs
commented at 7:40 am on November 9, 2023:
c5e7e6563201965e88e07aa8da8f26e17fc1b4db
“unprotected” here is sloppy. I’d just drop the comment.
pinheadmz
commented at 4:41 pm on November 9, 2023:
ok
in
src/node/eviction.h:42
in
c5e7e65632outdated
37@@ -38,8 +38,10 @@ struct NodeEvictionCandidate {
38 * fixed numbers of desirable peers per various criteria, followed by (mostly)
39 * ratios of desirable or disadvantaged peers. If any eviction candidates
40 * remain, the selection logic chooses a peer to evict.
41+ * @param[in] force Attempt to evict a random peer if no candidates
42+ * are available. (see CConman::AttemptToEvictConnection())
naumenkogs
commented at 7:44 am on November 9, 2023:
c5e7e6563201965e88e07aa8da8f26e17fc1b4db
“among inbound no-noban connections” or something (i hate no-noban but not sure what’s better)
pinheadmz
commented at 4:42 pm on November 9, 2023:
done, thanks
in
src/net.cpp:1738
in
311902f2cfoutdated
1775@@ -1776,8 +1776,9 @@ void CConnman::CreateNodeFromAcceptedSocket(std::unique_ptr<Sock>&& sock,
1776 const CAddress& addr_bind,
1777 const CAddress& addr)
1778 {
1779- int nInbound = 0;
1780- int nMaxInbound = nMaxConnections - m_max_outbound;
1781+ int nInbound{0};
1782+ int nForced{0};
naumenkogs
commented at 7:53 am on November 9, 2023:
311902f2cf94ee0e11ed34a1373db42dbc218b20
No need to use legacy naming patterns for new variables :)
pinheadmz
commented at 4:42 pm on November 9, 2023:
hmmm I’m gonna keep it actually because later in this function is a boolean called forced so, I think this is legible
naumenkogs
commented at 7:57 am on November 9, 2023:
311902f2cf94ee0e11ed34a1373db42dbc218b20
should this be exposed in peer info?
pinheadmz
commented at 4:42 pm on November 9, 2023:
good idea, added a new commit for this
eviction: track one random unprotected node to evict if forced
Accomplished by adding a bool argument `force` to SelectNodeToEvict()
0c0f2a2c36
net: add new permission ForceInbound
Only inbound nodes with this permission set will call
`SelectNodeToEvict()` with force=true, so when connections are full
there is an increased liklihood of opening a slot for the new inbound.
Extends NoBan permission.
99399b3cbf
net: nodes with ForceInbound permission force eviction8bc203073a
test: cover ForceInbound permission success even when connections are full6b6bcaf0b9
doc: add release note for #27600e4b860ab9e
net: only allow 8 simultaneous forced inbound connections75868022a9
pinheadmz force-pushed
on Nov 9, 2023
pinheadmz
commented at 4:55 pm on November 9, 2023:
member
Thanks for the review Gleb, I also rebased on master to fix conflicts
DrahtBot added the label
CI failed
on Nov 9, 2023
net: add forced_inbound to getpeerinfo8c2026848d
pinheadmz force-pushed
on Nov 9, 2023
DrahtBot removed the label
Needs rebase
on Nov 9, 2023
DrahtBot removed the label
CI failed
on Nov 9, 2023
in
src/net.h:229
in
8c2026848d
225@@ -226,6 +226,8 @@ class CNodeStats
226 TransportProtocolType m_transport_type;
227 /** BIP324 session id string in hex, if any. */
228 std::string m_session_id;
229+ /** whether this peer forced its connection by evicting another */
naumenkogs
commented at 7:45 am on November 10, 2023:
8c2026848da910fdebff0a9f73e29f1f6ae81e43
So forced actually means two things to you:
Before connecting — something that might evict another peer
After connecting — something that has evicted another peer
I think this is confusing and better to use different words. RPC can expose “force_evicted” flag (to cover the latter case and apply to the Connection), while “forceInbound” can stay in the network/potential-connection context (the former case).
naumenkogs
commented at 7:49 am on November 10, 2023:
Apparently, in the previous commit 75868022a904c1f77871abf962bf9b88a9c5faf6 you assign forced = true; even for non-forceInbound peers.
So basically if a node had 8 connections which are non-forceInbound but still did eviction, a real forceInbound connection won’t be able to join (see how you count nForced). Is that intended? Seems like a bug to me.
naumenkogs
commented at 7:50 am on November 10, 2023:
member
If we could approach this without the need to introduce an additional permission I’d be happier, since it seems a big change for a narrow use case with a potential workaround by just accepting more than 38 total connections (plus N for the nobans). I also wonder how this plays out with #27114, given this would be an in only permission.
Given this only triggers under really specific conditions, can we not just prioritize our whitelisted peer under those conditions? The addition of new whitelisted peers can take priority over the ones we protect for “diversity criteria” as long as we do not have enough candidates to evict a peer, that is: after protecting the current noban and outbound peers, if the number of remaining peers is smaller than the number of peers we will protect for diversity criteria, we just pick one of the remaining at random and remove them. All this limited to a maximum as you are already doing with MAX_FORCED_INBOUND_CONNECTIONS.
For this to trigger, your number of noban connections needs to be too high, and/or, your max number of connections is too low. If we are concerned that this could backfire, as @vasild mentioned, we could even gatekeep it under a global flag so it does not affect any of the existing noban connections.
Retropex referenced this in commit
06849e404b
on Mar 28, 2024
Retropex referenced this in commit
ad6ed41cd2
on Mar 28, 2024
Retropex referenced this in commit
f488e26da6
on Mar 28, 2024
Retropex referenced this in commit
cee2e8c415
on Mar 28, 2024
Retropex referenced this in commit
07b24236a8
on Mar 28, 2024
Retropex referenced this in commit
9349155145
on Mar 28, 2024
DrahtBot
commented at 0:02 am on April 2, 2024:
contributor
⌛ There hasn’t been much activity lately and the patch still needs rebase. What is the status here?
Is it still relevant? ➡️ Please solve the conflicts to make it ready for review and to ensure the CI passes.
Is it no longer relevant? ➡️ Please close.
Did the author lose interest or time to work on this? ➡️ Please close it and mark it ‘Up for grabs’ with the label, so that it can be picked up in the future.
This is a metadata mirror of the GitHub repository
bitcoin/bitcoin.
This site is not affiliated with GitHub.
Content is generated from a GitHub metadata backup.
generated: 2024-06-29 07:13 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me