mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-11 06:28:31 +01:00
Merge bitcoin/bitcoin#28155: net: improves addnode / m_added_nodes logic
0420f99f42Create net_peer_connection unit tests (Jon Atack)4b834f6499Allow unit tests to access additional CConnman members (Jon Atack)34b9ef443bnet/rpc: Makes CConnman::GetAddedNodeInfo able to return only non-connected address on request (Sergi Delgado Segura)94e8882d82rpc: Prevents adding the same ip more than once when formatted differently (Sergi Delgado Segura)2574b7e177net/rpc: Check all resolved addresses in ConnectNode rather than just one (Sergi Delgado Segura) Pull request description: ## 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 to `m_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: 1. Calling `addnode` more than once in a short time period, using two equivalent but distinct addresses 2. Calling `addnode add` using an IP, and `addnode onetry` after with an address that resolved to the same IP For the former, the issue boils down to `CConnman::ThreadOpenAddedConnections` calling `CConnman::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: ``` bitcoin-cli addnode "127.0.0.1:port" add bitcoin-cli addnode "localhost:port" add ``` 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 the `CConnman::ThreadOpenAddedConnections` logic and calling `CConnman::OpenNetworkConnection` straightaway. A way to test this would be: ``` bitcoin-cli addnode "127.0.0.1:port" add bitcoin-cli addnode "localhost:port" onetry ``` ### Adding the same peer with two different, yet equivalent, addresses The current implementation of `addnode` is pretty naive when checking what data is added to `m_added_nodes`. Given the collection stores strings, the checks at `CConnman::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 to `127.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 by `getaddednodeinfo`, given they map to the same `CService`. 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 by `getpeerinfo`). However, this adds redundant data to `m_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 for `rpc`, in `getaddednodeinfo`, in which we are reporting all data to the user, so the former applies, and to check what nodes we are not connected to, in `CConnman::ThreadOpenAddedConnections`, in which we are currently returning more data than needed and then actively filtering using `CService.fConnected()` ACKs for top commit: jonatack: re-ACK0420f99f42kashifs: > > tACK [0420f9](0420f99f42) sr-gi: > > > tACK [0420f9](0420f99f42) mzumsande: Tested ACK0420f99f42Tree-SHA512: a3a10e748c12d98d439dfb193c75bc8d9486717cda5f41560f5c0ace1baef523d001d5e7eabac9fa466a9159a30bb925cc1327c2d6c4efb89dcaf54e176d1752
This commit is contained in:
67
src/net.cpp
67
src/net.cpp
@@ -417,21 +417,25 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
|
||||
const uint16_t default_port{pszDest != nullptr ? GetDefaultPort(pszDest) :
|
||||
m_params.GetDefaultPort()};
|
||||
if (pszDest) {
|
||||
const std::vector<CService> resolved{Lookup(pszDest, default_port, fNameLookup && !HaveNameProxy(), 256)};
|
||||
std::vector<CService> resolved{Lookup(pszDest, default_port, fNameLookup && !HaveNameProxy(), 256)};
|
||||
if (!resolved.empty()) {
|
||||
const CService& rnd{resolved[GetRand(resolved.size())]};
|
||||
addrConnect = CAddress{MaybeFlipIPv6toCJDNS(rnd), NODE_NONE};
|
||||
if (!addrConnect.IsValid()) {
|
||||
LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToStringAddrPort(), pszDest);
|
||||
return nullptr;
|
||||
}
|
||||
// It is possible that we already have a connection to the IP/port pszDest resolved to.
|
||||
// In that case, drop the connection that was just created.
|
||||
LOCK(m_nodes_mutex);
|
||||
CNode* pnode = FindNode(static_cast<CService>(addrConnect));
|
||||
if (pnode) {
|
||||
LogPrintf("Failed to open new connection, already connected\n");
|
||||
return nullptr;
|
||||
Shuffle(resolved.begin(), resolved.end(), FastRandomContext());
|
||||
// If the connection is made by name, it can be the case that the name resolves to more than one address.
|
||||
// We don't want to connect any more of them if we are already connected to one
|
||||
for (const auto& r : resolved) {
|
||||
addrConnect = CAddress{MaybeFlipIPv6toCJDNS(r), NODE_NONE};
|
||||
if (!addrConnect.IsValid()) {
|
||||
LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToStringAddrPort(), pszDest);
|
||||
return nullptr;
|
||||
}
|
||||
// It is possible that we already have a connection to the IP/port pszDest resolved to.
|
||||
// In that case, drop the connection that was just created.
|
||||
LOCK(m_nodes_mutex);
|
||||
CNode* pnode = FindNode(static_cast<CService>(addrConnect));
|
||||
if (pnode) {
|
||||
LogPrintf("Not opening a connection to %s, already connected to %s\n", pszDest, addrConnect.ToStringAddrPort());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2753,7 +2757,7 @@ std::vector<CAddress> CConnman::GetCurrentBlockRelayOnlyConns() const
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
|
||||
std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo(bool include_connected) const
|
||||
{
|
||||
std::vector<AddedNodeInfo> ret;
|
||||
|
||||
@@ -2788,6 +2792,9 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
|
||||
// strAddNode is an IP:port
|
||||
auto it = mapConnected.find(service);
|
||||
if (it != mapConnected.end()) {
|
||||
if (!include_connected) {
|
||||
continue;
|
||||
}
|
||||
addedNode.resolvedAddress = service;
|
||||
addedNode.fConnected = true;
|
||||
addedNode.fInbound = it->second;
|
||||
@@ -2796,6 +2803,9 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
|
||||
// strAddNode is a name
|
||||
auto it = mapConnectedByName.find(addr.m_added_node);
|
||||
if (it != mapConnectedByName.end()) {
|
||||
if (!include_connected) {
|
||||
continue;
|
||||
}
|
||||
addedNode.resolvedAddress = it->second.second;
|
||||
addedNode.fConnected = true;
|
||||
addedNode.fInbound = it->second.first;
|
||||
@@ -2814,21 +2824,19 @@ void CConnman::ThreadOpenAddedConnections()
|
||||
while (true)
|
||||
{
|
||||
CSemaphoreGrant grant(*semAddnode);
|
||||
std::vector<AddedNodeInfo> vInfo = GetAddedNodeInfo();
|
||||
std::vector<AddedNodeInfo> vInfo = GetAddedNodeInfo(/*include_connected=*/false);
|
||||
bool tried = false;
|
||||
for (const AddedNodeInfo& info : vInfo) {
|
||||
if (!info.fConnected) {
|
||||
if (!grant) {
|
||||
// If we've used up our semaphore and need a new one, let's not wait here since while we are waiting
|
||||
// the addednodeinfo state might change.
|
||||
break;
|
||||
}
|
||||
tried = true;
|
||||
CAddress addr(CService(), NODE_NONE);
|
||||
OpenNetworkConnection(addr, false, std::move(grant), info.m_params.m_added_node.c_str(), ConnectionType::MANUAL, info.m_params.m_use_v2transport);
|
||||
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) return;
|
||||
grant = CSemaphoreGrant(*semAddnode, /*fTry=*/true);
|
||||
if (!grant) {
|
||||
// If we've used up our semaphore and need a new one, let's not wait here since while we are waiting
|
||||
// the addednodeinfo state might change.
|
||||
break;
|
||||
}
|
||||
tried = true;
|
||||
CAddress addr(CService(), NODE_NONE);
|
||||
OpenNetworkConnection(addr, false, std::move(grant), info.m_params.m_added_node.c_str(), ConnectionType::MANUAL, info.m_params.m_use_v2transport);
|
||||
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) return;
|
||||
grant = CSemaphoreGrant(*semAddnode, /*fTry=*/true);
|
||||
}
|
||||
// Retry every 60 seconds if a connection was attempted, otherwise two seconds
|
||||
if (!interruptNet.sleep_for(std::chrono::seconds(tried ? 60 : 2)))
|
||||
@@ -3424,9 +3432,12 @@ std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addres
|
||||
|
||||
bool CConnman::AddNode(const AddedNodeParams& add)
|
||||
{
|
||||
const CService resolved(LookupNumeric(add.m_added_node, GetDefaultPort(add.m_added_node)));
|
||||
const bool resolved_is_valid{resolved.IsValid()};
|
||||
|
||||
LOCK(m_added_nodes_mutex);
|
||||
for (const auto& it : m_added_node_params) {
|
||||
if (add.m_added_node == it.m_added_node) return false;
|
||||
if (add.m_added_node == it.m_added_node || (resolved_is_valid && resolved == LookupNumeric(it.m_added_node, GetDefaultPort(it.m_added_node)))) return false;
|
||||
}
|
||||
|
||||
m_added_node_params.push_back(add);
|
||||
|
||||
Reference in New Issue
Block a user