summaryrefslogtreecommitdiffstats
path: root/src/transport/p2p.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-27 03:44:03 +0100
committertv <tv@krebsco.de>2026-10-27 03:44:03 +0100
commitc64a96723ec068fa7a9a7b60e6f7edd0f2f8603b (patch)
tree6fcf2f9c8f57c071262a62b12f8baaef20801af7 /src/transport/p2p.rs
parent16942f707da89e4ce7ce178d116142a17911a614 (diff)
use libp2p mDNS
Diffstat (limited to 'src/transport/p2p.rs')
-rw-r--r--src/transport/p2p.rs125
1 files changed, 113 insertions, 12 deletions
diff --git a/src/transport/p2p.rs b/src/transport/p2p.rs
index 675ff13..e6abb0a 100644
--- a/src/transport/p2p.rs
+++ b/src/transport/p2p.rs
@@ -29,9 +29,13 @@
//! (nodes of the swarm) to dial it back. Once AutoNAT says it can be dialled, it lets
//! the relays it picked go.
//!
-//! What this transport doesn't do yet (`docs/future-work.md`): find peers on the LAN
-//! (mDNS). It never uses public libp2p infrastructure: relays are those configured, or
-//! members that say they relay.
+//! With `--libp2p-mdns`, a node finds libp2p peers on its LAN (multicast DNS). Their
+//! LAN addresses are noted, and those that turn out to speak continuum (identify says
+//! so) are seeds while the node joins (`discovered_seeds`): nodes on one LAN find each
+//! other without configured seeds.
+//!
+//! It never uses public libp2p infrastructure: relays are those configured, or members
+//! that say they relay.
use std::collections::HashMap;
use std::path::Path;
@@ -105,6 +109,8 @@ pub(crate) struct Libp2pConfig {
pub(crate) relays: Vec<Multiaddr>,
/// Relay for other nodes.
pub(crate) relay: bool,
+ /// Find peers on the LAN (mDNS).
+ pub(crate) mdns: bool,
/// libp2p's in-memory transport instead of TCP and QUIC: for tests.
pub(crate) memory: bool,
}
@@ -166,9 +172,29 @@ struct Book {
serving: bool,
/// Members that say they relay, and where (with the relay's peer ID).
relays: HashMap<PeerId, Multiaddr>,
+ /// Peers found on the LAN (mDNS) not known yet to speak continuum.
+ lan: std::collections::HashSet<PeerId>,
+ /// Peers that speak continuum (identify says so).
+ continuum: std::collections::HashSet<PeerId>,
+ /// Peers found on the LAN that speak continuum, with their address: seeds not
+ /// handed out yet (`discovered_seeds`).
+ discovered: Vec<Multiaddr>,
}
impl Book {
+ /// `peer`, found on the LAN, speaks continuum: a seed, at the address we know first.
+ fn discover(&mut self, peer: PeerId) {
+ let Some(address) = self.addresses.get(&peer).and_then(|known| known.first()) else {
+ return;
+ };
+ if let Ok(address) = address.clone().with_p2p(peer)
+ && !self.discovered.contains(&address)
+ && self.discovered.len() < MAX_PEERS
+ {
+ self.discovered.push(address);
+ }
+ }
+
/// Where others could dial us directly: the announced addresses, or else those we
/// listen on (`published`), without the peer ID.
fn direct(&self) -> Vec<Multiaddr> {
@@ -262,9 +288,9 @@ impl Libp2pTransport {
pub(crate) fn start(config: Libp2pConfig) -> Result<Self> {
let peer_id = config.key.public().to_peer_id();
let mut swarm = if config.memory {
- memory_swarm(config.key, config.relay)?
+ memory_swarm(config.key, config.relay, config.mdns)?
} else {
- network_swarm(config.key, config.relay)?
+ network_swarm(config.key, config.relay, config.mdns)?
};
for address in config.listen {
swarm
@@ -353,10 +379,17 @@ struct Behaviour {
relay: ::libp2p::swarm::behaviour::toggle::Toggle<::libp2p::relay::Behaviour>,
dcutr: ::libp2p::dcutr::Behaviour,
autonat: ::libp2p::autonat::Behaviour,
+ /// Only with `Libp2pConfig::mdns`.
+ mdns: ::libp2p::swarm::behaviour::toggle::Toggle<::libp2p::mdns::tokio::Behaviour>,
}
impl Behaviour {
- fn new(key: &Keypair, relay_client: ::libp2p::relay::client::Behaviour, relay: bool) -> Self {
+ fn new(
+ key: &Keypair,
+ relay_client: ::libp2p::relay::client::Behaviour,
+ relay: bool,
+ mdns: bool,
+ ) -> std::io::Result<Self> {
let peer_id = key.public().to_peer_id();
let identify = ::libp2p::identify::Config::new(IDENTIFY_VERSION.to_owned(), key.public())
.with_agent_version(format!("continuum/{}", env!("CARGO_PKG_VERSION")));
@@ -372,7 +405,14 @@ impl Behaviour {
},
)
});
- Self {
+ let mdns = match mdns {
+ true => Some(::libp2p::mdns::tokio::Behaviour::new(
+ ::libp2p::mdns::Config::default(),
+ peer_id,
+ )?),
+ false => None,
+ };
+ Ok(Self {
stream: libp2p_stream::Behaviour::new(),
identify: ::libp2p::identify::Behaviour::new(identify),
relay_client,
@@ -386,11 +426,12 @@ impl Behaviour {
..Default::default()
},
),
- }
+ mdns: mdns.into(),
+ })
}
}
-fn network_swarm(key: Keypair, relay: bool) -> Result<Swarm<Behaviour>> {
+fn network_swarm(key: Keypair, relay: bool, mdns: bool) -> Result<Swarm<Behaviour>> {
Ok(::libp2p::SwarmBuilder::with_existing_identity(key)
.with_tokio()
.with_tcp(
@@ -404,12 +445,15 @@ fn network_swarm(key: Keypair, relay: bool) -> Result<Swarm<Behaviour>> {
::libp2p::noise::Config::new,
::libp2p::yamux::Config::default,
)?
- .with_behaviour(|key, relay_client| Behaviour::new(key, relay_client, relay))?
+ .with_behaviour(|key, relay_client| {
+ Behaviour::new(key, relay_client, relay, mdns)
+ .map_err(|error| Box::new(error) as Box<dyn std::error::Error + Send + Sync>)
+ })?
.with_swarm_config(|config| config.with_idle_connection_timeout(IDLE_TIMEOUT))
.build())
}
-fn memory_swarm(key: Keypair, relay: bool) -> Result<Swarm<Behaviour>> {
+fn memory_swarm(key: Keypair, relay: bool, mdns: bool) -> Result<Swarm<Behaviour>> {
use ::libp2p::Transport as _;
use ::libp2p::core::transport::{MemoryTransport, OrTransport};
use ::libp2p::core::upgrade::Version;
@@ -425,7 +469,7 @@ fn memory_swarm(key: Keypair, relay: bool) -> Result<Swarm<Behaviour>> {
.boxed();
Ok(Swarm::new(
transport,
- Behaviour::new(&key, relay_client, relay),
+ Behaviour::new(&key, relay_client, relay, mdns)?,
peer_id,
::libp2p::swarm::Config::with_tokio_executor().with_idle_connection_timeout(IDLE_TIMEOUT),
))
@@ -718,6 +762,39 @@ async fn run(
for address in published(&info.listen_addrs).into_iter().rev() {
book.remember(peer_id, address);
}
+ if info.protocols.contains(&PROTOCOL) {
+ book.continuum.insert(peer_id);
+ if book.lan.remove(&peer_id) {
+ book.discover(peer_id);
+ }
+ }
+ }
+ SwarmEvent::Behaviour(BehaviourEvent::Mdns(
+ ::libp2p::mdns::Event::Discovered(found),
+ )) => {
+ for (peer, address) in found {
+ if peer == relaying.local {
+ continue;
+ }
+ debug!(peer = %peer, address = %address, "libp2p peer on the LAN");
+ let continuum = {
+ let mut book = lock(&book);
+ book.remember(peer, address.clone());
+ book.continuum.contains(&peer)
+ };
+ if continuum {
+ lock(&book).discover(peer);
+ } else if lock(&book).lan.len() < MAX_PEERS {
+ // Connected, identify says whether it speaks continuum.
+ lock(&book).lan.insert(peer);
+ let _ = swarm.dial(
+ DialOpts::peer_id(peer)
+ .addresses(vec![address])
+ .condition(PeerCondition::DisconnectedAndNotDialing)
+ .build(),
+ );
+ }
+ }
}
_ => {}
},
@@ -827,6 +904,14 @@ impl Transport for Libp2pTransport {
Some(peer.to_string())
}
+ /// Nodes found on the LAN (mDNS) that speak continuum.
+ fn discovered_seeds(&self) -> Vec<String> {
+ std::mem::take(&mut lock(&self.book).discovered)
+ .iter()
+ .map(Multiaddr::to_string)
+ .collect()
+ }
+
/// Where `target` is, each address with its peer ID.
fn locations(&self, target: &str) -> Vec<String> {
let Ok(peer) = PeerId::from_str(target) else {
@@ -980,6 +1065,7 @@ mod tests {
announce: Vec::new(),
relays,
relay,
+ mdns: false,
memory: true,
})
.unwrap();
@@ -1117,6 +1203,7 @@ mod tests {
announce: vec![announced.clone()],
relays: Vec::new(),
relay: false,
+ mdns: false,
memory: true,
})
.unwrap();
@@ -1229,6 +1316,20 @@ mod tests {
);
}
+ /// A peer on the LAN that speaks continuum becomes a seed once, at its address.
+ #[test]
+ fn lan_peers_become_seeds_once() {
+ let mut book = Book::default();
+ let lan = peer();
+ let address: Multiaddr = "/ip4/192.168.1.5/tcp/9661".parse().unwrap();
+ book.discover(lan); // No address known: nothing.
+ assert!(book.discovered.is_empty());
+ book.remember(lan, address.clone());
+ book.discover(lan);
+ book.discover(lan);
+ assert_eq!(book.discovered, [address.with_p2p(lan).unwrap()]);
+ }
+
#[tokio::test]
async fn unknown_peers_are_refused() {
let (b, _) = memory_node();