p2p: first addr self-announcement in separate msg
What changed, and why it matters
This Bitcoin Core change tweaks how a node tells other nodes about its own internet address when they first connect. Previously, the node's own address could get bundled with other addresses in the same network message. Because new connections only get one temporary 'token' that lets a single address through, the node's own address could accidentally be dropped by the peer's rate limiter. The fix sends the first self-announcement in its own dedicated message so it isn't crowded out by other addresses. It also makes the initial address exchange cleaner for inbound connections.
No immediate action required beyond normal review and testing. This is a hardening/cleanup change. Operators and downstream maintainers should include it in the next release cycle.
Security signals we found
P2P address propagation reliability improvement
Rate-limiting token exhaustion avoidance for first self-announcement
Reduced chance of self-announcement being dropped on new peer connections
Clean separation of GETADDR response and self-announcement for inbound peers
Evidence from the diff
In net_processing.cpp, MaybeSendAddr now checks whether this is the first local address send (peer.m_next_local_addr_send == 0us). If so, it pushes the self-announcement as a standalone ADDR or ADDRV2 message via MakeAndPushMessage. Subsequent self-announcements continue to use PushAddress and are batched with other addresses. The functional test is updated to assert that the first self-announcement arrives in the first addr/addrv2 message and that this message contains exactly one address. For inbound connections, the expected message count increases from one to two because the self-announcement is now separate from the GETADDR response.
Changed components
src/net_processing.cpptest/functional/p2p_addr_selfannouncement.pyInspect captured patch +27 / −4
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 6ec55256..e5ead349 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -5328,7 +5328,20 @@ void PeerManagerImpl::MaybeSendAddr(CNode& node, Peer& peer, std::chrono::micros
}
if (std::optional<CService> local_service = GetLocalAddrForPeer(node)) {
CAddress local_addr{*local_service, peer.m_our_services, Now<NodeSeconds>()};
- PushAddress(peer, local_addr);
+ if (peer.m_next_local_addr_send == 0us) {
+ // Send the initial self-announcement in its own message. This makes sure
+ // rate-limiting with limited start-tokens doesn't ignore it if the first
+ // message ends up containing multiple addresses.
+ std::vector<CAddress> self_announcement {local_addr};
+ if (peer.m_wants_addrv2) {
+ MakeAndPushMessage(node, NetMsgType::ADDRV2, CAddress::V2_NETWORK(self_announcement));
+ } else {
+ MakeAndPushMessage(node, NetMsgType::ADDR, CAddress::V1_NETWORK(self_announcement));
+ }
+ } else {
+ // All later self-announcements are sent together with the other addresses.
+ PushAddress(peer, local_addr);
+ }
}
peer.m_next_local_addr_send = current_time + m_rng.rand_exp_duration(AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
}
diff --git a/test/functional/p2p_addr_selfannouncement.py b/test/functional/p2p_addr_selfannouncement.py
index 1fb91e0a..9c98e692 100755
--- a/test/functional/p2p_addr_selfannouncement.py
+++ b/test/functional/p2p_addr_selfannouncement.py
@@ -5,6 +5,9 @@
"""
Test that a node sends a self-announcement with its external IP to
in- and outbound peers after connection open and again sometime later.
+
+Additionally, this checks that the first self-announcement arrives
+in its own message and that this message is the first we receive.
"""
import time
@@ -42,6 +45,13 @@ class SelfAnnouncementReceiver(P2PInterface):
self.addresses_received += 1
if addr == self.expected:
self.self_announcements_received += 1
+ if self.self_announcements_received == 1:
+ # If it's the first self-announcement, check that it is
+ # in the first addr message we receive, and that this message
+ # only contains one address. This also implies that it is
+ # the first address we receive.
+ assert_equal(self.addr_messages_received, 1)
+ assert_equal(len(message.addrs), 1)
def on_addrv2(self, message):
assert (self.addrv2_test)
@@ -69,10 +79,10 @@ class AddrSelfAnnouncementTest(BitcoinTestFramework):
self.self_announcement_test(outbound=True, addrv2=True)
def inbound_connection_open_assertions(self, addr_receiver):
- # We expect one self-announcement and multiple other addresses in
- # response to a GETADDR in a single addr / addrv2 message.
+ # In response to a GETADDR, we expect a message with the self-announcement
+ # and an addr message containing the GETADDR response.
assert_equal(addr_receiver.self_announcements_received, 1)
- assert_equal(addr_receiver.addr_messages_received, 1)
+ assert_equal(addr_receiver.addr_messages_received, 2)
assert_greater_than(addr_receiver.addresses_received, 1)
def outbound_connection_open_assertions(self, addr_receiver):
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.