Skip to content

Commit 9ad19fc

Browse files
committed
Merge bitcoin#28155: net: improves addnode / m_added_nodes logic
0420f99 Create net_peer_connection unit tests (Jon Atack) 4b834f6 Allow unit tests to access additional CConnman members (Jon Atack) 34b9ef4 net/rpc: Makes CConnman::GetAddedNodeInfo able to return only non-connected address on request (Sergi Delgado Segura) 94e8882 rpc: Prevents adding the same ip more than once when formatted differently (Sergi Delgado Segura) 2574b7e net/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-ACK 0420f99 kashifs: > > tACK [0420f9](bitcoin@0420f99) sr-gi: > > > tACK [0420f9](bitcoin@0420f99) mzumsande: Tested ACK 0420f99 Tree-SHA512: a3a10e748c12d98d439dfb193c75bc8d9486717cda5f41560f5c0ace1baef523d001d5e7eabac9fa466a9159a30bb925cc1327c2d6c4efb89dcaf54e176d1752
2 parents d690f89 + 0420f99 commit 9ad19fc

File tree

9 files changed

+238
-36
lines changed

9 files changed

+238
-36
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ BITCOIN_TESTS =\
112112
test/miniscript_tests.cpp \
113113
test/minisketch_tests.cpp \
114114
test/multisig_tests.cpp \
115+
test/net_peer_connection_tests.cpp \
115116
test/net_peer_eviction_tests.cpp \
116117
test/net_tests.cpp \
117118
test/netbase_tests.cpp \

src/net.cpp

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -417,21 +417,25 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
417417
const uint16_t default_port{pszDest != nullptr ? GetDefaultPort(pszDest) :
418418
m_params.GetDefaultPort()};
419419
if (pszDest) {
420-
const std::vector<CService> resolved{Lookup(pszDest, default_port, fNameLookup && !HaveNameProxy(), 256)};
420+
std::vector<CService> resolved{Lookup(pszDest, default_port, fNameLookup && !HaveNameProxy(), 256)};
421421
if (!resolved.empty()) {
422-
const CService& rnd{resolved[GetRand(resolved.size())]};
423-
addrConnect = CAddress{MaybeFlipIPv6toCJDNS(rnd), NODE_NONE};
424-
if (!addrConnect.IsValid()) {
425-
LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToStringAddrPort(), pszDest);
426-
return nullptr;
427-
}
428-
// It is possible that we already have a connection to the IP/port pszDest resolved to.
429-
// In that case, drop the connection that was just created.
430-
LOCK(m_nodes_mutex);
431-
CNode* pnode = FindNode(static_cast<CService>(addrConnect));
432-
if (pnode) {
433-
LogPrintf("Failed to open new connection, already connected\n");
434-
return nullptr;
422+
Shuffle(resolved.begin(), resolved.end(), FastRandomContext());
423+
// If the connection is made by name, it can be the case that the name resolves to more than one address.
424+
// We don't want to connect any more of them if we are already connected to one
425+
for (const auto& r : resolved) {
426+
addrConnect = CAddress{MaybeFlipIPv6toCJDNS(r), NODE_NONE};
427+
if (!addrConnect.IsValid()) {
428+
LogPrint(BCLog::NET, "Resolver returned invalid address %s for %s\n", addrConnect.ToStringAddrPort(), pszDest);
429+
return nullptr;
430+
}
431+
// It is possible that we already have a connection to the IP/port pszDest resolved to.
432+
// In that case, drop the connection that was just created.
433+
LOCK(m_nodes_mutex);
434+
CNode* pnode = FindNode(static_cast<CService>(addrConnect));
435+
if (pnode) {
436+
LogPrintf("Not opening a connection to %s, already connected to %s\n", pszDest, addrConnect.ToStringAddrPort());
437+
return nullptr;
438+
}
435439
}
436440
}
437441
}
@@ -2753,7 +2757,7 @@ std::vector<CAddress> CConnman::GetCurrentBlockRelayOnlyConns() const
27532757
return ret;
27542758
}
27552759

2756-
std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
2760+
std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo(bool include_connected) const
27572761
{
27582762
std::vector<AddedNodeInfo> ret;
27592763

@@ -2788,6 +2792,9 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
27882792
// strAddNode is an IP:port
27892793
auto it = mapConnected.find(service);
27902794
if (it != mapConnected.end()) {
2795+
if (!include_connected) {
2796+
continue;
2797+
}
27912798
addedNode.resolvedAddress = service;
27922799
addedNode.fConnected = true;
27932800
addedNode.fInbound = it->second;
@@ -2796,6 +2803,9 @@ std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() const
27962803
// strAddNode is a name
27972804
auto it = mapConnectedByName.find(addr.m_added_node);
27982805
if (it != mapConnectedByName.end()) {
2806+
if (!include_connected) {
2807+
continue;
2808+
}
27992809
addedNode.resolvedAddress = it->second.second;
28002810
addedNode.fConnected = true;
28012811
addedNode.fInbound = it->second.first;
@@ -2814,21 +2824,19 @@ void CConnman::ThreadOpenAddedConnections()
28142824
while (true)
28152825
{
28162826
CSemaphoreGrant grant(*semAddnode);
2817-
std::vector<AddedNodeInfo> vInfo = GetAddedNodeInfo();
2827+
std::vector<AddedNodeInfo> vInfo = GetAddedNodeInfo(/*include_connected=*/false);
28182828
bool tried = false;
28192829
for (const AddedNodeInfo& info : vInfo) {
2820-
if (!info.fConnected) {
2821-
if (!grant) {
2822-
// If we've used up our semaphore and need a new one, let's not wait here since while we are waiting
2823-
// the addednodeinfo state might change.
2824-
break;
2825-
}
2826-
tried = true;
2827-
CAddress addr(CService(), NODE_NONE);
2828-
OpenNetworkConnection(addr, false, std::move(grant), info.m_params.m_added_node.c_str(), ConnectionType::MANUAL, info.m_params.m_use_v2transport);
2829-
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) return;
2830-
grant = CSemaphoreGrant(*semAddnode, /*fTry=*/true);
2830+
if (!grant) {
2831+
// If we've used up our semaphore and need a new one, let's not wait here since while we are waiting
2832+
// the addednodeinfo state might change.
2833+
break;
28312834
}
2835+
tried = true;
2836+
CAddress addr(CService(), NODE_NONE);
2837+
OpenNetworkConnection(addr, false, std::move(grant), info.m_params.m_added_node.c_str(), ConnectionType::MANUAL, info.m_params.m_use_v2transport);
2838+
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) return;
2839+
grant = CSemaphoreGrant(*semAddnode, /*fTry=*/true);
28322840
}
28332841
// Retry every 60 seconds if a connection was attempted, otherwise two seconds
28342842
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
34243432

34253433
bool CConnman::AddNode(const AddedNodeParams& add)
34263434
{
3435+
const CService resolved(LookupNumeric(add.m_added_node, GetDefaultPort(add.m_added_node)));
3436+
const bool resolved_is_valid{resolved.IsValid()};
3437+
34273438
LOCK(m_added_nodes_mutex);
34283439
for (const auto& it : m_added_node_params) {
3429-
if (add.m_added_node == it.m_added_node) return false;
3440+
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;
34303441
}
34313442

34323443
m_added_node_params.push_back(add);

src/net.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1184,7 +1184,7 @@ class CConnman
11841184

11851185
bool AddNode(const AddedNodeParams& add) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex);
11861186
bool RemoveAddedNode(const std::string& node) EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex);
1187-
std::vector<AddedNodeInfo> GetAddedNodeInfo() const EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex);
1187+
std::vector<AddedNodeInfo> GetAddedNodeInfo(bool include_connected) const EXCLUSIVE_LOCKS_REQUIRED(!m_added_nodes_mutex);
11881188

11891189
/**
11901190
* Attempts to open a connection. Currently only used from tests.

src/rpc/net.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ static RPCHelpMan getaddednodeinfo()
500500
NodeContext& node = EnsureAnyNodeContext(request.context);
501501
const CConnman& connman = EnsureConnman(node);
502502

503-
std::vector<AddedNodeInfo> vInfo = connman.GetAddedNodeInfo();
503+
std::vector<AddedNodeInfo> vInfo = connman.GetAddedNodeInfo(/*include_connected=*/true);
504504

505505
if (!request.params[0].isNull()) {
506506
bool found = false;

src/test/fuzz/connman.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ FUZZ_TARGET(connman, .init = initialize_connman)
121121
connman.SetTryNewOutboundPeer(fuzzed_data_provider.ConsumeBool());
122122
});
123123
}
124-
(void)connman.GetAddedNodeInfo();
124+
(void)connman.GetAddedNodeInfo(fuzzed_data_provider.ConsumeBool());
125125
(void)connman.GetExtraFullOutboundCount();
126126
(void)connman.GetLocalServices();
127127
(void)connman.GetMaxOutboundTarget();
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright (c) 2023-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <chainparams.h>
6+
#include <compat/compat.h>
7+
#include <net.h>
8+
#include <net_processing.h>
9+
#include <netaddress.h>
10+
#include <netbase.h>
11+
#include <netgroup.h>
12+
#include <node/connection_types.h>
13+
#include <protocol.h>
14+
#include <random.h>
15+
#include <test/util/logging.h>
16+
#include <test/util/net.h>
17+
#include <test/util/random.h>
18+
#include <test/util/setup_common.h>
19+
#include <tinyformat.h>
20+
#include <util/chaintype.h>
21+
#include <version.h>
22+
23+
#include <algorithm>
24+
#include <cstdint>
25+
#include <memory>
26+
#include <optional>
27+
#include <string>
28+
#include <vector>
29+
30+
#include <boost/test/unit_test.hpp>
31+
32+
struct LogIPsTestingSetup : public TestingSetup {
33+
LogIPsTestingSetup()
34+
: TestingSetup{ChainType::MAIN, /*extra_args=*/{"-logips"}} {}
35+
};
36+
37+
BOOST_FIXTURE_TEST_SUITE(net_peer_connection_tests, LogIPsTestingSetup)
38+
39+
static CService ip(uint32_t i)
40+
{
41+
struct in_addr s;
42+
s.s_addr = i;
43+
return CService{CNetAddr{s}, Params().GetDefaultPort()};
44+
}
45+
46+
/** Create a peer and connect to it. If the optional `address` (IP/CJDNS only) isn't passed, a random address is created. */
47+
static void AddPeer(NodeId& id, std::vector<CNode*>& nodes, PeerManager& peerman, ConnmanTestMsg& connman, ConnectionType conn_type, bool onion_peer = false, std::optional<std::string> address = std::nullopt)
48+
{
49+
CAddress addr{};
50+
51+
if (address.has_value()) {
52+
addr = CAddress{MaybeFlipIPv6toCJDNS(LookupNumeric(address.value(), Params().GetDefaultPort())), NODE_NONE};
53+
} else if (onion_peer) {
54+
auto tor_addr{g_insecure_rand_ctx.randbytes(ADDR_TORV3_SIZE)};
55+
BOOST_REQUIRE(addr.SetSpecial(OnionToString(tor_addr)));
56+
}
57+
58+
while (!addr.IsLocal() && !addr.IsRoutable()) {
59+
addr = CAddress{ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE};
60+
}
61+
62+
BOOST_REQUIRE(addr.IsValid());
63+
64+
const bool inbound_onion{onion_peer && conn_type == ConnectionType::INBOUND};
65+
66+
nodes.emplace_back(new CNode{++id,
67+
/*sock=*/nullptr,
68+
addr,
69+
/*nKeyedNetGroupIn=*/0,
70+
/*nLocalHostNonceIn=*/0,
71+
CAddress{},
72+
/*addrNameIn=*/"",
73+
conn_type,
74+
/*inbound_onion=*/inbound_onion});
75+
CNode& node = *nodes.back();
76+
node.SetCommonVersion(PROTOCOL_VERSION);
77+
78+
peerman.InitializeNode(node, ServiceFlags(NODE_NETWORK | NODE_WITNESS));
79+
node.fSuccessfullyConnected = true;
80+
81+
connman.AddTestNode(node);
82+
}
83+
84+
BOOST_AUTO_TEST_CASE(test_addnode_getaddednodeinfo_and_connection_detection)
85+
{
86+
auto connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params());
87+
auto peerman = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, {});
88+
NodeId id{0};
89+
std::vector<CNode*> nodes;
90+
91+
// Connect a localhost peer.
92+
{
93+
ASSERT_DEBUG_LOG("Added connection to 127.0.0.1:8333 peer=1");
94+
AddPeer(id, nodes, *peerman, *connman, ConnectionType::MANUAL, /*onion_peer=*/false, /*address=*/"127.0.0.1");
95+
BOOST_REQUIRE(nodes.back() != nullptr);
96+
}
97+
98+
// Call ConnectNode(), which is also called by RPC addnode onetry, for a localhost
99+
// address that resolves to multiple IPs, including that of the connected peer.
100+
// The connection attempt should consistently fail due to the check in ConnectNode().
101+
for (int i = 0; i < 10; ++i) {
102+
ASSERT_DEBUG_LOG("Not opening a connection to localhost, already connected to 127.0.0.1:8333");
103+
BOOST_CHECK(!connman->ConnectNodePublic(*peerman, "localhost", ConnectionType::MANUAL));
104+
}
105+
106+
// Add 3 more peer connections.
107+
AddPeer(id, nodes, *peerman, *connman, ConnectionType::OUTBOUND_FULL_RELAY);
108+
AddPeer(id, nodes, *peerman, *connman, ConnectionType::BLOCK_RELAY, /*onion_peer=*/true);
109+
AddPeer(id, nodes, *peerman, *connman, ConnectionType::INBOUND);
110+
111+
BOOST_TEST_MESSAGE("Call AddNode() for all the peers");
112+
for (auto node : connman->TestNodes()) {
113+
BOOST_CHECK(connman->AddNode({/*m_added_node=*/node->addr.ToStringAddrPort(), /*m_use_v2transport=*/true}));
114+
BOOST_TEST_MESSAGE(strprintf("peer id=%s addr=%s", node->GetId(), node->addr.ToStringAddrPort()));
115+
}
116+
117+
BOOST_TEST_MESSAGE("\nCall AddNode() with 2 addrs resolving to existing localhost addnode entry; neither should be added");
118+
BOOST_CHECK(!connman->AddNode({/*m_added_node=*/"127.0.0.1", /*m_use_v2transport=*/true}));
119+
BOOST_CHECK(!connman->AddNode({/*m_added_node=*/"127.1", /*m_use_v2transport=*/true}));
120+
121+
BOOST_TEST_MESSAGE("\nExpect GetAddedNodeInfo to return expected number of peers with `include_connected` true/false");
122+
BOOST_CHECK_EQUAL(connman->GetAddedNodeInfo(/*include_connected=*/true).size(), nodes.size());
123+
BOOST_CHECK(connman->GetAddedNodeInfo(/*include_connected=*/false).empty());
124+
125+
BOOST_TEST_MESSAGE("\nPrint GetAddedNodeInfo contents:");
126+
for (const auto& info : connman->GetAddedNodeInfo(/*include_connected=*/true)) {
127+
BOOST_TEST_MESSAGE(strprintf("\nadded node: %s", info.m_params.m_added_node));
128+
BOOST_TEST_MESSAGE(strprintf("connected: %s", info.fConnected));
129+
if (info.fConnected) {
130+
BOOST_TEST_MESSAGE(strprintf("IP address: %s", info.resolvedAddress.ToStringAddrPort()));
131+
BOOST_TEST_MESSAGE(strprintf("direction: %s", info.fInbound ? "inbound" : "outbound"));
132+
}
133+
}
134+
135+
BOOST_TEST_MESSAGE("\nCheck that all connected peers are correctly detected as connected");
136+
for (auto node : connman->TestNodes()) {
137+
BOOST_CHECK(connman->AlreadyConnectedPublic(node->addr));
138+
}
139+
140+
// Clean up
141+
for (auto node : connman->TestNodes()) {
142+
peerman->FinalizeNode(*node);
143+
}
144+
connman->ClearTestNodes();
145+
}
146+
147+
BOOST_AUTO_TEST_SUITE_END()

src/test/util/net.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44

55
#include <test/util/net.h>
66

7-
#include <chainparams.h>
8-
#include <node/eviction.h>
97
#include <net.h>
108
#include <net_processing.h>
9+
#include <netaddress.h>
1110
#include <netmessagemaker.h>
11+
#include <node/connection_types.h>
12+
#include <node/eviction.h>
13+
#include <protocol.h>
14+
#include <random.h>
15+
#include <serialize.h>
1216
#include <span.h>
1317

1418
#include <vector>
@@ -98,6 +102,17 @@ bool ConnmanTestMsg::ReceiveMsgFrom(CNode& node, CSerializedNetMsg&& ser_msg) co
98102
return complete;
99103
}
100104

105+
CNode* ConnmanTestMsg::ConnectNodePublic(PeerManager& peerman, const char* pszDest, ConnectionType conn_type)
106+
{
107+
CNode* node = ConnectNode(CAddress{}, pszDest, /*fCountFailure=*/false, conn_type, /*use_v2transport=*/true);
108+
if (!node) return nullptr;
109+
node->SetCommonVersion(PROTOCOL_VERSION);
110+
peerman.InitializeNode(*node, ServiceFlags(NODE_NETWORK | NODE_WITNESS));
111+
node->fSuccessfullyConnected = true;
112+
AddTestNode(*node);
113+
return node;
114+
}
115+
101116
std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(int n_candidates, FastRandomContext& random_context)
102117
{
103118
std::vector<NodeEvictionCandidate> candidates;

src/test/util/net.h

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,30 @@
66
#define BITCOIN_TEST_UTIL_NET_H
77

88
#include <compat/compat.h>
9-
#include <node/eviction.h>
10-
#include <netaddress.h>
119
#include <net.h>
10+
#include <net_permissions.h>
11+
#include <net_processing.h>
12+
#include <netaddress.h>
13+
#include <node/connection_types.h>
14+
#include <node/eviction.h>
15+
#include <sync.h>
1216
#include <util/sock.h>
1317

18+
#include <algorithm>
1419
#include <array>
1520
#include <cassert>
21+
#include <chrono>
22+
#include <cstdint>
1623
#include <cstring>
1724
#include <memory>
1825
#include <string>
26+
#include <unordered_map>
27+
#include <vector>
28+
29+
class FastRandomContext;
30+
31+
template <typename C>
32+
class Span;
1933

2034
struct ConnmanTestMsg : public CConnman {
2135
using CConnman::CConnman;
@@ -25,6 +39,12 @@ struct ConnmanTestMsg : public CConnman {
2539
m_peer_connect_timeout = timeout;
2640
}
2741

42+
std::vector<CNode*> TestNodes()
43+
{
44+
LOCK(m_nodes_mutex);
45+
return m_nodes;
46+
}
47+
2848
void AddTestNode(CNode& node)
2949
{
3050
LOCK(m_nodes_mutex);
@@ -56,6 +76,11 @@ struct ConnmanTestMsg : public CConnman {
5676

5777
bool ReceiveMsgFrom(CNode& node, CSerializedNetMsg&& ser_msg) const;
5878
void FlushSendBuffer(CNode& node) const;
79+
80+
bool AlreadyConnectedPublic(const CAddress& addr) { return AlreadyConnectedToAddress(addr); };
81+
82+
CNode* ConnectNodePublic(PeerManager& peerman, const char* pszDest, ConnectionType conn_type)
83+
EXCLUSIVE_LOCKS_REQUIRED(!m_unused_i2p_sessions_mutex);
5984
};
6085

6186
constexpr ServiceFlags ALL_SERVICE_FLAGS[]{

0 commit comments

Comments
 (0)