Skip to content

Commit 35dddbc

Browse files
committed
Merge bitcoin/bitcoin#30394: net: fix race condition in self-connect detection
16bd283 Reapply "test: p2p: check that connecting to ourself leads to disconnect" (Sebastian Falbesoner) 0dbcd4c net: prevent sending messages in `NetEventsInterface::InitializeNode` (Sebastian Falbesoner) 66673f1 net: fix race condition in self-connect detection (Sebastian Falbesoner) Pull request description: This PR fixes a recently discovered race condition in the self-connect detection (see #30362 and #30368). Initiating an outbound network connection currently involves the following steps after the socket connection is established (see [`CConnman::OpenNetworkConnection`](https://github.com/bitcoin/bitcoin/blob/bd5d1688b4311e21c0e0ff89a3ae02ef7d0543b8/src/net.cpp#L2923-L2930) method): 1. set up node state 2. queue VERSION message (both steps 1 and 2 happen in [`InitializeNode`](https://github.com/bitcoin/bitcoin/blob/bd5d1688b4311e21c0e0ff89a3ae02ef7d0543b8/src/net_processing.cpp#L1662-L1683)) 3. add new node to vector `m_nodes` If we connect to ourself, it can happen that the sent VERSION message (step 2) is received and processed locally *before* the node object is added to the connection manager's `m_nodes` vector (step 3). In this case, the self-connect remains undiscovered, as the detection doesn't find the outbound peer in `m_nodes` yet (see `CConnman::CheckIncomingNonce`). Fix this by swapping the order of 2. and 3., by taking the `PushNodeVersion` call out of `InitializeNode` and doing that in the `SendMessages` method instead, which is only called for `CNode` instances in `m_nodes`. The temporarily reverted test introduced in #30362 is readded. Fixes #30368. Thanks go to vasild, mzumsande and dergoegge for suggestions on how to fix this (see bitcoin/bitcoin#30368 (comment) ff. and bitcoin/bitcoin#30394 (comment)). ACKs for top commit: naiyoma: tested ACK [https://github.com/bitcoin/bitcoin/pull/30394/commits/16bd283b3ad05daa41259a062aee0fc05b463fa6](https://github.com/bitcoin/bitcoin/pull/30394/commits/16bd283b3ad05daa41259a062aee0fc05b463fa6), built and tested locally, test passes successfully. mzumsande: ACK 16bd283 tdb3: ACK 16bd283 glozow: ACK 16bd283 dergoegge: ACK 16bd283 Tree-SHA512: 5b8aced6cda8deb38d4cd3fe4980b8af505d37ffa0925afaa734c5d81efe9d490dc48a42e1d0d45dd2961c0e1172a3d5b6582ae9a2d642f2592a17fbdc184445
2 parents d41f4a6 + 16bd283 commit 35dddbc

File tree

4 files changed

+25
-11
lines changed

4 files changed

+25
-11
lines changed

src/net.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -991,8 +991,8 @@ class NetEventsInterface
991991
/** Mutex for anything that is only accessed via the msg processing thread */
992992
static Mutex g_msgproc_mutex;
993993

994-
/** Initialize a peer (setup state, queue any initial messages) */
995-
virtual void InitializeNode(CNode& node, ServiceFlags our_services) = 0;
994+
/** Initialize a peer (setup state) */
995+
virtual void InitializeNode(const CNode& node, ServiceFlags our_services) = 0;
996996

997997
/** Handle removal of a peer (clear state) */
998998
virtual void FinalizeNode(const CNode& node) = 0;

src/net_processing.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ struct Peer {
243243
* Most peers use headers-first syncing, which doesn't use this mechanism */
244244
uint256 m_continuation_block GUARDED_BY(m_block_inv_mutex) {};
245245

246+
/** Set to true once initial VERSION message was sent (only relevant for outbound peers). */
247+
bool m_outbound_version_message_sent GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false};
248+
246249
/** This peer's reported block height when we connected */
247250
std::atomic<int> m_starting_height{-1};
248251

@@ -498,7 +501,7 @@ class PeerManagerImpl final : public PeerManager
498501
EXCLUSIVE_LOCKS_REQUIRED(!m_most_recent_block_mutex);
499502

500503
/** Implement NetEventsInterface */
501-
void InitializeNode(CNode& node, ServiceFlags our_services) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
504+
void InitializeNode(const CNode& node, ServiceFlags our_services) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
502505
void FinalizeNode(const CNode& node) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_headers_presync_mutex);
503506
bool HasAllDesirableServiceFlags(ServiceFlags services) const override;
504507
bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override
@@ -1659,7 +1662,7 @@ void PeerManagerImpl::UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_s
16591662
if (state) state->m_last_block_announcement = time_in_seconds;
16601663
}
16611664

1662-
void PeerManagerImpl::InitializeNode(CNode& node, ServiceFlags our_services)
1665+
void PeerManagerImpl::InitializeNode(const CNode& node, ServiceFlags our_services)
16631666
{
16641667
NodeId nodeid = node.GetId();
16651668
{
@@ -1677,9 +1680,6 @@ void PeerManagerImpl::InitializeNode(CNode& node, ServiceFlags our_services)
16771680
LOCK(m_peer_mutex);
16781681
m_peer_map.emplace_hint(m_peer_map.end(), nodeid, peer);
16791682
}
1680-
if (!node.IsInboundConn()) {
1681-
PushNodeVersion(node, *peer);
1682-
}
16831683
}
16841684

16851685
void PeerManagerImpl::ReattemptInitialBroadcast(CScheduler& scheduler)
@@ -5326,6 +5326,10 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
53265326
PeerRef peer = GetPeerRef(pfrom->GetId());
53275327
if (peer == nullptr) return false;
53285328

5329+
// For outbound connections, ensure that the initial VERSION message
5330+
// has been sent first before processing any incoming messages
5331+
if (!pfrom->IsInboundConn() && !peer->m_outbound_version_message_sent) return false;
5332+
53295333
{
53305334
LOCK(peer->m_getdata_requests_mutex);
53315335
if (!peer->m_getdata_requests.empty()) {
@@ -5817,6 +5821,12 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
58175821
// disconnect misbehaving peers even before the version handshake is complete.
58185822
if (MaybeDiscourageAndDisconnect(*pto, *peer)) return true;
58195823

5824+
// Initiate version handshake for outbound connections
5825+
if (!pto->IsInboundConn() && !peer->m_outbound_version_message_sent) {
5826+
PushNodeVersion(*pto, *peer);
5827+
peer->m_outbound_version_message_sent = true;
5828+
}
5829+
58205830
// Don't send anything until the version handshake is complete
58215831
if (!pto->fSuccessfullyConnected || pto->fDisconnect)
58225832
return true;

src/test/util/net.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ void ConnmanTestMsg::Handshake(CNode& node,
2828
auto& connman{*this};
2929

3030
peerman.InitializeNode(node, local_services);
31-
FlushSendBuffer(node); // Drop the version message added by InitializeNode.
31+
peerman.SendMessages(&node);
32+
FlushSendBuffer(node); // Drop the version message added by SendMessages.
3233

3334
CSerializedNetMsg msg_version{
3435
NetMsg::Make(NetMsgType::VERSION,

test/functional/p2p_handshake.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
NODE_WITNESS,
1818
)
1919
from test_framework.p2p import P2PInterface
20+
from test_framework.util import p2p_port
2021

2122

2223
# Desirable service flags for outbound non-pruned and pruned peers. Note that
@@ -88,9 +89,11 @@ def run_test(self):
8889
with node.assert_debug_log([f"feeler connection completed"]):
8990
self.add_outbound_connection(node, "feeler", NODE_NONE, wait_for_disconnect=True)
9091

91-
# TODO: re-add test introduced in commit 5d2fb14bafe4e80c0a482d99e5ebde07c477f000
92-
# ("test: p2p: check that connecting to ourself leads to disconnect") once
93-
# the race condition causing issue #30368 is fixed
92+
self.log.info("Check that connecting to ourself leads to immediate disconnect")
93+
with node.assert_debug_log(["connected to self", "disconnecting"]):
94+
node_listen_addr = f"127.0.0.1:{p2p_port(0)}"
95+
node.addconnection(node_listen_addr, "outbound-full-relay", self.options.v2transport)
96+
self.wait_until(lambda: len(node.getpeerinfo()) == 0)
9497

9598

9699
if __name__ == '__main__':

0 commit comments

Comments
 (0)