diff --git a/protocols/mdns/CHANGELOG.md b/protocols/mdns/CHANGELOG.md index c6bf70d2e10..955950a79ad 100644 --- a/protocols/mdns/CHANGELOG.md +++ b/protocols/mdns/CHANGELOG.md @@ -1,9 +1,13 @@ ## 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) - Skip address translation when the observed mDNS source is IPv6 link-local, keeping the announced routable address intact. See [PR 6479](https://github.com/libp2p/rust-libp2p/pull/6479). - - 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 8a38c90baac..dd08e930a48 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}; @@ -274,13 +274,23 @@ 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. + let iface_ip = this.addr; + let read = this + .listen_addresses + .read() + .unwrap_or_else(|e| e.into_inner()); + let relevant_addrs = read + .iter() + .filter(|multiaddr| addr_matches_interface(multiaddr, iface_ip)) + .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, this.ttl, )); continue; @@ -333,3 +343,16 @@ 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 { + match addr.iter().next() { + Some(Protocol::Ip4(v4)) => IpAddr::V4(v4) == iface_ip, + Some(Protocol::Ip6(v6)) => IpAddr::V6(v6) == iface_ip, + _ => false, + } +} diff --git a/protocols/mdns/src/behaviour/iface/dns.rs b/protocols/mdns/src/behaviour/iface/dns.rs index 990cc4287ac..1ae5f632de3 100644 --- a/protocols/mdns/src/behaviour/iface/dns.rs +++ b/protocols/mdns/src/behaviour/iface/dns.rs @@ -104,17 +104,17 @@ pub(crate) fn build_query() -> MdnsPacket { /// Builds the response to an address discovery DNS query. /// /// If there are more than 2^16-1 addresses, ignores the rest. -pub(crate) fn build_query_response<'a>( +pub(crate) fn build_query_response( id: u16, peer_id: PeerId, - addresses: impl ExactSizeIterator, + mut addresses: Vec<&Multiaddr>, ttl: Duration, ) -> Vec { // Convert the TTL into seconds. let ttl = duration_to_secs(ttl); // Add a limit to 2^16-1 addresses, as the protocol limits to this number. - let addresses = addresses.take(65535); + addresses.truncate(65535); let peer_name_bytes = generate_peer_name(); debug_assert!(peer_name_bytes.len() <= 0xffff); @@ -413,7 +413,7 @@ mod tests { let packets = build_query_response( 0xf8f8, my_peer_id, - vec![&addr1, &addr2].into_iter(), + vec![&addr1, &addr2], Duration::from_secs(60), ); for packet in packets { diff --git a/protocols/mdns/src/behaviour/iface/query.rs b/protocols/mdns/src/behaviour/iface/query.rs index 645dcd1510f..28086427c8b 100644 --- a/protocols/mdns/src/behaviour/iface/query.rs +++ b/protocols/mdns/src/behaviour/iface/query.rs @@ -344,7 +344,7 @@ mod tests { let packets = build_query_response( 0xf8f8, peer_id, - vec![&addr1, &addr2].into_iter(), + vec![&addr1, &addr2], Duration::from_secs(60), ); @@ -403,7 +403,7 @@ mod tests_ipv6 { let packets = build_query_response( 0x1234, peer_id, - vec![&announced_addr].into_iter(), + vec![&announced_addr], Duration::from_secs(300), ); diff --git a/protocols/mdns/tests/use-tokio.rs b/protocols/mdns/tests/use-tokio.rs index 6805cae5a2e..240f5cfb21f 100644 --- a/protocols/mdns/tests/use-tokio.rs +++ b/protocols/mdns/tests/use-tokio.rs @@ -115,18 +115,19 @@ async fn create_swarm(config: Config) -> Swarm { }); // Manually listen on all interfaces because mDNS only works for non-loopback addresses. - let expected_listener_id = swarm + let expected_listener_id_ip4 = swarm .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap()) .unwrap(); + let expected_listener_id_ip6 = swarm.listen_on("/ip6/::/tcp/0".parse().unwrap()).unwrap(); - swarm - .wait(|e| match e { - SwarmEvent::NewListenAddr { listener_id, .. } => { - (listener_id == expected_listener_id).then_some(()) - } - _ => None, - }) - .await; + let mut listen_both = false; + + while !listen_both { + if let SwarmEvent::NewListenAddr { listener_id, .. } = swarm.next_swarm_event().await { + listen_both |= listener_id == expected_listener_id_ip4; + listen_both |= listener_id == expected_listener_id_ip6; + } + } swarm }