protocols/mdns: Add IPv6 support (#2161)

Signed-off-by: Emil Majchrzak <majchrzakemil@gitlab.com>

Co-authored-by: Emil Majchrzak <majchrzakemil@gitlab.com>
Co-authored-by: Max Inden <mail@max-inden.de>
This commit is contained in:
David Craven 2021-08-03 14:55:06 +02:00 committed by GitHub
parent a696039c74
commit e1f1af899b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 193 additions and 37 deletions

View File

@ -2,6 +2,12 @@
- Update dependencies. - 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] # 0.31.0 [2021-07-12]
- Update dependencies. - Update dependencies.

View File

@ -25,6 +25,6 @@ socket2 = { version = "0.4.0", features = ["all"] }
void = "1.0.2" void = "1.0.2"
[dev-dependencies] [dev-dependencies]
async-std = "1.9.0" async-std = { version = "1.9.0", features = ["attributes"] }
if-addrs = "0.6.5" libp2p = { path = "../.." }
tokio = { version = "1.2.0", default-features = false, features = ["rt", "rt-multi-thread"] } tokio = { version = "1.2.0", default-features = false, features = ["macros", "rt", "rt-multi-thread"] }

View File

@ -18,12 +18,12 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
use crate::IPV4_MDNS_MULTICAST_ADDRESS;
use crate::dns::{build_query, build_query_response, build_service_discovery_response}; use crate::dns::{build_query, build_query_response, build_service_discovery_response};
use crate::query::MdnsPacket; use crate::query::MdnsPacket;
use async_io::{Async, Timer}; use async_io::{Async, Timer};
use futures::prelude::*; use futures::prelude::*;
use if_watch::{IfEvent, IfWatcher}; use if_watch::{IfEvent, IfWatcher};
use lazy_static::lazy_static;
use libp2p_core::connection::ListenerId; use libp2p_core::connection::ListenerId;
use libp2p_core::{ use libp2p_core::{
address_translation, multiaddr::Protocol, Multiaddr, PeerId, address_translation, multiaddr::Protocol, Multiaddr, PeerId,
@ -38,18 +38,13 @@ use std::{
cmp, cmp,
collections::VecDeque, collections::VecDeque,
fmt, io, iter, fmt, io, iter,
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, UdpSocket},
pin::Pin, pin::Pin,
task::Context, task::Context,
task::Poll, task::Poll,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
lazy_static! {
static ref IPV4_MDNS_MULTICAST_ADDRESS: SocketAddr =
SocketAddr::from((Ipv4Addr::new(224, 0, 0, 251), 5353));
}
/// Configuration for mDNS. /// Configuration for mDNS.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MdnsConfig { pub struct MdnsConfig {
@ -61,6 +56,8 @@ pub struct MdnsConfig {
/// peer joins the network. Receiving an mdns packet resets the timer /// peer joins the network. Receiving an mdns packet resets the timer
/// preventing unnecessary traffic. /// preventing unnecessary traffic.
pub query_interval: Duration, pub query_interval: Duration,
/// IP address for multicast.
pub multicast_addr: IpAddr,
} }
impl Default for MdnsConfig { impl Default for MdnsConfig {
@ -68,6 +65,7 @@ impl Default for MdnsConfig {
Self { Self {
ttl: Duration::from_secs(6 * 60), ttl: Duration::from_secs(6 * 60),
query_interval: Duration::from_secs(5 * 60), query_interval: Duration::from_secs(5 * 60),
multicast_addr: *IPV4_MDNS_MULTICAST_ADDRESS,
} }
} }
} }
@ -118,24 +116,42 @@ pub struct Mdns {
/// Discovery timer. /// Discovery timer.
timeout: Timer, timeout: Timer,
// Multicast address.
multicast_addr: IpAddr,
} }
impl Mdns { impl Mdns {
/// Builds a new `Mdns` behaviour. /// Builds a new `Mdns` behaviour.
pub async fn new(config: MdnsConfig) -> io::Result<Self> { pub async fn new(config: MdnsConfig) -> io::Result<Self> {
let recv_socket = { let recv_socket = match config.multicast_addr {
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(socket2::Protocol::UDP))?; IpAddr::V4(_) => {
socket.set_reuse_address(true)?; let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(socket2::Protocol::UDP))?;
#[cfg(unix)] socket.set_reuse_address(true)?;
socket.set_reuse_port(true)?; #[cfg(unix)]
socket.bind(&SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5353).into())?; socket.set_reuse_port(true)?;
let socket = UdpSocket::from(socket); socket.bind(&SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5353).into())?;
socket.set_multicast_loop_v4(true)?; socket.set_multicast_loop_v4(true)?;
socket.set_multicast_ttl_v4(255)?; socket.set_multicast_ttl_v4(255)?;
Async::new(socket)? 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 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)? Async::new(socket)?
}; };
let if_watch = if_watch::IfWatcher::new().await?; let if_watch = if_watch::IfWatcher::new().await?;
@ -151,6 +167,7 @@ impl Mdns {
query_interval: config.query_interval, query_interval: config.query_interval,
ttl: config.ttl, ttl: config.ttl,
timeout: Timer::interval(config.query_interval), 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) { 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(); let socket = self.recv_socket.get_ref();
match event { match event {
Ok(IfEvent::Up(inet)) => { Ok(IfEvent::Up(inet)) => {
if inet.addr().is_loopback() { if inet.addr().is_loopback() {
continue; continue;
} }
if let IpAddr::V4(addr) = inet.addr() { match self.multicast_addr {
log::trace!("joining multicast on iface {}", addr); IpAddr::V4(multicast) => {
if let Err(err) = socket.join_multicast_v4(&multicast, &addr) { if let IpAddr::V4(addr) = inet.addr() {
log::error!("join multicast failed: {}", err); log::trace!("joining multicast on iface {}", addr);
} else { if let Err(err) = socket.join_multicast_v4(&multicast, &addr) {
self.timeout log::error!("join multicast failed: {}", err);
.set_interval_at(Instant::now(), self.query_interval); } 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() { if inet.addr().is_loopback() {
continue; continue;
} }
if let IpAddr::V4(addr) = inet.addr() { match self.multicast_addr {
log::trace!("leaving multicast on iface {}", addr); IpAddr::V4(multicast) => {
if let Err(err) = socket.leave_multicast_v4(&multicast, &addr) { if let IpAddr::V4(addr) = inet.addr() {
log::error!("leave multicast failed: {}", err); 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() { if let Some(packet) = self.send_buffer.pop_front() {
match self match self
.send_socket .send_socket
.send_to(&packet, *IPV4_MDNS_MULTICAST_ADDRESS) .send_to(&packet, SocketAddr::new(self.multicast_addr, 5353))
.now_or_never() .now_or_never()
{ {
Some(Ok(_)) => {} Some(Ok(_)) => {}

View File

@ -417,9 +417,7 @@ mod tests {
#[test] #[test]
fn build_query_response_correct() { fn build_query_response_correct() {
let my_peer_id = identity::Keypair::generate_ed25519() let my_peer_id = identity::Keypair::generate_ed25519().public().to_peer_id();
.public()
.to_peer_id();
let addr1 = "/ip4/1.2.3.4/tcp/5000".parse().unwrap(); let addr1 = "/ip4/1.2.3.4/tcp/5000".parse().unwrap();
let addr2 = "/ip6/::1/udp/10000".parse().unwrap(); let addr2 = "/ip6/::1/udp/10000".parse().unwrap();
let packets = build_query_response( let packets = build_query_response(

View File

@ -29,12 +29,20 @@
//! This crate provides the `Mdns` struct which implements the `NetworkBehaviour` trait. This //! This crate provides the `Mdns` struct which implements the `NetworkBehaviour` trait. This
//! struct will automatically discover other libp2p nodes on the local network. //! 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. /// The DNS service name for all libp2p peers used to query for addresses.
const SERVICE_NAME: &[u8] = b"_p2p._udp.local"; const SERVICE_NAME: &[u8] = b"_p2p._udp.local";
/// The meta query for looking up the `SERVICE_NAME`. /// The meta query for looking up the `SERVICE_NAME`.
const META_QUERY_SERVICE: &[u8] = b"_services._dns-sd._udp.local"; 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}; pub use crate::behaviour::{Mdns, MdnsConfig, MdnsEvent};
mod behaviour; mod behaviour;

View File

@ -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<Swarm<Mdns>, Box<dyn Error>> {
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<dyn Error>> {
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<dyn Error>> {
run_test(MdnsConfig::default()).await
}
#[async_std::test]
async fn test_discovery_async_std_ipv6() -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
run_test(MdnsConfig::default()).await
}
#[tokio::test]
async fn test_discovery_tokio_ipv6() -> Result<(), Box<dyn Error>> {
let mut config = MdnsConfig::default();
config.multicast_addr = *IPV6_MDNS_MULTICAST_ADDRESS;
run_test(MdnsConfig::default()).await
}