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:
@@ -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"
|
||||||
|
|||||||
27
src/lan.rs
27
src/lan.rs
@@ -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)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user