#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)] use std::net::SocketAddr; use eyre::bail; pub use mdns_sd::DaemonEvent; use mdns_sd::{Receiver, ServiceDaemon, ServiceEvent, ServiceInfo}; pub const LANSPREAD_SERVICE_TYPE: &str = "_lanspread._udp.local."; pub struct MdnsAdvertiser { daemon: ServiceDaemon, service_info: ServiceInfo, pub monitor: Receiver, } impl MdnsAdvertiser { pub fn new(service_type: &str, instance_name: &str, address: SocketAddr) -> eyre::Result { let host_name = format!("{}.local.", address.ip()); let daemon = ServiceDaemon::new()?; let service_info = ServiceInfo::new( service_type, instance_name, &host_name, address.ip(), address.port(), None, )?; let monitor = daemon.monitor()?; // Register the service daemon.register(service_info.clone())?; Ok(Self { daemon, service_info, monitor, }) } } impl Drop for MdnsAdvertiser { fn drop(&mut self) { let _ = self.daemon.unregister(self.service_info.get_fullname()); let _ = self.daemon.shutdown(); } } pub struct MdnsBrowser { daemon: ServiceDaemon, receiver: Receiver, service_type: String, } impl MdnsBrowser { pub fn new(service_type: &str) -> eyre::Result { let daemon = ServiceDaemon::new()?; let receiver = daemon.browse(service_type)?; Ok(Self { daemon, receiver, service_type: service_type.to_string(), }) } pub fn next_address( &self, ignore_addr: Option, ) -> eyre::Result> { loop { match self.receiver.recv() { Ok(ServiceEvent::ServiceResolved(info)) => { log::trace!("mdns ServiceResolved event: {info:?}"); if info.ty_domain != self.service_type { log::trace!( "Got mDNS with uninteresting service type: {} (expected: {})", info.ty_domain, self.service_type, ); continue; } let mut ignored_match = false; for address in info.get_addresses() { let addr = SocketAddr::new(address.to_ip_addr(), info.get_port()); if ignore_addr.is_some_and(|ignore| ignore == addr) { ignored_match = true; log::trace!("Ignoring mDNS advertisement for local server at {addr}"); continue; } log::info!("Found server at {addr}"); return Ok(Some(addr)); } if ignored_match { log::trace!( "Only saw ignored mDNS advertisements (probably ourselves) for {:?}", info.get_fullname() ); continue; } log::error!("No address found in mDNS response: {info:?}"); } Ok(other_event) => { log::trace!("mdns unrelated event: {other_event:?}"); } Err(err) => { log::error!("mDNS browse channel closed: {err}"); return Ok(None); } } } } } impl Drop for MdnsBrowser { fn drop(&mut self) { let _ = self.daemon.shutdown(); } } pub fn discover_service( service_type: &str, ignore_addr: Option, ) -> eyre::Result { // Currently unused; kept for potential one-off discovery callers that just need a single address. let browser = MdnsBrowser::new(service_type)?; match browser.next_address(ignore_addr)? { Some(addr) => Ok(addr), None => bail!("No server found."), } }