-
Notifications
You must be signed in to change notification settings - Fork 35.5k
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: improves addnode / m_added_nodes logic #28155
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code CoverageFor detailed information about the code coverage, see the test coverage report. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
I've purposely split this into three commits according to each of the fixes so they can be discussed, and considered independently. That way it'll be easier to remove any if the change does not reach enough consensus. |
ad3fee2
to
2110ac1
Compare
2110ac1
to
866bc36
Compare
would it make sense to add a functional test like the below under
in |
I don't think that'd work. You'll still be able to add two nodes that resolve to the same IP using What could be added, however, is a check for PS: This actually made me realize that the check for whether to add something to |
6304950
to
b5763bc
Compare
Added a test to |
b5763bc
to
a6fcf17
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approach ACK a6fcf17
a6fcf17
to
d0e7e18
Compare
Addressed @vasild comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK d0e7e18
Would be nice to print the hostname here: #28155 (comment)
d0e7e18
to
fd8b1db
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK fd8b1db
that are otherwise private: - CConnman::m_nodes - CConnman::ConnectNodes() - CConnman::AlreadyConnectedToAddress() and update the #include headers per iwyu.
3d1854c
to
fc1e9e4
Compare
Addresses review comments and add @jonatack's test commits |
for initial partial unit test coverage of these CConnman class methods: - AddNode() - ConnectNode() - GetAddedNodeInfo() - AlreadyConnectedToAddress() - ThreadOpenAddedConnections() and of the GetAddedNodeInfo() call in RPC addnode.
fc1e9e4
to
0420f99
Compare
re-ACK 0420f99 |
tACK 0420f9 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested ACK 0420f99
I pulled the branch, compiled from source on my Mac, and ran:
and
I also pored over every line of modified code in order to better understand the design. Is there anything else that I should do that might be helpful? |
Nope, that's good. Thanks again for reviewing and testing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 0420f99
CNode* ConnmanTestMsg::ConnectNodePublic(PeerManager& peerman, const char* pszDest, ConnectionType conn_type) | ||
{ | ||
CNode* node = ConnectNode(CAddress{}, pszDest, /*fCountFailure=*/false, conn_type, /*use_v2transport=*/true); | ||
if (!node) return nullptr; | ||
node->SetCommonVersion(PROTOCOL_VERSION); | ||
peerman.InitializeNode(*node, ServiceFlags(NODE_NETWORK | NODE_WITNESS)); | ||
node->fSuccessfullyConnected = true; | ||
AddTestNode(*node); | ||
return node; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, naming: maybe a different name would have been better than ConnectNodePublic()
, given that it contains extra logic on top of the private ConnectNode()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. I'm touching that code for #28248, if you have a naming suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dunno, maybe ConnectNodeAndInitialize()
?
… connecting to a node fd81a37 net: attempts to connect to all resolved addresses when connecting to a node (Sergi Delgado Segura) Pull request description: This is a follow-up of #28155 motivated by #28155 (comment) ## Rationale Prior to this, when establishing a network connection via `CConnman::ConnectNode`, if the connection needed address resolution, a single address would be picked at random from the resolved addresses and our node would try to connect to it. However, this would lead to the behavior of `ConnectNode` being unpredictable when the address was resolved to various ips (e.g. the address resolving to IPv4 and IPv6, but we only support one of them). This patches the aforementioned behavior by going over all resolved IPs until a valid one is found or until we exhaust them. ACKs for top commit: mzumsande: re-ACK fd81a37 (just looked at diff, only small logging change) achow101: ACK fd81a37 vasild: ACK fd81a37 Tree-SHA512: fa1ebc5c84fe61dd0a7fe1113ae2d594a75ad661c43ed8984a31fc9bc50f166b2759b0d8d84ee5dc247691eff78c8156fac970af797bbcbf67492eec0353fb58
… one The current `addnode` rpc command has some edge cases in where it is possible to connect to the same node twice by combining ip and address requests. This can happen under two situations: The two commands are run one right after each other, in which case they will be processed under the same loop in `CConnman::ThreadOpenAddedConnections` without refreshing `vInfo`, so both will go trough. An example of this would be: ``` bitcoin-cli addnode "localhost:port" add ``` A node is added by IP using `addnode "add"` while the other is added by name using `addnode "onetry"` with an address that resolves to multiple IPs. In this case, we currently only check one of the resolved IPs (picked at random), instead of all the resolved ones, meaning this will only probabilistically fail/succeed. An example of this would be: ``` bitcoin-cli addnode "127.0.0.1:port" add [...] bitcoin-cli addnode "localhost:port" onetry ``` Both cases can be fixed by iterating over all resolved addresses in `CConnman::ConnectNode` instead of picking one at random Github-Pull: bitcoin#28155 Rebased-From: 2574b7e
…ently Currently it is possible to add the same node twice when formatting IPs in different, yet equivalent, manner. This applies to both ipv4 and ipv6, e.g: 127.0.0.1 = 127.1 | [::1] = [0:0:0:0:0:0:0:1] `addnode` will accept both and display both as connected (given they translate to the same IP). This will not result in multiple connections to the same node, but will report redundant info when querying `getaddednodeinfo` and populate `m_added_nodes` with redundant data. This can be avoided performing comparing the contents of `m_added_addr` and the address to be added as `CServices` instead of as strings. Github-Pull: bitcoin#28155 Rebased-From: 94e8882
…nected address on request `CConnman::GetAddedNodeInfo` is used both to get a list of addresses to manually connect to in `CConnman::ThreadOpenAddedConnections`, and to report about manually added connections in `getaddednodeinfo`. In both cases, all addresses added to `m_added_nodes` are returned, however the nodes we are already connected to are only relevant to the latter, in the former they are actively discarded. Parametrizes `CConnman::GetAddedNodeInfo` so we can ask for only addresses we are not connected to, to avoid passing useless information around. Github-Pull: bitcoin#28155 Rebased-From: 34b9ef4
that are otherwise private: - CConnman::m_nodes - CConnman::ConnectNodes() - CConnman::AlreadyConnectedToAddress() and update the #include headers per iwyu. Github-Pull: bitcoin#28155 Rebased-From: 4b834f6
for initial partial unit test coverage of these CConnman class methods: - AddNode() - ConnectNode() - GetAddedNodeInfo() - AlreadyConnectedToAddress() - ThreadOpenAddedConnections() and of the GetAddedNodeInfo() call in RPC addnode. Github-Pull: bitcoin#28155 Rebased-From: 0420f99
Rationale
Currently,
addnode
has a couple of corner cases that allow it to either connect to the same peer more than once, hence wasting outbound connection slots, or add redundant information tom_added_nodes
, hence making Bitcoin iterate through useless data on a regular basis.Connecting to the same node more than once
In general, connecting to the same node more than once is something we should try to prevent. Currently, this is possible via
addnode
in two different ways:addnode
more than once in a short time period, using two equivalent but distinct addressesaddnode add
using an IP, andaddnode onetry
after with an address that resolved to the same IPFor the former, the issue boils down to
CConnman::ThreadOpenAddedConnections
callingCConnman::GetAddedNodeInfo
once, and iterating over the result to open connections (CConman::OpenNetworkConnection
) on the same loop for all addresses.CConnman::ConnectNode
only checks a single address, at random, when resolving from a hostname, and uses it to check whether we are already connected to it.An example to test this would be calling:
And check how it allows us to perform both connections some times, and some times it fails.
The latter boils down to the same issue, but takes advantage of
onetry
bypassing theCConnman::ThreadOpenAddedConnections
logic and callingCConnman::OpenNetworkConnection
straightaway. A way to test this would be:Adding the same peer with two different, yet equivalent, addresses
The current implementation of
addnode
is pretty naive when checking what data is added tom_added_nodes
. Given the collection stores strings, the checks atCConnman::AddNode()
basically check wether the exact provided string is already in the collection. If so, the data is rejected, otherwise, it is accepted. However, ips can be formatted in several ways that would bypass those checks.Two examples would be
127.0.0.1
being equal to127.1
and[::1]
being equal to[0:0:0:0:0:0:0:1]
. Adding any pair of these will be allowed by the rpc command, and both will be reported as connected bygetaddednodeinfo
, given they map to the sameCService
.This is less severe than the previous issue, since even tough both nodes are reported as connected by
getaddednodeinfo
, there is only a single connection to them (as properly reported bygetpeerinfo
). However, this adds redundant data tom_added_nodes
, which is undesirable.Parametrize
CConnman::GetAddedNodeInfo
Finally, this PR also parametrizes
CConnman::GetAddedNodeInfo
so it returns either all added nodes info, or only info about the nodes we are not connected to. This method is used both forrpc
, ingetaddednodeinfo
, in which we are reporting all data to the user, so the former applies, and to check what nodes we are not connected to, inCConnman::ThreadOpenAddedConnections
, in which we are currently returning more data than needed and then actively filtering usingCService.fConnected()