Skip to content

Commit eca1738

Browse files
committed
fix(network): prevent mDNS from overwriting valid peer addresses (TM-B1)
After network partition recovery, mDNS rediscovery was overwriting complete peer addresses (e.g., /ip4/x.x.x.x/tcp/10000) with incomplete ones (e.g., /ip4/x.x.x.x without port), causing "Multiaddr is not supported" errors during reconnection attempts. Changes: - Add `Ignored` variant to AlysNetworkBehaviourEvent for unhandled events - Filter mDNS addresses to only include dialable ones with transport protocol - Validate addresses in update_peer_address() before overwriting complete addresses with incomplete ones (defense-in-depth) - Handle Ignored events in network_actor event loop
1 parent 4655ef0 commit eca1738

File tree

3 files changed

+67
-34
lines changed

3 files changed

+67
-34
lines changed

app/src/actors_v2/network/behaviour.rs

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ pub enum AlysNetworkBehaviourEvent {
7070
MdnsPeerExpired { peer_id: String },
7171
/// Remote peer subscribed to a gossipsub topic (used for bidirectional mesh recovery)
7272
GossipPeerSubscribed { peer_id: String, topic: String },
73+
/// Internal event that should be ignored (placeholder for unhandled events)
74+
Ignored,
7375
}
7476

7577
impl AlysNetworkBehaviour {
@@ -199,12 +201,7 @@ impl From<libp2p::gossipsub::Event> for AlysNetworkBehaviourEvent {
199201
}
200202
_ => {
201203
tracing::trace!("Unhandled gossipsub event: {:?}", event);
202-
// For unhandled events, return a dummy event
203-
AlysNetworkBehaviourEvent::PeerIdentified {
204-
peer_id: String::new(),
205-
protocols: vec![],
206-
addresses: vec![],
207-
}
204+
AlysNetworkBehaviourEvent::Ignored
208205
}
209206
}
210207
}
@@ -222,11 +219,7 @@ impl From<libp2p::identify::Event> for AlysNetworkBehaviourEvent {
222219
}
223220
_ => {
224221
tracing::trace!("Unhandled identify event: {:?}", event);
225-
AlysNetworkBehaviourEvent::PeerIdentified {
226-
peer_id: String::new(),
227-
protocols: vec![],
228-
addresses: vec![],
229-
}
222+
AlysNetworkBehaviourEvent::Ignored
230223
}
231224
}
232225
}
@@ -236,32 +229,43 @@ impl From<libp2p::mdns::Event> for AlysNetworkBehaviourEvent {
236229
fn from(event: libp2p::mdns::Event) -> Self {
237230
match event {
238231
libp2p::mdns::Event::Discovered(peers) => {
239-
// Return first discovered peer (simplified)
240232
if let Some((peer_id, addresses)) = peers.into_iter().next() {
241-
AlysNetworkBehaviourEvent::MdnsPeerDiscovered {
242-
peer_id: peer_id.to_string(),
243-
addresses: addresses.iter().map(|a| a.to_string()).collect(),
233+
// Filter to only include complete multiaddrs with transport protocol
234+
// This prevents incomplete addresses (e.g., "/ip4/172.22.0.11" without port)
235+
// from being stored and causing reconnection failures
236+
let valid_addresses: Vec<String> = addresses
237+
.iter()
238+
.filter(|addr| {
239+
let addr_str = addr.to_string();
240+
addr_str.contains("/tcp/") || addr_str.contains("/udp/")
241+
})
242+
.map(|a| a.to_string())
243+
.collect();
244+
245+
if valid_addresses.is_empty() {
246+
tracing::debug!(
247+
peer_id = %peer_id,
248+
raw_addresses = ?addresses.iter().map(|a| a.to_string()).collect::<Vec<_>>(),
249+
"mDNS discovered peer but no valid transport addresses found"
250+
);
251+
AlysNetworkBehaviourEvent::Ignored
252+
} else {
253+
AlysNetworkBehaviourEvent::MdnsPeerDiscovered {
254+
peer_id: peer_id.to_string(),
255+
addresses: valid_addresses,
256+
}
244257
}
245258
} else {
246-
AlysNetworkBehaviourEvent::PeerIdentified {
247-
peer_id: String::new(),
248-
protocols: vec![],
249-
addresses: vec![],
250-
}
259+
AlysNetworkBehaviourEvent::Ignored
251260
}
252261
}
253262
libp2p::mdns::Event::Expired(peers) => {
254-
// Return first expired peer (simplified)
255263
if let Some((peer_id, _)) = peers.into_iter().next() {
256264
AlysNetworkBehaviourEvent::MdnsPeerExpired {
257265
peer_id: peer_id.to_string(),
258266
}
259267
} else {
260-
AlysNetworkBehaviourEvent::PeerIdentified {
261-
peer_id: String::new(),
262-
protocols: vec![],
263-
addresses: vec![],
264-
}
268+
AlysNetworkBehaviourEvent::Ignored
265269
}
266270
}
267271
}

app/src/actors_v2/network/managers/peer_manager.rs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,20 +253,45 @@ impl PeerManager {
253253
}
254254

255255
/// Update peer's address without resetting other fields
256-
/// This is called when PeerIdentified provides a potentially updated address
256+
/// This is called when PeerIdentified or mDNS provides a potentially updated address.
257+
///
258+
/// IMPORTANT: This method validates that the new address is complete (has transport protocol)
259+
/// before overwriting an existing complete address. This prevents mDNS from overwriting
260+
/// good addresses with incomplete ones (e.g., "/ip4/172.22.0.11" without port), which
261+
/// would cause reconnection failures after network partitions.
257262
pub fn update_peer_address(&mut self, peer_id: &PeerId, address: String) {
263+
// Validate that the new address is dialable (has transport protocol)
264+
// This prevents mDNS incomplete addresses from overwriting good addresses
265+
let is_new_address_complete = address.contains("/tcp/") || address.contains("/udp/");
266+
258267
// Update in connected_peers
259268
if let Some(peer_info) = self.connected_peers.get_mut(peer_id) {
260-
if peer_info.address != address {
269+
let is_existing_address_complete = peer_info.address.contains("/tcp/")
270+
|| peer_info.address.contains("/udp/");
271+
272+
// Only overwrite if:
273+
// 1. New address is complete, OR
274+
// 2. Existing address is also incomplete (nothing to lose)
275+
if is_new_address_complete || !is_existing_address_complete {
276+
if peer_info.address != address {
277+
tracing::debug!(
278+
peer_id = %peer_id,
279+
old_address = %peer_info.address,
280+
new_address = %address,
281+
"Updated peer address"
282+
);
283+
peer_info.address = address.clone();
284+
peer_info.last_seen = SystemTime::now();
285+
peer_info.last_activity = Instant::now();
286+
}
287+
} else {
261288
tracing::debug!(
262289
peer_id = %peer_id,
263-
old_address = %peer_info.address,
264-
new_address = %address,
265-
"Updated peer address"
290+
existing_address = %peer_info.address,
291+
rejected_address = %address,
292+
"Keeping complete address, rejecting incomplete update"
266293
);
267-
peer_info.address = address.clone();
268-
peer_info.last_seen = SystemTime::now();
269-
peer_info.last_activity = Instant::now();
294+
return; // Don't update known_peers either
270295
}
271296
}
272297

app/src/actors_v2/network/network_actor.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,6 +2034,10 @@ impl NetworkActor {
20342034
);
20352035
}
20362036
}
2037+
2038+
AlysNetworkBehaviourEvent::Ignored => {
2039+
// Silently ignore - no action needed for placeholder events
2040+
}
20372041
}
20382042

20392043
Ok(())

0 commit comments

Comments
 (0)