Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPv6 support for mDNS #2161

Merged
merged 11 commits into from
Aug 3, 2021
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: 6 additions & 0 deletions protocols/mdns/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions protocols/mdns/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
105 changes: 74 additions & 31 deletions protocols/mdns/src/behaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -61,13 +56,16 @@ 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 {
fn default() -> Self {
Self {
ttl: Duration::from_secs(6 * 60),
query_interval: Duration::from_secs(5 * 60),
multicast_addr: *IPV4_MDNS_MULTICAST_ADDRESS,
}
}
}
Expand Down Expand Up @@ -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<Self> {
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?;
Expand All @@ -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,
})
}

Expand Down Expand Up @@ -279,31 +296,57 @@ 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);
}
}
}
}
}
Ok(IfEvent::Down(inet)) => {
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);
}
}
}
}
}
Expand Down Expand Up @@ -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(_)) => {}
Expand Down
4 changes: 1 addition & 3 deletions protocols/mdns/src/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 8 additions & 0 deletions protocols/mdns/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
101 changes: 101 additions & 0 deletions protocols/mdns/tests/smoke.rs
Original file line number Diff line number Diff line change
@@ -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
}