Skip to content

Commit 6d44f36

Browse files
fanquakePastaPastaPasta
authored andcommitted
Merge bitcoin#22096: p2p: AddrFetch - don't disconnect on self-announcements
5730a43 test: Add functional test for AddrFetch connections (Martin Zumsande) c34ad33 net, rpc: Enable AddrFetch connections for functional testing (Martin Zumsande) 533500d p2p: Add timeout for AddrFetch peers (Martin Zumsande) b6c5d1e p2p: AddrFetch - don't disconnect on self-announcements (Martin Zumsande) Pull request description: AddrFetch connections (old name: oneshots) are intended to be short-lived connections on which we ask a peer for addresses via `getaddr` and disconnect after receiving them. This is done by disconnecting after receiving the first `addr`. However, it is no longer working as intended, because nowadays, the first `addr` a typical bitcoin core node sends is its self-announcement. So we'll disconnect before the peer gets a chance to answer our `getaddr`. I checked that this affects both `-seednode` peers specified manually, and DNS seeds when AddrFetch is used as a fallback if DNS doesn't work for us. The current behavior of getting peers via AddrFetch when starting with an empty addrman would be to connect to the peer, receive its self-announcement and add it to addrman, disconnect, reconnect to the same peer again as a full outbound (no other addresses in addrman) and then receive more `addr`. This is silly and not in line with AddrFetch peer being intended to be short-lived peers.  Fix this by only disconnecting after receiving an `addr` message of size > 1. [Edit] As per review discussion, this PR now also adds a timeout after which we disconnect if we haven't received any suitable `addr`, and a functional test. ACKs for top commit: amitiuttarwar: reACK 5730a43 naumenkogs: ACK 5730a43 jnewbery: ACK 5730a43 Tree-SHA512: 8a81234f37e827705138eb254223f7f3b3bf44a06cb02126fc7990b0d231b9bd8f07d38d185cc30d55bf35548a6fdc286b69602498d875b937e7c58332158bf9
1 parent e4c848b commit 6d44f36

File tree

7 files changed

+95
-9
lines changed

7 files changed

+95
-9
lines changed

src/net.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,16 +1296,29 @@ void CConnman::CreateNodeFromAcceptedSocket(SOCKET hSocket,
12961296

12971297
bool CConnman::AddConnection(const std::string& address, ConnectionType conn_type)
12981298
{
1299-
if (conn_type != ConnectionType::OUTBOUND_FULL_RELAY && conn_type != ConnectionType::BLOCK_RELAY) return false;
1300-
1301-
const int max_connections = conn_type == ConnectionType::OUTBOUND_FULL_RELAY ? m_max_outbound_full_relay : m_max_outbound_block_relay;
1299+
std::optional<int> max_connections;
1300+
switch (conn_type) {
1301+
case ConnectionType::INBOUND:
1302+
case ConnectionType::MANUAL:
1303+
case ConnectionType::FEELER:
1304+
return false;
1305+
case ConnectionType::OUTBOUND_FULL_RELAY:
1306+
max_connections = m_max_outbound_full_relay;
1307+
break;
1308+
case ConnectionType::BLOCK_RELAY:
1309+
max_connections = m_max_outbound_block_relay;
1310+
break;
1311+
// no limit for ADDR_FETCH because -seednode has no limit either
1312+
case ConnectionType::ADDR_FETCH:
1313+
break;
1314+
} // no default case, so the compiler can warn about missing cases
13021315

13031316
// Count existing connections
13041317
int existing_connections = WITH_LOCK(m_nodes_mutex,
13051318
return std::count_if(m_nodes.begin(), m_nodes.end(), [conn_type](CNode* node) { return node->m_conn_type == conn_type; }););
13061319

13071320
// Max connections of specified type already exist
1308-
if (existing_connections >= max_connections) return false;
1321+
if (max_connections != std::nullopt && existing_connections >= max_connections) return false;
13091322

13101323
// Max total outbound connections already exist
13111324
CSemaphoreGrant grant(*semOutbound, true);

src/net.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,7 @@ friend class CNode;
11061106
*
11071107
* @param[in] address Address of node to try connecting to
11081108
* @param[in] conn_type ConnectionType::OUTBOUND or ConnectionType::BLOCK_RELAY
1109+
* or ConnectionType::ADDR_FETCH
11091110
* @return bool Returns false if there are no available
11101111
* slots for this connection:
11111112
* - conn_type not a supported ConnectionType

src/net_processing.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3657,7 +3657,9 @@ void PeerManagerImpl::ProcessMessage(
36573657

36583658
m_addrman.Add(vAddrOk, pfrom.addr, 2 * 60 * 60);
36593659
if (vAddr.size() < 1000) peer->m_getaddr_sent = false;
3660-
if (pfrom.IsAddrFetchConn()) {
3660+
3661+
// AddrFetch: Require multiple addresses to avoid disconnecting on self-announcements
3662+
if (pfrom.IsAddrFetchConn() && vAddr.size() > 1) {
36613663
LogPrint(BCLog::NET_NETCONN, "addrfetch connection completed peer=%d; disconnecting\n", pfrom.GetId());
36623664
pfrom.fDisconnect = true;
36633665
}
@@ -5351,6 +5353,12 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
53515353

53525354
const auto current_time = GetTime<std::chrono::microseconds>();
53535355

5356+
if (pto->IsAddrFetchConn() && current_time - std::chrono::seconds(pto->nTimeConnected) > 10 * AVG_ADDRESS_BROADCAST_INTERVAL) {
5357+
LogPrint(BCLog::NET_NETCONN, "addrfetch connection timeout; disconnecting peer=%d\n", pto->GetId());
5358+
pto->fDisconnect = true;
5359+
return true;
5360+
}
5361+
53545362
MaybeSendPing(*pto, *peer, current_time);
53555363

53565364
// MaybeSendPing may have marked peer for disconnection

src/rpc/net.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ static RPCHelpMan addconnection()
352352
"\nOpen an outbound connection to a specified node. This RPC is for testing only.\n",
353353
{
354354
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address and port to attempt connecting to."},
355-
{"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of connection to open, either \"outbound-full-relay\" or \"block-relay-only\"."},
355+
{"connection_type", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of connection to open (\"outbound-full-relay\", \"block-relay-only\" or \"addr-fetch\")."},
356356
},
357357
RPCResult{
358358
RPCResult::Type::OBJ, "", "",
@@ -378,6 +378,8 @@ static RPCHelpMan addconnection()
378378
conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
379379
} else if (conn_type_in == "block-relay-only") {
380380
conn_type = ConnectionType::BLOCK_RELAY;
381+
} else if (conn_type_in == "addr-fetch") {
382+
conn_type = ConnectionType::ADDR_FETCH;
381383
} else {
382384
throw JSONRPCError(RPC_INVALID_PARAMETER, self.ToString());
383385
}

test/functional/p2p_addrfetch.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2021 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""
6+
Test p2p addr-fetch connections
7+
"""
8+
9+
import time
10+
11+
from test_framework.messages import msg_addr, CAddress, NODE_NETWORK
12+
from test_framework.p2p import P2PInterface, p2p_lock
13+
from test_framework.test_framework import BitcoinTestFramework
14+
from test_framework.util import assert_equal
15+
16+
ADDR = CAddress()
17+
ADDR.time = int(time.time())
18+
ADDR.nServices = NODE_NETWORK
19+
ADDR.ip = "192.0.0.8"
20+
ADDR.port = 18444
21+
22+
23+
class P2PAddrFetch(BitcoinTestFramework):
24+
25+
def set_test_params(self):
26+
self.setup_clean_chain = True
27+
self.num_nodes = 1
28+
29+
def run_test(self):
30+
node = self.nodes[0]
31+
self.log.info("Connect to an addr-fetch peer")
32+
peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="addr-fetch")
33+
info = node.getpeerinfo()
34+
assert_equal(len(info), 1)
35+
assert_equal(info[0]['connection_type'], 'addr-fetch')
36+
37+
self.log.info("Check that we send getaddr but don't try to sync headers with the addr-fetch peer")
38+
peer.sync_send_with_ping()
39+
with p2p_lock:
40+
assert peer.message_count['getaddr'] == 1
41+
assert peer.message_count['getheaders'] == 0
42+
43+
self.log.info("Check that answering the getaddr with a single address does not lead to disconnect")
44+
# This prevents disconnecting on self-announcements
45+
msg = msg_addr()
46+
msg.addrs = [ADDR]
47+
peer.send_and_ping(msg)
48+
assert_equal(len(node.getpeerinfo()), 1)
49+
50+
self.log.info("Check that answering with larger addr messages leads to disconnect")
51+
msg.addrs = [ADDR] * 2
52+
peer.send_message(msg)
53+
peer.wait_for_disconnect(timeout=5)
54+
55+
self.log.info("Check timeout for addr-fetch peer that does not send addrs")
56+
peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="addr-fetch")
57+
node.setmocktime(int(time.time()) + 301) # Timeout: 5 minutes
58+
peer.wait_for_disconnect(timeout=5)
59+
60+
61+
if __name__ == '__main__':
62+
P2PAddrFetch().main()

test/functional/test_framework/test_node.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -571,9 +571,8 @@ def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, **kwargs):
571571
return p2p_conn
572572

573573
def add_outbound_p2p_connection(self, p2p_conn, *, p2p_idx, connection_type="outbound-full-relay", **kwargs):
574-
"""Add an outbound p2p connection from node. Either
575-
full-relay("outbound-full-relay") or
576-
block-relay-only("block-relay-only") connection.
574+
"""Add an outbound p2p connection from node. Must be an
575+
"outbound-full-relay", "block-relay-only" or "addr-fetch" connection.
577576
578577
This method adds the p2p connection to the self.p2ps list and returns
579578
the connection to the caller.

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@
207207
'p2p_addr_relay.py',
208208
'p2p_getaddr_caching.py',
209209
'p2p_getdata.py',
210+
'p2p_addrfetch.py',
210211
'rpc_net.py',
211212
'wallet_keypool.py --legacy-wallet',
212213
'wallet_keypool_hd.py --legacy-wallet',

0 commit comments

Comments
 (0)