Skip to content

Commit a5db29d

Browse files
committed
fix(mdns): only advertise listening addresses matching the interface IP
When responding to mDNS queries, only advertise listening addresses whose IP matches the address family / interface the query arrived on, instead of advertising every listening address. Avoids announcing unreachable addresses.
1 parent a714395 commit a5db29d

2 files changed

Lines changed: 91 additions & 6 deletions

File tree

protocols/mdns/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
See [PR 5753](https://github.com/libp2p/rust-libp2p/pull/5753)
1616
- Upgrade `hickory-proto`.
1717
See [PR 5727](https://github.com/libp2p/rust-libp2p/pull/5727)
18+
- Fix mDNS to only advertise listening addresses that match the interface IP.
19+
Previously, all listening addresses (including loopback and addresses from other interfaces)
20+
were sent in mDNS responses, causing dial failures when peers tried to connect via
21+
unreachable addresses.
1822

1923
<!-- Update to libp2p-core v0.43.0 -->
2024

protocols/mdns/src/behaviour/iface.rs

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ use std::{
3232
time::{Duration, Instant},
3333
};
3434

35-
use futures::{SinkExt, StreamExt, channel::mpsc};
36-
use libp2p_core::Multiaddr;
35+
use futures::{channel::mpsc, SinkExt, StreamExt};
36+
use libp2p_core::{multiaddr::Protocol, Multiaddr};
3737
use libp2p_identity::PeerId;
3838
use libp2p_swarm::ListenAddresses;
3939
use socket2::{Domain, Socket, Type};
@@ -276,13 +276,33 @@ where
276276
"received query from remote address on address"
277277
);
278278

279+
// Only send addresses that belong to this interface.
280+
// This prevents advertising loopback or other interface addresses
281+
// to peers that can't reach them.
282+
// Note: We collect into a Vec because build_query_response requires
283+
// an ExactSizeIterator, which Filter doesn't implement.
284+
let iface_ip = this.addr;
285+
let relevant_addrs: Vec<_> = this
286+
.listen_addresses
287+
.read()
288+
.unwrap_or_else(|e| e.into_inner())
289+
.iter()
290+
.filter(|multiaddr| {
291+
let first_component = multiaddr.iter().next();
292+
let addr_ip = match first_component {
293+
Some(Protocol::Ip4(v4)) => Some(IpAddr::V4(v4)),
294+
Some(Protocol::Ip6(v6)) => Some(IpAddr::V6(v6)),
295+
_ => None,
296+
};
297+
addr_ip == Some(iface_ip)
298+
})
299+
.cloned()
300+
.collect();
301+
279302
this.send_buffer.extend(build_query_response(
280303
query.query_id(),
281304
this.local_peer_id,
282-
this.listen_addresses
283-
.read()
284-
.unwrap_or_else(|e| e.into_inner())
285-
.iter(),
305+
relevant_addrs.iter(),
286306
this.ttl,
287307
));
288308
continue;
@@ -335,3 +355,64 @@ where
335355
}
336356
}
337357
}
358+
359+
#[cfg(test)]
360+
mod tests {
361+
use super::*;
362+
363+
#[test]
364+
fn filter_addresses_by_interface_ip() {
365+
// Test that addresses are correctly filtered by interface IP
366+
let iface_ipv4: IpAddr = "192.168.1.100".parse().unwrap();
367+
let iface_ipv6: IpAddr = "fe80::1".parse().unwrap();
368+
369+
let addr_matching_v4: Multiaddr = "/ip4/192.168.1.100/tcp/1234".parse().unwrap();
370+
let addr_different_v4: Multiaddr = "/ip4/10.0.0.1/tcp/1234".parse().unwrap();
371+
let addr_loopback_v4: Multiaddr = "/ip4/127.0.0.1/tcp/1234".parse().unwrap();
372+
let addr_matching_v6: Multiaddr = "/ip6/fe80::1/tcp/1234".parse().unwrap();
373+
let addr_different_v6: Multiaddr = "/ip6/::1/tcp/1234".parse().unwrap();
374+
375+
// Helper closure that mimics the filtering logic in poll
376+
let filter_fn = |multiaddr: &Multiaddr, iface_ip: IpAddr| {
377+
let first_component = multiaddr.iter().next();
378+
let addr_ip = match first_component {
379+
Some(Protocol::Ip4(v4)) => Some(IpAddr::V4(v4)),
380+
Some(Protocol::Ip6(v6)) => Some(IpAddr::V6(v6)),
381+
_ => None,
382+
};
383+
addr_ip == Some(iface_ip)
384+
};
385+
386+
// IPv4 interface tests
387+
assert!(
388+
filter_fn(&addr_matching_v4, iface_ipv4),
389+
"Should match same IPv4"
390+
);
391+
assert!(
392+
!filter_fn(&addr_different_v4, iface_ipv4),
393+
"Should not match different IPv4"
394+
);
395+
assert!(
396+
!filter_fn(&addr_loopback_v4, iface_ipv4),
397+
"Should not match loopback when interface is not loopback"
398+
);
399+
assert!(
400+
!filter_fn(&addr_matching_v6, iface_ipv4),
401+
"Should not match IPv6 on IPv4 interface"
402+
);
403+
404+
// IPv6 interface tests
405+
assert!(
406+
filter_fn(&addr_matching_v6, iface_ipv6),
407+
"Should match same IPv6"
408+
);
409+
assert!(
410+
!filter_fn(&addr_different_v6, iface_ipv6),
411+
"Should not match different IPv6"
412+
);
413+
assert!(
414+
!filter_fn(&addr_matching_v4, iface_ipv6),
415+
"Should not match IPv4 on IPv6 interface"
416+
);
417+
}
418+
}

0 commit comments

Comments
 (0)