diff --git a/protocols/mdns/CHANGELOG.md b/protocols/mdns/CHANGELOG.md index cc260a06a58..0ef9aec5818 100644 --- a/protocols/mdns/CHANGELOG.md +++ b/protocols/mdns/CHANGELOG.md @@ -1,5 +1,10 @@ ## 0.49.0 +- Fix mDNS to only advertise listening addresses that match the interface IP. + Previously, all listening addresses (including loopback and addresses from other interfaces) + were sent in mDNS responses, causing dial failures when peers tried to connect via + unreachable addresses. + See [PR 6500](https://github.com/libp2p/rust-libp2p/pull/6500) - Raise MSRV to 1.88.0. See [PR 6273](https://github.com/libp2p/rust-libp2p/pull/6273). diff --git a/protocols/mdns/src/behaviour/iface.rs b/protocols/mdns/src/behaviour/iface.rs index 7ddef6c911c..660a35da010 100644 --- a/protocols/mdns/src/behaviour/iface.rs +++ b/protocols/mdns/src/behaviour/iface.rs @@ -33,7 +33,7 @@ use std::{ }; use futures::{SinkExt, StreamExt, channel::mpsc}; -use libp2p_core::Multiaddr; +use libp2p_core::{Multiaddr, multiaddr::Protocol}; use libp2p_identity::PeerId; use libp2p_swarm::ListenAddresses; use socket2::{Domain, Socket, Type}; @@ -276,13 +276,25 @@ where "received query from remote address on address" ); + // Only send addresses that belong to this interface. + // This prevents advertising loopback or other interface addresses + // to peers that can't reach them. + // Note: We collect into a Vec because build_query_response requires + // an ExactSizeIterator, which Filter doesn't implement. + let iface_ip = this.addr; + let relevant_addrs: Vec<_> = this + .listen_addresses + .read() + .unwrap_or_else(|e| e.into_inner()) + .iter() + .filter(|multiaddr| addr_matches_interface(multiaddr, iface_ip)) + .cloned() + .collect(); + this.send_buffer.extend(build_query_response( query.query_id(), this.local_peer_id, - this.listen_addresses - .read() - .unwrap_or_else(|e| e.into_inner()) - .iter(), + relevant_addrs.iter(), this.ttl, )); continue; @@ -335,3 +347,66 @@ where } } } + +/// Returns `true` if the first protocol component of `addr` is an IP address equal to +/// `iface_ip`. +/// +/// Used when answering mDNS queries to advertise only the listening addresses that are +/// reachable on the interface the query arrived on, instead of every listening address. +fn addr_matches_interface(addr: &Multiaddr, iface_ip: IpAddr) -> bool { + let addr_ip = match addr.iter().next() { + Some(Protocol::Ip4(v4)) => Some(IpAddr::V4(v4)), + Some(Protocol::Ip6(v6)) => Some(IpAddr::V6(v6)), + _ => None, + }; + addr_ip == Some(iface_ip) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn filter_addresses_by_interface_ip() { + let iface_ipv4: IpAddr = "192.168.1.100".parse().unwrap(); + let iface_ipv6: IpAddr = "fe80::1".parse().unwrap(); + + let addr_matching_v4: Multiaddr = "/ip4/192.168.1.100/tcp/1234".parse().unwrap(); + let addr_different_v4: Multiaddr = "/ip4/10.0.0.1/tcp/1234".parse().unwrap(); + let addr_loopback_v4: Multiaddr = "/ip4/127.0.0.1/tcp/1234".parse().unwrap(); + let addr_matching_v6: Multiaddr = "/ip6/fe80::1/tcp/1234".parse().unwrap(); + let addr_different_v6: Multiaddr = "/ip6/::1/tcp/1234".parse().unwrap(); + + // IPv4 interface + assert!( + addr_matches_interface(&addr_matching_v4, iface_ipv4), + "Should match same IPv4" + ); + assert!( + !addr_matches_interface(&addr_different_v4, iface_ipv4), + "Should not match different IPv4" + ); + assert!( + !addr_matches_interface(&addr_loopback_v4, iface_ipv4), + "Should not match loopback when interface is not loopback" + ); + assert!( + !addr_matches_interface(&addr_matching_v6, iface_ipv4), + "Should not match IPv6 on IPv4 interface" + ); + + // IPv6 interface + assert!( + addr_matches_interface(&addr_matching_v6, iface_ipv6), + "Should match same IPv6" + ); + assert!( + !addr_matches_interface(&addr_different_v6, iface_ipv6), + "Should not match different IPv6" + ); + assert!( + !addr_matches_interface(&addr_matching_v4, iface_ipv6), + "Should not match IPv4 on IPv6 interface" + ); + } +}