Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion protocols/mdns/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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).

Expand Down
33 changes: 28 additions & 5 deletions protocols/mdns/src/behaviour/iface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
}
}
8 changes: 4 additions & 4 deletions protocols/mdns/src/behaviour/iface/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = &'a Multiaddr>,
mut addresses: Vec<&Multiaddr>,
ttl: Duration,
) -> Vec<MdnsPacket> {
// 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);
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions protocols/mdns/src/behaviour/iface/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
);

Expand Down Expand Up @@ -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),
);

Expand Down
19 changes: 10 additions & 9 deletions protocols/mdns/tests/use-tokio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,19 @@ async fn create_swarm(config: Config) -> Swarm<Behaviour> {
});

// 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
}
Loading