fix LAN discovery: instance-based self-filter and multicast port reuse

Replace IP-based self-announcement filtering with a per-process instance
ID (pid ^ timestamp) so multiple instances on the same host can discover
each other. Enable SO_REUSEPORT for multicast socket binding on Unix.
Add multicast address validation on configured group.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Razvan Dimescu
2026-03-22 00:20:33 +02:00
parent 9041ccc2e1
commit 6fdadd637c
2 changed files with 22 additions and 7 deletions

View File

@@ -22,7 +22,7 @@ hyper = { version = "1", features = ["client", "http1", "server"] }
hyper-util = { version = "0.1", features = ["client-legacy", "http1", "tokio"] } hyper-util = { version = "0.1", features = ["client-legacy", "http1", "tokio"] }
http-body-util = "0.1" http-body-util = "0.1"
futures = "0.3" futures = "0.3"
socket2 = "0.5" socket2 = { version = "0.5", features = ["all"] }
rcgen = { version = "0.13", features = ["pem", "x509-parser"] } rcgen = { version = "0.13", features = ["pem", "x509-parser"] }
time = "0.3" time = "0.3"
rustls = "0.23" rustls = "0.23"

View File

@@ -63,6 +63,7 @@ impl PeerStore {
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct Announcement { struct Announcement {
instance_id: u64,
host: String, host: String,
services: Vec<AnnouncedService>, services: Vec<AnnouncedService>,
} }
@@ -83,8 +84,12 @@ pub fn detect_lan_ip() -> Option<Ipv4Addr> {
} }
pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) { pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
let multicast_group: Ipv4Addr = match config.multicast_group.parse() { let multicast_group: Ipv4Addr = match config.multicast_group.parse::<Ipv4Addr>() {
Ok(g) => g, Ok(g) if g.is_multicast() => g,
Ok(g) => {
warn!("LAN: {} is not a multicast address (224.0.0.0/4)", g);
return;
}
Err(e) => { Err(e) => {
warn!( warn!(
"LAN: invalid multicast group {}: {}", "LAN: invalid multicast group {}: {}",
@@ -96,10 +101,18 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
let port = config.port; let port = config.port;
let interval = Duration::from_secs(config.broadcast_interval_secs); let interval = Duration::from_secs(config.broadcast_interval_secs);
let instance_id: u64 = {
let pid = std::process::id() as u64;
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
pid ^ ts
};
let local_ip = detect_lan_ip().unwrap_or(Ipv4Addr::LOCALHOST); let local_ip = detect_lan_ip().unwrap_or(Ipv4Addr::LOCALHOST);
info!( info!(
"LAN discovery on {}:{}, local IP {}", "LAN discovery on {}:{}, local IP {}, instance {:016x}",
multicast_group, port, local_ip multicast_group, port, local_ip, instance_id
); );
// Create socket with SO_REUSEADDR for multicast // Create socket with SO_REUSEADDR for multicast
@@ -126,7 +139,6 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
let sender_ctx = Arc::clone(&ctx); let sender_ctx = Arc::clone(&ctx);
let sender_socket = Arc::clone(&socket); let sender_socket = Arc::clone(&socket);
let local_ip_str = local_ip.to_string(); let local_ip_str = local_ip.to_string();
let self_filter = local_ip_str.clone();
let dest = SocketAddr::new(IpAddr::V4(multicast_group), port); let dest = SocketAddr::new(IpAddr::V4(multicast_group), port);
tokio::spawn(async move { tokio::spawn(async move {
let mut ticker = tokio::time::interval(interval); let mut ticker = tokio::time::interval(interval);
@@ -147,6 +159,7 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
continue; continue;
} }
let announcement = Announcement { let announcement = Announcement {
instance_id,
host: local_ip_str.clone(), host: local_ip_str.clone(),
services, services,
}; };
@@ -171,7 +184,7 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
Err(_) => continue, Err(_) => continue,
}; };
// Skip self-announcements // Skip self-announcements
if announcement.host == self_filter { if announcement.instance_id == instance_id {
continue; continue;
} }
let peer_ip: IpAddr = match announcement.host.parse() { let peer_ip: IpAddr = match announcement.host.parse() {
@@ -202,6 +215,8 @@ fn create_multicast_socket(group: Ipv4Addr, port: u16) -> std::io::Result<std::n
Some(socket2::Protocol::UDP), Some(socket2::Protocol::UDP),
)?; )?;
socket.set_reuse_address(true)?; socket.set_reuse_address(true)?;
#[cfg(unix)]
socket.set_reuse_port(true)?;
socket.set_nonblocking(true)?; socket.set_nonblocking(true)?;
socket.bind(&socket2::SockAddr::from(addr))?; socket.bind(&socket2::SockAddr::from(addr))?;
socket.join_multicast_v4(&group, &Ipv4Addr::UNSPECIFIED)?; socket.join_multicast_v4(&group, &Ipv4Addr::UNSPECIFIED)?;