Skip to content

Commit 76d035c

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 76d035c

2 files changed

Lines changed: 86 additions & 6 deletions

File tree

protocols/mdns/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
## 0.49.0
22

3+
- Fix mDNS to only advertise listening addresses that match the interface IP.
4+
Previously, all listening addresses (including loopback and addresses from other interfaces)
5+
were sent in mDNS responses, causing dial failures when peers tried to connect via
6+
unreachable addresses.
7+
See [PR 6500](https://github.com/libp2p/rust-libp2p/pull/6500)
38
- Raise MSRV to 1.88.0.
49
See [PR 6273](https://github.com/libp2p/rust-libp2p/pull/6273).
510

protocols/mdns/src/behaviour/iface.rs

Lines changed: 81 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,25 @@ 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| addr_matches_interface(multiaddr, iface_ip))
291+
.cloned()
292+
.collect();
293+
279294
this.send_buffer.extend(build_query_response(
280295
query.query_id(),
281296
this.local_peer_id,
282-
this.listen_addresses
283-
.read()
284-
.unwrap_or_else(|e| e.into_inner())
285-
.iter(),
297+
relevant_addrs.iter(),
286298
this.ttl,
287299
));
288300
continue;
@@ -335,3 +347,66 @@ where
335347
}
336348
}
337349
}
350+
351+
/// Returns `true` if the first protocol component of `addr` is an IP address equal to
352+
/// `iface_ip`.
353+
///
354+
/// Used when answering mDNS queries to advertise only the listening addresses that are
355+
/// reachable on the interface the query arrived on, instead of every listening address.
356+
fn addr_matches_interface(addr: &Multiaddr, iface_ip: IpAddr) -> bool {
357+
let addr_ip = match addr.iter().next() {
358+
Some(Protocol::Ip4(v4)) => Some(IpAddr::V4(v4)),
359+
Some(Protocol::Ip6(v6)) => Some(IpAddr::V6(v6)),
360+
_ => None,
361+
};
362+
addr_ip == Some(iface_ip)
363+
}
364+
365+
#[cfg(test)]
366+
mod tests {
367+
use super::*;
368+
369+
#[test]
370+
fn filter_addresses_by_interface_ip() {
371+
let iface_ipv4: IpAddr = "192.168.1.100".parse().unwrap();
372+
let iface_ipv6: IpAddr = "fe80::1".parse().unwrap();
373+
374+
let addr_matching_v4: Multiaddr = "/ip4/192.168.1.100/tcp/1234".parse().unwrap();
375+
let addr_different_v4: Multiaddr = "/ip4/10.0.0.1/tcp/1234".parse().unwrap();
376+
let addr_loopback_v4: Multiaddr = "/ip4/127.0.0.1/tcp/1234".parse().unwrap();
377+
let addr_matching_v6: Multiaddr = "/ip6/fe80::1/tcp/1234".parse().unwrap();
378+
let addr_different_v6: Multiaddr = "/ip6/::1/tcp/1234".parse().unwrap();
379+
380+
// IPv4 interface
381+
assert!(
382+
addr_matches_interface(&addr_matching_v4, iface_ipv4),
383+
"Should match same IPv4"
384+
);
385+
assert!(
386+
!addr_matches_interface(&addr_different_v4, iface_ipv4),
387+
"Should not match different IPv4"
388+
);
389+
assert!(
390+
!addr_matches_interface(&addr_loopback_v4, iface_ipv4),
391+
"Should not match loopback when interface is not loopback"
392+
);
393+
assert!(
394+
!addr_matches_interface(&addr_matching_v6, iface_ipv4),
395+
"Should not match IPv6 on IPv4 interface"
396+
);
397+
398+
// IPv6 interface
399+
assert!(
400+
addr_matches_interface(&addr_matching_v6, iface_ipv6),
401+
"Should match same IPv6"
402+
);
403+
assert!(
404+
!addr_matches_interface(&addr_different_v6, iface_ipv6),
405+
"Should not match different IPv6"
406+
);
407+
assert!(
408+
!addr_matches_interface(&addr_matching_v4, iface_ipv6),
409+
"Should not match IPv4 on IPv6 interface"
410+
);
411+
}
412+
}

0 commit comments

Comments
 (0)