Adds support for handling interface changes to mdns behaviour. (#1830)

* mdns: handle address changes.

* Update examples.

* Use async-io.

* Fix tokio-chat.

* Address review comments.

* Update if-watch.

* Poll interfaces correctly.

* Use socket2 and remove wasm-time.

* Update if-watch.

* Update versions and changelogs.

* Further changelog updates.

Co-authored-by: Roman Borschel <romanb@users.noreply.github.com>
Co-authored-by: Roman S. Borschel <roman@parity.io>
This commit is contained in:
David Craven 2020-12-03 13:30:52 +01:00 committed by GitHub
parent 4bdb61be0d
commit 505a17dfc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 161 additions and 167 deletions

View File

@ -23,6 +23,10 @@
- [`parity-multiaddr` CHANGELOG](misc/multiaddr/CHANGELOG.md) - [`parity-multiaddr` CHANGELOG](misc/multiaddr/CHANGELOG.md)
- [`libp2p-core-derive` CHANGELOG](misc/core-derive/CHANGELOG.md) - [`libp2p-core-derive` CHANGELOG](misc/core-derive/CHANGELOG.md)
# Version 0.32.0 [unreleased]
- Update to `libp2p-mdns-0.26`.
# Version 0.31.2 [2020-12-02] # Version 0.31.2 [2020-12-02]
- Bump minimum `libp2p-core` patch version. - Bump minimum `libp2p-core` patch version.

View File

@ -2,7 +2,7 @@
name = "libp2p" name = "libp2p"
edition = "2018" edition = "2018"
description = "Peer-to-peer networking library" description = "Peer-to-peer networking library"
version = "0.31.2" version = "0.32.0"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT" license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p" repository = "https://github.com/libp2p/rust-libp2p"
@ -17,7 +17,7 @@ default = [
"identify", "identify",
"kad", "kad",
"gossipsub", "gossipsub",
"mdns-async-std", "mdns",
"mplex", "mplex",
"noise", "noise",
"ping", "ping",
@ -37,8 +37,7 @@ floodsub = ["libp2p-floodsub"]
identify = ["libp2p-identify"] identify = ["libp2p-identify"]
kad = ["libp2p-kad"] kad = ["libp2p-kad"]
gossipsub = ["libp2p-gossipsub"] gossipsub = ["libp2p-gossipsub"]
mdns-async-std = ["libp2p-mdns", "libp2p-mdns/async-std"] mdns = ["libp2p-mdns"]
mdns-tokio = ["libp2p-mdns", "libp2p-mdns/tokio"]
mplex = ["libp2p-mplex"] mplex = ["libp2p-mplex"]
noise = ["libp2p-noise"] noise = ["libp2p-noise"]
ping = ["libp2p-ping"] ping = ["libp2p-ping"]
@ -87,7 +86,7 @@ wasm-timer = "0.2.4"
[target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))'.dependencies] [target.'cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))'.dependencies]
libp2p-deflate = { version = "0.25.0", path = "protocols/deflate", optional = true } libp2p-deflate = { version = "0.25.0", path = "protocols/deflate", optional = true }
libp2p-dns = { version = "0.25.0", path = "transports/dns", optional = true } libp2p-dns = { version = "0.25.0", path = "transports/dns", optional = true }
libp2p-mdns = { version = "0.25.0", path = "protocols/mdns", optional = true } libp2p-mdns = { version = "0.26.0", path = "protocols/mdns", optional = true }
libp2p-tcp = { version = "0.25.1", path = "transports/tcp", optional = true } libp2p-tcp = { version = "0.25.1", path = "transports/tcp", optional = true }
libp2p-websocket = { version = "0.26.0", path = "transports/websocket", optional = true } libp2p-websocket = { version = "0.26.0", path = "transports/websocket", optional = true }
@ -125,4 +124,4 @@ members = [
[[example]] [[example]]
name = "chat-tokio" name = "chat-tokio"
required-features = ["tcp-tokio", "mdns-tokio"] required-features = ["tcp-tokio", "mdns"]

View File

@ -46,8 +46,7 @@ use libp2p::{
core::upgrade, core::upgrade,
identity, identity,
floodsub::{self, Floodsub, FloodsubEvent}, floodsub::{self, Floodsub, FloodsubEvent},
// `TokioMdns` is available through the `mdns-tokio` feature. mdns::{Mdns, MdnsEvent},
mdns::{TokioMdns, MdnsEvent},
mplex, mplex,
noise, noise,
swarm::{NetworkBehaviourEventProcess, SwarmBuilder}, swarm::{NetworkBehaviourEventProcess, SwarmBuilder},
@ -90,7 +89,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
#[derive(NetworkBehaviour)] #[derive(NetworkBehaviour)]
struct MyBehaviour { struct MyBehaviour {
floodsub: Floodsub, floodsub: Floodsub,
mdns: TokioMdns, mdns: Mdns,
} }
impl NetworkBehaviourEventProcess<FloodsubEvent> for MyBehaviour { impl NetworkBehaviourEventProcess<FloodsubEvent> for MyBehaviour {
@ -122,7 +121,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
// Create a Swarm to manage peers and events. // Create a Swarm to manage peers and events.
let mut swarm = { let mut swarm = {
let mdns = TokioMdns::new()?; let mdns = Mdns::new().await?;
let mut behaviour = MyBehaviour { let mut behaviour = MyBehaviour {
floodsub: Floodsub::new(peer_id.clone()), floodsub: Floodsub::new(peer_id.clone()),
mdns, mdns,
@ -172,4 +171,4 @@ async fn main() -> Result<(), Box<dyn Error>> {
} }
} }
} }
} }

View File

@ -121,7 +121,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// Create a Swarm to manage peers and events // Create a Swarm to manage peers and events
let mut swarm = { let mut swarm = {
let mdns = Mdns::new()?; let mdns = task::block_on(Mdns::new())?;
let mut behaviour = MyBehaviour { let mut behaviour = MyBehaviour {
floodsub: Floodsub::new(local_peer_id.clone()), floodsub: Floodsub::new(local_peer_id.clone()),
mdns, mdns,

View File

@ -151,7 +151,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// Create a Kademlia behaviour. // Create a Kademlia behaviour.
let store = MemoryStore::new(local_peer_id.clone()); let store = MemoryStore::new(local_peer_id.clone());
let kademlia = Kademlia::new(local_peer_id.clone(), store); let kademlia = Kademlia::new(local_peer_id.clone(), store);
let mdns = Mdns::new()?; let mdns = task::block_on(Mdns::new())?;
let behaviour = MyBehaviour { kademlia, mdns }; let behaviour = MyBehaviour { kademlia, mdns };
Swarm::new(transport, behaviour, local_peer_id) Swarm::new(transport, behaviour, local_peer_id)
}; };

View File

@ -26,7 +26,7 @@ fn main() -> Result<(), Box<dyn Error>> {
// This example provides passive discovery of the libp2p nodes on the // This example provides passive discovery of the libp2p nodes on the
// network that send mDNS queries and answers. // network that send mDNS queries and answers.
task::block_on(async move { task::block_on(async move {
let mut service = MdnsService::new()?; let mut service = MdnsService::new().await?;
loop { loop {
let (srv, packet) = service.next().await; let (srv, packet) = service.next().await;
match packet { match packet {

View File

@ -1,3 +1,16 @@
# 0.26.0 [unreleased]
- Detect interface changes and join the MDNS multicast
group on all interfaces as they become available.
[PR 1830](https://github.com/libp2p/rust-libp2p/pull/1830).
- Replace the use of macros for abstracting over `tokio`
and `async-std` with the use of `async-io`. As a result
there may now be an additional reactor thread running
called `async-io` when using `tokio`, with the futures
still being polled by the `tokio` runtime.
[PR 1830](https://github.com/libp2p/rust-libp2p/pull/1830).
# 0.25.0 [2020-11-25] # 0.25.0 [2020-11-25]
- Update `libp2p-swarm` and `libp2p-core`. - Update `libp2p-swarm` and `libp2p-core`.

View File

@ -1,7 +1,7 @@
[package] [package]
name = "libp2p-mdns" name = "libp2p-mdns"
edition = "2018" edition = "2018"
version = "0.25.0" version = "0.26.0"
description = "Implementation of the libp2p mDNS discovery method" description = "Implementation of the libp2p mDNS discovery method"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT" license = "MIT"
@ -10,22 +10,21 @@ keywords = ["peer-to-peer", "libp2p", "networking"]
categories = ["network-programming", "asynchronous"] categories = ["network-programming", "asynchronous"]
[dependencies] [dependencies]
async-std = { version = "1.6.2", optional = true } async-io = "1.3.0"
data-encoding = "2.0" data-encoding = "2.3.1"
dns-parser = "0.8" dns-parser = "0.8.0"
either = "1.5.3" futures = "0.3.8"
futures = "0.3.1" if-watch = "0.1.6"
lazy_static = "1.2" lazy_static = "1.4.0"
libp2p-core = { version = "0.25.0", path = "../../core" } libp2p-core = { version = "0.25.0", path = "../../core" }
libp2p-swarm = { version = "0.25.0", path = "../../swarm" } libp2p-swarm = { version = "0.25.0", path = "../../swarm" }
log = "0.4" log = "0.4.11"
net2 = "0.2" rand = "0.7.3"
rand = "0.7" smallvec = "1.5.0"
smallvec = "1.0" socket2 = { version = "0.3.17", features = ["reuseport"] }
tokio = { version = "0.3", default-features = false, features = ["net"], optional = true } void = "1.0.2"
void = "1.0"
wasm-timer = "0.2.4"
[dev-dependencies] [dev-dependencies]
if-addrs = "0.6.4" async-std = "1.7.0"
tokio = { version = "0.3", default-features = false, features = ["rt", "rt-multi-thread"] } if-addrs = "0.6.5"
tokio = { version = "0.3.4", default-features = false, features = ["rt", "rt-multi-thread"] }

View File

@ -18,7 +18,8 @@
// 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::service::{MdnsPacket, build_query_response, build_service_discovery_response}; use crate::service::{MdnsPacket, MdnsService, build_query_response, build_service_discovery_response};
use async_io::Timer;
use futures::prelude::*; use futures::prelude::*;
use libp2p_core::{ use libp2p_core::{
Multiaddr, Multiaddr,
@ -34,21 +35,16 @@ use libp2p_swarm::{
ProtocolsHandler, ProtocolsHandler,
protocols_handler::DummyProtocolsHandler protocols_handler::DummyProtocolsHandler
}; };
use log::warn;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{cmp, fmt, io, iter, mem, pin::Pin, time::Duration, task::Context, task::Poll}; use std::{cmp, fmt, io, iter, mem, pin::Pin, time::{Duration, Instant}, task::Context, task::Poll};
use wasm_timer::{Delay, Instant};
const MDNS_RESPONSE_TTL: std::time::Duration = Duration::from_secs(5 * 60); const MDNS_RESPONSE_TTL: std::time::Duration = Duration::from_secs(5 * 60);
macro_rules! codegen {
($feature_name:expr, $behaviour_name:ident, $maybe_busy_wrapper:ident, $service_name:ty) => {
/// A `NetworkBehaviour` for mDNS. Automatically discovers peers on the local network and adds /// A `NetworkBehaviour` for mDNS. Automatically discovers peers on the local network and adds
/// them to the topology. /// them to the topology.
pub struct $behaviour_name { pub struct Mdns {
/// The inner service. /// The inner service.
service: $maybe_busy_wrapper, service: MdnsBusyWrapper,
/// List of nodes that we have discovered, the address, and when their TTL expires. /// List of nodes that we have discovered, the address, and when their TTL expires.
/// ///
@ -59,44 +55,44 @@ pub struct $behaviour_name {
/// Future that fires when the TTL of at least one node in `discovered_nodes` expires. /// Future that fires when the TTL of at least one node in `discovered_nodes` expires.
/// ///
/// `None` if `discovered_nodes` is empty. /// `None` if `discovered_nodes` is empty.
closest_expiration: Option<Delay>, closest_expiration: Option<Timer>,
} }
/// `MdnsService::next` takes ownership of `self`, returning a future that resolves with both itself /// `MdnsService::next` takes ownership of `self`, returning a future that resolves with both itself
/// and a `MdnsPacket` (similar to the old Tokio socket send style). The two states are thus `Free` /// and a `MdnsPacket` (similar to the old Tokio socket send style). The two states are thus `Free`
/// with an `MdnsService` or `Busy` with a future returning the original `MdnsService` and an /// with an `MdnsService` or `Busy` with a future returning the original `MdnsService` and an
/// `MdnsPacket`. /// `MdnsPacket`.
enum $maybe_busy_wrapper { enum MdnsBusyWrapper {
Free($service_name), Free(MdnsService),
Busy(Pin<Box<dyn Future<Output = ($service_name, MdnsPacket)> + Send>>), Busy(Pin<Box<dyn Future<Output = (MdnsService, MdnsPacket)> + Send>>),
Poisoned, Poisoned,
} }
impl fmt::Debug for $maybe_busy_wrapper { impl fmt::Debug for MdnsBusyWrapper {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
$maybe_busy_wrapper::Free(service) => { Self::Free(service) => {
fmt.debug_struct("$maybe_busy_wrapper::Free") fmt.debug_struct("MdnsBusyWrapper::Free")
.field("service", service) .field("service", service)
.finish() .finish()
}, },
$maybe_busy_wrapper::Busy(_) => { Self::Busy(_) => {
fmt.debug_struct("$maybe_busy_wrapper::Busy") fmt.debug_struct("MdnsBusyWrapper::Busy")
.finish() .finish()
} }
$maybe_busy_wrapper::Poisoned => { Self::Poisoned => {
fmt.debug_struct("$maybe_busy_wrapper::Poisoned") fmt.debug_struct("MdnsBusyWrapper::Poisoned")
.finish() .finish()
} }
} }
} }
} }
impl $behaviour_name { impl Mdns {
/// Builds a new `Mdns` behaviour. /// Builds a new `Mdns` behaviour.
pub fn new() -> io::Result<$behaviour_name> { pub async fn new() -> io::Result<Self> {
Ok($behaviour_name { Ok(Self {
service: $maybe_busy_wrapper::Free(<$service_name>::new()?), service: MdnsBusyWrapper::Free(MdnsService::new().await?),
discovered_nodes: SmallVec::new(), discovered_nodes: SmallVec::new(),
closest_expiration: None, closest_expiration: None,
}) })
@ -113,7 +109,7 @@ impl $behaviour_name {
} }
} }
impl NetworkBehaviour for $behaviour_name { impl NetworkBehaviour for Mdns {
type ProtocolsHandler = DummyProtocolsHandler; type ProtocolsHandler = DummyProtocolsHandler;
type OutEvent = MdnsEvent; type OutEvent = MdnsEvent;
@ -138,9 +134,9 @@ impl NetworkBehaviour for $behaviour_name {
&mut self, &mut self,
_: PeerId, _: PeerId,
_: ConnectionId, _: ConnectionId,
_ev: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent, ev: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
) { ) {
void::unreachable(_ev) void::unreachable(ev)
} }
fn poll( fn poll(
@ -155,9 +151,8 @@ impl NetworkBehaviour for $behaviour_name {
> { > {
// Remove expired peers. // Remove expired peers.
if let Some(ref mut closest_expiration) = self.closest_expiration { if let Some(ref mut closest_expiration) = self.closest_expiration {
match Future::poll(Pin::new(closest_expiration), cx) { match Pin::new(closest_expiration).poll(cx) {
Poll::Ready(Ok(())) => { Poll::Ready(now) => {
let now = Instant::now();
let mut expired = SmallVec::<[(PeerId, Multiaddr); 4]>::new(); let mut expired = SmallVec::<[(PeerId, Multiaddr); 4]>::new();
while let Some(pos) = self.discovered_nodes.iter().position(|(_, _, exp)| *exp < now) { while let Some(pos) = self.discovered_nodes.iter().position(|(_, _, exp)| *exp < now) {
let (peer_id, addr, _) = self.discovered_nodes.remove(pos); let (peer_id, addr, _) = self.discovered_nodes.remove(pos);
@ -173,38 +168,37 @@ impl NetworkBehaviour for $behaviour_name {
} }
}, },
Poll::Pending => (), Poll::Pending => (),
Poll::Ready(Err(err)) => warn!("timer has errored: {:?}", err),
} }
} }
// Polling the mDNS service, and obtain the list of nodes discovered this round. // Polling the mDNS service, and obtain the list of nodes discovered this round.
let discovered = loop { let discovered = loop {
let service = mem::replace(&mut self.service, $maybe_busy_wrapper::Poisoned); let service = mem::replace(&mut self.service, MdnsBusyWrapper::Poisoned);
let packet = match service { let packet = match service {
$maybe_busy_wrapper::Free(service) => { MdnsBusyWrapper::Free(service) => {
self.service = $maybe_busy_wrapper::Busy(Box::pin(service.next())); self.service = MdnsBusyWrapper::Busy(Box::pin(service.next()));
continue; continue;
}, },
$maybe_busy_wrapper::Busy(mut fut) => { MdnsBusyWrapper::Busy(mut fut) => {
match fut.as_mut().poll(cx) { match fut.as_mut().poll(cx) {
Poll::Ready((service, packet)) => { Poll::Ready((service, packet)) => {
self.service = $maybe_busy_wrapper::Free(service); self.service = MdnsBusyWrapper::Free(service);
packet packet
}, },
Poll::Pending => { Poll::Pending => {
self.service = $maybe_busy_wrapper::Busy(fut); self.service = MdnsBusyWrapper::Busy(fut);
return Poll::Pending; return Poll::Pending;
} }
} }
}, },
$maybe_busy_wrapper::Poisoned => panic!("Mdns poisoned"), MdnsBusyWrapper::Poisoned => panic!("Mdns poisoned"),
}; };
match packet { match packet {
MdnsPacket::Query(query) => { MdnsPacket::Query(query) => {
// MaybeBusyMdnsService should always be Free. // MaybeBusyMdnsService should always be Free.
if let $maybe_busy_wrapper::Free(ref mut service) = self.service { if let MdnsBusyWrapper::Free(ref mut service) = self.service {
let resp = build_query_response( let resp = build_query_response(
query.query_id(), query.query_id(),
params.local_peer_id().clone(), params.local_peer_id().clone(),
@ -256,7 +250,7 @@ impl NetworkBehaviour for $behaviour_name {
}, },
MdnsPacket::ServiceDiscovery(disc) => { MdnsPacket::ServiceDiscovery(disc) => {
// MaybeBusyMdnsService should always be Free. // MaybeBusyMdnsService should always be Free.
if let $maybe_busy_wrapper::Free(ref mut service) = self.service { if let MdnsBusyWrapper::Free(ref mut service) = self.service {
let resp = build_service_discovery_response( let resp = build_service_discovery_response(
disc.query_id(), disc.query_id(),
MDNS_RESPONSE_TTL, MDNS_RESPONSE_TTL,
@ -273,7 +267,7 @@ impl NetworkBehaviour for $behaviour_name {
.fold(None, |exp, &(_, _, elem_exp)| { .fold(None, |exp, &(_, _, elem_exp)| {
Some(exp.map(|exp| cmp::min(exp, elem_exp)).unwrap_or(elem_exp)) Some(exp.map(|exp| cmp::min(exp, elem_exp)).unwrap_or(elem_exp))
}) })
.map(Delay::new_at); .map(Timer::at);
Poll::Ready(NetworkBehaviourAction::GenerateEvent(MdnsEvent::Discovered(DiscoveredAddrsIter { Poll::Ready(NetworkBehaviourAction::GenerateEvent(MdnsEvent::Discovered(DiscoveredAddrsIter {
inner: discovered.into_iter(), inner: discovered.into_iter(),
@ -281,7 +275,7 @@ impl NetworkBehaviour for $behaviour_name {
} }
} }
impl fmt::Debug for $behaviour_name { impl fmt::Debug for Mdns {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("Mdns") fmt.debug_struct("Mdns")
.field("service", &self.service) .field("service", &self.service)
@ -289,15 +283,6 @@ impl fmt::Debug for $behaviour_name {
} }
} }
};
}
#[cfg(feature = "async-std")]
codegen!("async-std", Mdns, MaybeBusyMdnsService, crate::service::MdnsService);
#[cfg(feature = "tokio")]
codegen!("tokio", TokioMdns, MaybeBusyTokioMdnsService, crate::service::TokioMdnsService);
/// Event that can be produced by the `Mdns` behaviour. /// Event that can be produced by the `Mdns` behaviour.
#[derive(Debug)] #[derive(Debug)]
pub enum MdnsEvent { pub enum MdnsEvent {

View File

@ -22,9 +22,7 @@
//! `dns_parser` library. //! `dns_parser` library.
use crate::{META_QUERY_SERVICE, SERVICE_NAME}; use crate::{META_QUERY_SERVICE, SERVICE_NAME};
use data_encoding;
use libp2p_core::{Multiaddr, PeerId}; use libp2p_core::{Multiaddr, PeerId};
use rand;
use std::{borrow::Cow, cmp, error, fmt, str, time::Duration}; use std::{borrow::Cow, cmp, error, fmt, str, time::Duration};
/// Maximum size of a DNS label as per RFC1035 /// Maximum size of a DNS label as per RFC1035
@ -226,7 +224,7 @@ fn segment_peer_id(peer_id: String) -> String {
/// Combines and encodes a `PeerId` and service name for a DNS query. /// Combines and encodes a `PeerId` and service name for a DNS query.
fn encode_peer_id(peer_id: &PeerId) -> Vec<u8> { fn encode_peer_id(peer_id: &PeerId) -> Vec<u8> {
// DNS-safe encoding for the Peer ID // DNS-safe encoding for the Peer ID
let raw_peer_id = data_encoding::BASE32_DNSCURVE.encode(&peer_id.as_bytes()); let raw_peer_id = data_encoding::BASE32_DNSCURVE.encode(&peer_id.as_bytes());
// ensure we don't have any labels over 63 bytes long // ensure we don't have any labels over 63 bytes long
let encoded_peer_id = segment_peer_id(raw_peer_id); let encoded_peer_id = segment_peer_id(raw_peer_id);

View File

@ -35,12 +35,10 @@ const SERVICE_NAME: &[u8] = b"_p2p._udp.local";
/// Hardcoded name of the service used for DNS-SD. /// Hardcoded name of the service used for DNS-SD.
const META_QUERY_SERVICE: &[u8] = b"_services._dns-sd._udp.local"; const META_QUERY_SERVICE: &[u8] = b"_services._dns-sd._udp.local";
#[cfg(feature = "async-std")] pub use crate::{
pub use self::{behaviour::Mdns, service::MdnsService}; behaviour::{Mdns, MdnsEvent},
#[cfg(feature = "tokio")] service::MdnsService,
pub use self::{behaviour::TokioMdns, service::TokioMdnsService}; };
pub use self::behaviour::MdnsEvent;
mod behaviour; mod behaviour;
mod dns; mod dns;

View File

@ -19,14 +19,15 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
use crate::{SERVICE_NAME, META_QUERY_SERVICE, dns}; use crate::{SERVICE_NAME, META_QUERY_SERVICE, dns};
use async_io::{Async, Timer};
use dns_parser::{Packet, RData}; use dns_parser::{Packet, RData};
use either::Either::{Left, Right}; use futures::{prelude::*, select};
use futures::{future, prelude::*}; use if_watch::{IfEvent, IfWatcher};
use lazy_static::lazy_static;
use libp2p_core::{multiaddr::{Multiaddr, Protocol}, PeerId}; use libp2p_core::{multiaddr::{Multiaddr, Protocol}, PeerId};
use log::warn; use log::warn;
use std::{convert::TryFrom as _, fmt, io, net::Ipv4Addr, net::SocketAddr, str, time::{Duration, Instant}}; use socket2::{Socket, Domain, Type};
use wasm_timer::Interval; use std::{convert::TryFrom, fmt, io, net::{IpAddr, Ipv4Addr, UdpSocket, SocketAddr}, str, time::{Duration, Instant}};
use lazy_static::lazy_static;
pub use dns::{MdnsResponseError, build_query_response, build_service_discovery_response}; pub use dns::{MdnsResponseError, build_query_response, build_service_discovery_response};
@ -37,9 +38,6 @@ lazy_static! {
)); ));
} }
macro_rules! codegen {
($feature_name:expr, $service_name:ident, $udp_socket:ty, $udp_socket_from_std:tt) => {
/// A running service that discovers libp2p peers and responds to other libp2p peers' queries on /// A running service that discovers libp2p peers and responds to other libp2p peers' queries on
/// the local network. /// the local network.
/// ///
@ -71,10 +69,7 @@ macro_rules! codegen {
/// # let my_peer_id = PeerId::from(identity::Keypair::generate_ed25519().public()); /// # let my_peer_id = PeerId::from(identity::Keypair::generate_ed25519().public());
/// # let my_listened_addrs: Vec<Multiaddr> = vec![]; /// # let my_listened_addrs: Vec<Multiaddr> = vec![];
/// # async { /// # async {
/// # #[cfg(feature = "async-std")] /// # let mut service = libp2p_mdns::service::MdnsService::new().await.unwrap();
/// # let mut service = libp2p_mdns::service::MdnsService::new().unwrap();
/// # #[cfg(feature = "tokio")]
/// # let mut service = libp2p_mdns::service::TokioMdnsService::new().unwrap();
/// let _future_to_poll = async { /// let _future_to_poll = async {
/// let (mut service, packet) = service.next().await; /// let (mut service, packet) = service.next().await;
/// ///
@ -108,19 +103,18 @@ macro_rules! codegen {
/// }; /// };
/// # }; /// # };
/// # } /// # }
#[cfg_attr(docsrs, doc(cfg(feature = $feature_name)))] pub struct MdnsService {
pub struct $service_name {
/// Main socket for listening. /// Main socket for listening.
socket: $udp_socket, socket: Async<UdpSocket>,
/// Socket for sending queries on the network. /// Socket for sending queries on the network.
query_socket: $udp_socket, query_socket: Async<UdpSocket>,
/// Interval for sending queries. /// Interval for sending queries.
query_interval: Interval, query_interval: Timer,
/// Whether we send queries on the network at all. /// Whether we send queries on the network at all.
/// Note that we still need to have an interval for querying, as we need to wake up the socket /// Note that we still need to have an interval for querying, as we need to wake up the socket
/// regularly to recover from errors. Otherwise we could simply use an `Option<Interval>`. /// regularly to recover from errors. Otherwise we could simply use an `Option<Timer>`.
silent: bool, silent: bool,
/// Buffer used for receiving data from the main socket. /// Buffer used for receiving data from the main socket.
/// RFC6762 discourages packets larger than the interface MTU, but allows sizes of up to 9000 /// RFC6762 discourages packets larger than the interface MTU, but allows sizes of up to 9000
@ -133,55 +127,54 @@ pub struct $service_name {
send_buffers: Vec<Vec<u8>>, send_buffers: Vec<Vec<u8>>,
/// Buffers pending to send on the query socket. /// Buffers pending to send on the query socket.
query_send_buffers: Vec<Vec<u8>>, query_send_buffers: Vec<Vec<u8>>,
/// Iface watch.
if_watch: IfWatcher,
} }
impl $service_name { impl MdnsService {
/// Starts a new mDNS service. /// Starts a new mDNS service.
pub fn new() -> io::Result<$service_name> { pub async fn new() -> io::Result<Self> {
Self::new_inner(false) Self::new_inner(false).await
} }
/// Same as `new`, but we don't automatically send queries on the network. /// Same as `new`, but we don't automatically send queries on the network.
pub fn silent() -> io::Result<$service_name> { pub async fn silent() -> io::Result<Self> {
Self::new_inner(true) Self::new_inner(true).await
} }
/// Starts a new mDNS service. /// Starts a new mDNS service.
fn new_inner(silent: bool) -> io::Result<$service_name> { async fn new_inner(silent: bool) -> io::Result<Self> {
let std_socket = { let socket = {
let socket = Socket::new(Domain::ipv4(), Type::dgram(), Some(socket2::Protocol::udp()))?;
socket.set_reuse_address(true)?;
#[cfg(unix)] #[cfg(unix)]
fn platform_specific(s: &net2::UdpBuilder) -> io::Result<()> { socket.set_reuse_port(true)?;
net2::unix::UnixUdpBuilderExt::reuse_port(s, true)?; socket.bind(&SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 5353).into())?;
Ok(()) let socket = socket.into_udp_socket();
} socket.set_multicast_loop_v4(true)?;
#[cfg(not(unix))] socket.set_multicast_ttl_v4(255)?;
fn platform_specific(_: &net2::UdpBuilder) -> io::Result<()> { Ok(()) } Async::new(socket)?
let builder = net2::UdpBuilder::new_v4()?;
builder.reuse_address(true)?;
platform_specific(&builder)?;
builder.bind(("0.0.0.0", 5353))?
}; };
let socket = $udp_socket_from_std(std_socket)?;
// Given that we pass an IP address to bind, which does not need to be resolved, we can // Given that we pass an IP address to bind, which does not need to be resolved, we can
// use std::net::UdpSocket::bind, instead of its async counterpart from async-std. // use std::net::UdpSocket::bind, instead of its async counterpart from async-std.
let query_socket = $udp_socket_from_std( let query_socket = {
std::net::UdpSocket::bind((Ipv4Addr::from([0u8, 0, 0, 0]), 0u16))?, let socket = std::net::UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?;
)?; Async::new(socket)?
};
socket.set_multicast_loop_v4(true)?;
socket.set_multicast_ttl_v4(255)?;
// TODO: correct interfaces?
socket.join_multicast_v4(From::from([224, 0, 0, 251]), Ipv4Addr::UNSPECIFIED)?;
Ok($service_name { let if_watch = if_watch::IfWatcher::new().await?;
Ok(Self {
socket, socket,
query_socket, query_socket,
query_interval: Interval::new_at(Instant::now(), Duration::from_secs(20)), query_interval: Timer::interval_at(Instant::now(), Duration::from_secs(20)),
silent, silent,
recv_buffer: [0; 4096], recv_buffer: [0; 4096],
send_buffers: Vec::new(), send_buffers: Vec::new(),
query_send_buffers: Vec::new(), query_send_buffers: Vec::new(),
if_watch,
}) })
} }
@ -247,18 +240,8 @@ impl $service_name {
} }
} }
// Either (left) listen for incoming packets or (right) send query packets whenever the select! {
// query interval fires. res = self.socket.recv_from(&mut self.recv_buffer).fuse() => match res {
let selected_output = match futures::future::select(
Box::pin(self.socket.recv_from(&mut self.recv_buffer)),
Box::pin(self.query_interval.next()),
).await {
future::Either::Left((recved, _)) => Left(recved),
future::Either::Right(_) => Right(()),
};
match selected_output {
Left(left) => match left {
Ok((len, from)) => { Ok((len, from)) => {
match MdnsPacket::new_from_bytes(&self.recv_buffer[..len], from) { match MdnsPacket::new_from_bytes(&self.recv_buffer[..len], from) {
Some(packet) => return (self, packet), Some(packet) => return (self, packet),
@ -270,7 +253,7 @@ impl $service_name {
// The query interval will wake up the task at some point so that we can try again. // The query interval will wake up the task at some point so that we can try again.
}, },
}, },
Right(_) => { _ = self.query_interval.next().fuse() => {
// Ensure underlying task is woken up on the next interval tick. // Ensure underlying task is woken up on the next interval tick.
while let Some(_) = self.query_interval.next().now_or_never() {}; while let Some(_) = self.query_interval.next().now_or_never() {};
@ -278,13 +261,42 @@ impl $service_name {
let query = dns::build_query(); let query = dns::build_query();
self.query_send_buffers.push(query.to_vec()); self.query_send_buffers.push(query.to_vec());
} }
},
event = self.if_watch.next().fuse() => {
let multicast = From::from([224, 0, 0, 251]);
let socket = self.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);
}
}
}
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);
}
}
}
Err(err) => log::error!("if watch returned an error: {}", err),
}
} }
}; };
} }
} }
} }
impl fmt::Debug for $service_name { impl fmt::Debug for MdnsService {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("$service_name") fmt.debug_struct("$service_name")
.field("silent", &self.silent) .field("silent", &self.silent)
@ -292,17 +304,6 @@ impl fmt::Debug for $service_name {
} }
} }
};
}
#[cfg(feature = "async-std")]
codegen!("async-std", MdnsService, async_std::net::UdpSocket, (|socket| Ok::<_, std::io::Error>(async_std::net::UdpSocket::from(socket))));
// Note: Tokio's UdpSocket::from_std does not set the socket into non-blocking mode.
#[cfg(feature = "tokio")]
codegen!("tokio", TokioMdnsService, tokio::net::UdpSocket, (|socket: std::net::UdpSocket| { socket.set_nonblocking(true); tokio::net::UdpSocket::from_std(socket) }));
/// A valid mDNS packet received by the service. /// A valid mDNS packet received by the service.
#[derive(Debug)] #[derive(Debug)]
pub enum MdnsPacket { pub enum MdnsPacket {
@ -595,7 +596,7 @@ mod tests {
fn discover(peer_id: PeerId) { fn discover(peer_id: PeerId) {
let fut = async { let fut = async {
let mut service = <$service_name>::new().unwrap(); let mut service = <$service_name>::new().await.unwrap();
loop { loop {
let next = service.next().await; let next = service.next().await;
@ -639,7 +640,7 @@ mod tests {
.collect(); .collect();
let fut = async { let fut = async {
let mut service = <$service_name>::new().unwrap(); let mut service = <$service_name>::new().await.unwrap();
let mut sent_queries = vec![]; let mut sent_queries = vec![];
@ -690,17 +691,15 @@ mod tests {
} }
} }
#[cfg(feature = "async-std")]
testgen!( testgen!(
async_std, async_std,
crate::service::MdnsService, crate::service::MdnsService,
(|fut| async_std::task::block_on::<_, ()>(fut)) (|fut| async_std::task::block_on::<_, ()>(fut))
); );
#[cfg(feature = "tokio")]
testgen!( testgen!(
tokio, tokio,
crate::service::TokioMdnsService, crate::service::MdnsService,
(|fut| tokio::runtime::Runtime::new().unwrap().block_on::<futures::future::BoxFuture<()>>(fut)) (|fut| tokio::runtime::Runtime::new().unwrap().block_on::<futures::future::BoxFuture<()>>(fut))
); );
} }

View File

@ -196,8 +196,8 @@ pub use libp2p_gossipsub as gossipsub;
#[cfg_attr(docsrs, doc(cfg(feature = "mplex")))] #[cfg_attr(docsrs, doc(cfg(feature = "mplex")))]
#[doc(inline)] #[doc(inline)]
pub use libp2p_mplex as mplex; pub use libp2p_mplex as mplex;
#[cfg(any(feature = "mdns-async-std", feature = "mdns-tokio"))] #[cfg(feature = "mdns")]
#[cfg_attr(docsrs, doc(cfg(any(feature = "mdns-async-std", feature = "mdns-tokio"))))] #[cfg_attr(docsrs, doc(cfg(feature = "mdns")))]
#[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))] #[cfg(not(any(target_os = "emscripten", target_os = "wasi", target_os = "unknown")))]
#[doc(inline)] #[doc(inline)]
pub use libp2p_mdns as mdns; pub use libp2p_mdns as mdns;