use if_addrs::get_if_addrs;
use rand::{rng, RngExt};
use std::collections::HashSet;
use std::{
io::{stderr, stdin},
net::UdpSocket,
sync::Arc,
time::Duration,
};
use swarm_discovery::Discoverer;
use tokio::runtime::Builder;
use tokio::time;
use tracing_subscriber::{fmt, EnvFilter};
fn main() {
fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_writer(stderr)
.init();
let rt = Builder::new_multi_thread()
.enable_all()
.build()
.expect("build runtime");
let my_peer_id = format!("peer_id{}", rng().random_range(0..100));
let addrs = get_if_addrs()
.expect("get_if_addrs")
.into_iter()
.map(|iface| iface.addr.ip())
.collect::<Vec<_>>();
let port = UdpSocket::bind((addrs[0], 0))
.expect("bind")
.local_addr()
.expect("local_addr")
.port();
println!("my_peer_id: {}", my_peer_id);
println!("addrs: {:?}", addrs);
println!("Using multi-interface multicast with dynamic interface monitoring");
let mut peer_set: HashSet<String> = HashSet::new();
peer_set.insert(my_peer_id.clone());
println!("peer set: {:?}", peer_set);
let initial_ips: Vec<std::net::Ipv4Addr> = get_if_addrs()
.expect("get_if_addrs")
.into_iter()
.filter(|iface| !iface.is_loopback())
.map(|iface| iface.addr.ip())
.filter_map(|ip| match ip {
std::net::IpAddr::V4(ipv4) => Some(ipv4),
std::net::IpAddr::V6(_) => None,
})
.collect();
println!("Initial interfaces: {:?}", initial_ips);
let guard = Arc::new(
Discoverer::new_interactive("swarm".to_owned(), my_peer_id.clone())
.with_addrs(port, addrs.iter().take(1).copied())
.with_addrs(port + 1, addrs)
.with_multicast_interfaces_v4(initial_ips.clone()) .with_callback(move |peer_id, peer| {
if peer_set.insert(peer_id.to_string()) {
println!("new peer discovered {peer_id}: {:?}", peer);
println!("peer set: {:?}", peer_set);
}
if peer.addrs().is_empty() {
println!("peer removed: {peer_id}");
peer_set.remove(peer_id);
println!("peer set: {:?}", peer_set);
}
})
.spawn(rt.handle())
.expect("discoverer spawn"),
);
let guard_clone = guard.clone();
let mut known_interfaces: HashSet<std::net::Ipv4Addr> = initial_ips.into_iter().collect();
rt.spawn(async move {
println!("\nStarting interface monitor (checking every 5 seconds)...");
loop {
time::sleep(Duration::from_secs(5)).await;
let current_interfaces: HashSet<std::net::Ipv4Addr> = match get_if_addrs() {
Ok(addrs) => addrs
.into_iter()
.filter(|iface| !iface.is_loopback())
.map(|iface| iface.addr.ip())
.filter_map(|ip| match ip {
std::net::IpAddr::V4(ipv4) => Some(ipv4),
std::net::IpAddr::V6(_) => None,
})
.collect(),
Err(e) => {
eprintln!("Failed to get interfaces: {}", e);
continue;
}
};
for new_if in current_interfaces.difference(&known_interfaces) {
println!(
"📡 New interface detected: {} - adding to multicast",
new_if
);
guard_clone.add_interface_v4(*new_if);
}
for old_if in known_interfaces.difference(¤t_interfaces) {
println!("❌ Interface removed: {} - removing from multicast", old_if);
guard_clone.remove_interface_v4(*old_if);
}
known_interfaces = current_interfaces;
}
});
println!("\nPress Enter to exit...");
println!("While running, try connecting/disconnecting VPN, USB network adapters, etc.");
println!("The discoverer will automatically adapt to network changes!\n");
stdin().read_line(&mut String::new()).expect("read_line");
}