diff --git a/protocols/mdns/CHANGELOG.md b/protocols/mdns/CHANGELOG.md index b631ac94..899222c6 100644 --- a/protocols/mdns/CHANGELOG.md +++ b/protocols/mdns/CHANGELOG.md @@ -2,6 +2,12 @@ - Update dependencies. +- Add support for IPv6. To enable set the multicast address + in `MdnsConfig` to `IPV6_MDNS_MULTICAST_ADDRESS`. + See [PR 2161] for details. + +[PR 2161]: https://github.com/libp2p/rust-libp2p/pull/2161/ + # 0.31.0 [2021-07-12] - Update dependencies. diff --git a/protocols/mdns/Cargo.toml b/protocols/mdns/Cargo.toml index 35015044..64cf9263 100644 --- a/protocols/mdns/Cargo.toml +++ b/protocols/mdns/Cargo.toml @@ -25,6 +25,6 @@ socket2 = { version = "0.4.0", features = ["all"] } void = "1.0.2" [dev-dependencies] -async-std = "1.9.0" -if-addrs = "0.6.5" -tokio = { version = "1.2.0", default-features = false, features = ["rt", "rt-multi-thread"] } +async-std = { version = "1.9.0", features = ["attributes"] } +libp2p = { path = "../.." } +tokio = { version = "1.2.0", default-features = false, features = ["macros", "rt", "rt-multi-thread"] } diff --git a/protocols/mdns/src/behaviour.rs b/protocols/mdns/src/behaviour.rs index 2e88d720..7348227c 100644 --- a/protocols/mdns/src/behaviour.rs +++ b/protocols/mdns/src/behaviour.rs @@ -18,12 +18,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +use crate::IPV4_MDNS_MULTICAST_ADDRESS; use crate::dns::{build_query, build_query_response, build_service_discovery_response}; use crate::query::MdnsPacket; use async_io::{Async, Timer}; use futures::prelude::*; use if_watch::{IfEvent, IfWatcher}; -use lazy_static::lazy_static; use libp2p_core::connection::ListenerId; use libp2p_core::{ address_translation, multiaddr::Protocol, Multiaddr, PeerId, @@ -38,18 +38,13 @@ use std::{ cmp, collections::VecDeque, fmt, io, iter, - net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket}, pin::Pin, task::Context, task::Poll, time::{Duration, Instant}, }; -lazy_static! { - static ref IPV4_MDNS_MULTICAST_ADDRESS: SocketAddr = - SocketAddr::from((Ipv4Addr::new(224, 0, 0, 251), 5353)); -} - /// Configuration for mDNS. #[derive(Clone, Debug)] pub struct MdnsConfig { @@ -61,6 +56,8 @@ pub struct MdnsConfig { /// peer joins the network. Receiving an mdns packet resets the timer /// preventing unnecessary traffic. pub query_interval: Duration, + /// IP address for multicast. + pub multicast_addr: IpAddr, } impl Default for MdnsConfig { @@ -68,6 +65,7 @@ impl Default for MdnsConfig { Self { ttl: Duration::from_secs(6 * 60), query_interval: Duration::from_secs(5 * 60), + multicast_addr: *IPV4_MDNS_MULTICAST_ADDRESS, } } } @@ -118,24 +116,42 @@ pub struct Mdns { /// Discovery timer. timeout: Timer, + + // Multicast address. + multicast_addr: IpAddr, } impl Mdns { /// Builds a new `Mdns` behaviour. pub async fn new(config: MdnsConfig) -> io::Result { - let recv_socket = { - let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(socket2::Protocol::UDP))?; - socket.set_reuse_address(true)?; - #[cfg(unix)] - socket.set_reuse_port(true)?; - socket.bind(&SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5353).into())?; - let socket = UdpSocket::from(socket); - socket.set_multicast_loop_v4(true)?; - socket.set_multicast_ttl_v4(255)?; - Async::new(socket)? + let recv_socket = match config.multicast_addr { + IpAddr::V4(_) => { + let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(socket2::Protocol::UDP))?; + socket.set_reuse_address(true)?; + #[cfg(unix)] + socket.set_reuse_port(true)?; + socket.bind(&SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5353).into())?; + socket.set_multicast_loop_v4(true)?; + socket.set_multicast_ttl_v4(255)?; + Async::new(UdpSocket::from(socket))? + } + IpAddr::V6(_) => { + let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(socket2::Protocol::UDP))?; + socket.set_reuse_address(true)?; + #[cfg(unix)] + socket.set_reuse_port(true)?; + socket.bind(&SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 5353).into())?; + socket.set_multicast_loop_v6(true)?; + Async::new(UdpSocket::from(socket))? + } }; let send_socket = { - let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?; + let addrs = [ + SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0), + SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0), + ]; + + let socket = std::net::UdpSocket::bind(&addrs[..])?; Async::new(socket)? }; let if_watch = if_watch::IfWatcher::new().await?; @@ -151,6 +167,7 @@ impl Mdns { query_interval: config.query_interval, ttl: config.ttl, timeout: Timer::interval(config.query_interval), + multicast_addr: config.multicast_addr, }) } @@ -279,20 +296,34 @@ impl NetworkBehaviour for Mdns { >, > { while let Poll::Ready(event) = Pin::new(&mut self.if_watch).poll(cx) { - let multicast = From::from([224, 0, 0, 251]); let socket = self.recv_socket.get_ref(); match event { Ok(IfEvent::Up(inet)) => { if inet.addr().is_loopback() { continue; } - if let IpAddr::V4(addr) = inet.addr() { - log::trace!("joining multicast on iface {}", addr); - if let Err(err) = socket.join_multicast_v4(&multicast, &addr) { - log::error!("join multicast failed: {}", err); - } else { - self.timeout - .set_interval_at(Instant::now(), self.query_interval); + match self.multicast_addr { + IpAddr::V4(multicast) => { + if let IpAddr::V4(addr) = inet.addr() { + log::trace!("joining multicast on iface {}", addr); + if let Err(err) = socket.join_multicast_v4(&multicast, &addr) { + log::error!("join multicast failed: {}", err); + } else { + self.timeout + .set_interval_at(Instant::now(), self.query_interval); + } + } + } + IpAddr::V6(multicast) => { + if let IpAddr::V6(addr) = inet.addr() { + log::trace!("joining multicast on iface {}", addr); + if let Err(err) = socket.join_multicast_v6(&multicast, 0) { + log::error!("join multicast failed: {}", err); + } else { + self.timeout + .set_interval_at(Instant::now(), self.query_interval); + } + } } } } @@ -300,10 +331,22 @@ impl NetworkBehaviour for Mdns { if inet.addr().is_loopback() { continue; } - if let IpAddr::V4(addr) = inet.addr() { - log::trace!("leaving multicast on iface {}", addr); - if let Err(err) = socket.leave_multicast_v4(&multicast, &addr) { - log::error!("leave multicast failed: {}", err); + match self.multicast_addr { + IpAddr::V4(multicast) => { + if let IpAddr::V4(addr) = inet.addr() { + log::trace!("leaving multicast on iface {}", addr); + if let Err(err) = socket.leave_multicast_v4(&multicast, &addr) { + log::error!("leave multicast failed: {}", err); + } + } + } + IpAddr::V6(multicast) => { + if let IpAddr::V6(addr) = inet.addr() { + log::trace!("leaving multicast on iface {}", addr); + if let Err(err) = socket.leave_multicast_v6(&multicast, 0) { + log::error!("leave multicast failed: {}", err); + } + } } } } @@ -332,7 +375,7 @@ impl NetworkBehaviour for Mdns { if let Some(packet) = self.send_buffer.pop_front() { match self .send_socket - .send_to(&packet, *IPV4_MDNS_MULTICAST_ADDRESS) + .send_to(&packet, SocketAddr::new(self.multicast_addr, 5353)) .now_or_never() { Some(Ok(_)) => {} diff --git a/protocols/mdns/src/dns.rs b/protocols/mdns/src/dns.rs index 82b52b0d..92fd980c 100644 --- a/protocols/mdns/src/dns.rs +++ b/protocols/mdns/src/dns.rs @@ -417,9 +417,7 @@ mod tests { #[test] fn build_query_response_correct() { - let my_peer_id = identity::Keypair::generate_ed25519() - .public() - .to_peer_id(); + let my_peer_id = identity::Keypair::generate_ed25519().public().to_peer_id(); let addr1 = "/ip4/1.2.3.4/tcp/5000".parse().unwrap(); let addr2 = "/ip6/::1/udp/10000".parse().unwrap(); let packets = build_query_response( diff --git a/protocols/mdns/src/lib.rs b/protocols/mdns/src/lib.rs index 23806138..d8e51bdd 100644 --- a/protocols/mdns/src/lib.rs +++ b/protocols/mdns/src/lib.rs @@ -29,12 +29,20 @@ //! This crate provides the `Mdns` struct which implements the `NetworkBehaviour` trait. This //! struct will automatically discover other libp2p nodes on the local network. //! +use lazy_static::lazy_static; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; /// The DNS service name for all libp2p peers used to query for addresses. const SERVICE_NAME: &[u8] = b"_p2p._udp.local"; /// The meta query for looking up the `SERVICE_NAME`. const META_QUERY_SERVICE: &[u8] = b"_services._dns-sd._udp.local"; +lazy_static! { + pub static ref IPV4_MDNS_MULTICAST_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(224, 0, 0, 251)); + pub static ref IPV6_MDNS_MULTICAST_ADDRESS: IpAddr = + IpAddr::V6(Ipv6Addr::new(0xFF02, 0, 0, 0, 0, 0, 0, 0xFB)); +} + pub use crate::behaviour::{Mdns, MdnsConfig, MdnsEvent}; mod behaviour; diff --git a/protocols/mdns/tests/smoke.rs b/protocols/mdns/tests/smoke.rs new file mode 100644 index 00000000..3d374dd4 --- /dev/null +++ b/protocols/mdns/tests/smoke.rs @@ -0,0 +1,101 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE.use futures::StreamExt; + +use futures::StreamExt; +use libp2p::{ + identity, + mdns::{Mdns, MdnsConfig, MdnsEvent, IPV6_MDNS_MULTICAST_ADDRESS}, + swarm::{Swarm, SwarmEvent}, + PeerId, +}; +use std::error::Error; + +async fn create_swarm(config: MdnsConfig) -> Result, Box> { + let id_keys = identity::Keypair::generate_ed25519(); + let peer_id = PeerId::from(id_keys.public()); + let transport = libp2p::development_transport(id_keys).await?; + let behaviour = Mdns::new(config).await?; + let mut swarm = Swarm::new(transport, behaviour, peer_id); + swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?; + Ok(swarm) +} + +async fn run_test(config: MdnsConfig) -> Result<(), Box> { + let mut a = create_swarm(config.clone()).await?; + let mut b = create_swarm(config).await?; + let mut discovered_a = false; + let mut discovered_b = false; + loop { + futures::select! { + ev = a.select_next_some() => match ev { + SwarmEvent::Behaviour(MdnsEvent::Discovered(peers)) => { + for (peer, _addr) in peers { + if peer == *b.local_peer_id() { + if discovered_a { + return Ok(()); + } else { + discovered_b = true; + } + } + } + } + _ => {} + }, + ev = b.select_next_some() => match ev { + SwarmEvent::Behaviour(MdnsEvent::Discovered(peers)) => { + for (peer, _addr) in peers { + if peer == *a.local_peer_id() { + if discovered_b { + return Ok(()); + } else { + discovered_a = true; + } + } + } + } + _ => {} + } + } + } +} + +#[async_std::test] +async fn test_discovery_async_std_ipv4() -> Result<(), Box> { + run_test(MdnsConfig::default()).await +} + +#[async_std::test] +async fn test_discovery_async_std_ipv6() -> Result<(), Box> { + let mut config = MdnsConfig::default(); + config.multicast_addr = *IPV6_MDNS_MULTICAST_ADDRESS; + run_test(MdnsConfig::default()).await +} + +#[tokio::test] +async fn test_discovery_tokio_ipv4() -> Result<(), Box> { + run_test(MdnsConfig::default()).await +} + +#[tokio::test] +async fn test_discovery_tokio_ipv6() -> Result<(), Box> { + let mut config = MdnsConfig::default(); + config.multicast_addr = *IPV6_MDNS_MULTICAST_ADDRESS; + run_test(MdnsConfig::default()).await +}