mdns fix: use heuristic to find suitable interface and use IP of that interface to anounce service

This commit is contained in:
2025-11-13 20:35:29 +01:00
parent 2baf32f78a
commit 651e3db988
4 changed files with 99 additions and 3 deletions
Generated
+12 -1
View File
@@ -1948,6 +1948,16 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "if-addrs"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624c5448ba529e74f594c65b7024f31b2de7b64a9b228b8df26796bbb6e32c36"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "if-addrs"
version = "0.14.0"
@@ -2206,6 +2216,7 @@ dependencies = [
"bytes",
"eyre",
"gethostname",
"if-addrs 0.11.1",
"lanspread-compat",
"lanspread-db",
"lanspread-mdns",
@@ -2425,7 +2436,7 @@ checksum = "3426fcc57a3b93e136cbc83861d30ccbc6e6eb8788bd09b6eb92565d29841029"
dependencies = [
"fastrand",
"flume",
"if-addrs",
"if-addrs 0.14.0",
"log",
"mio",
"socket-pktinfo",
+1
View File
@@ -39,6 +39,7 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1", features = ["v7"] }
walkdir = "2"
if-addrs = "0.11"
windows = { version = "0.62", features = [
"Win32",
"Win32_UI",
+1
View File
@@ -29,3 +29,4 @@ tokio = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true }
walkdir = { workspace = true }
if-addrs = { workspace = true }
+85 -2
View File
@@ -5,13 +5,14 @@ mod peer;
use std::{
collections::{HashMap, VecDeque},
net::SocketAddr,
net::{IpAddr, SocketAddr},
path::{Path, PathBuf},
sync::Arc,
time::{Duration, Instant},
};
use bytes::BytesMut;
use if_addrs::{IfAddr, Interface, get_if_addrs};
use lanspread_db::db::{Game, GameDB, GameFileDescription};
use lanspread_mdns::{LANSPREAD_SERVICE_TYPE, MdnsAdvertiser, discover_service};
use lanspread_proto::{Message, Request, Response};
@@ -1269,6 +1270,10 @@ async fn run_server_component(
let server_addr = server.local_addr()?;
log::info!("Peer server listening on {server_addr}");
let advertise_ip = select_advertise_ip()?;
let advertise_addr = SocketAddr::new(advertise_ip, server_addr.port());
log::info!("Advertising peer via mDNS from {advertise_addr}");
// Start mDNS advertising for peer discovery
let peer_id = Uuid::now_v7().simple().to_string();
let hostname = gethostname::gethostname();
@@ -1289,7 +1294,7 @@ async fn run_server_component(
};
let mdns = tokio::task::spawn_blocking(move || {
MdnsAdvertiser::new(LANSPREAD_SERVICE_TYPE, &combined_str, server_addr)
MdnsAdvertiser::new(LANSPREAD_SERVICE_TYPE, &combined_str, advertise_addr)
})
.await??;
@@ -1935,6 +1940,84 @@ async fn ping_peer(peer_addr: SocketAddr) -> eyre::Result<bool> {
Ok(is_alive)
}
fn select_advertise_ip() -> eyre::Result<IpAddr> {
let mut best_candidate: Option<(u8, IpAddr)> = None;
let mut loopback_fallback = None;
for interface in get_if_addrs()? {
if interface.is_loopback() {
loopback_fallback.get_or_insert(interface.ip());
continue;
}
if let Some(candidate) = classify_interface(&interface)
&& best_candidate
.as_ref()
.is_none_or(|(rank, _)| candidate.0 < *rank)
{
best_candidate = Some(candidate);
}
}
if let Some((_, ip)) = best_candidate {
return Ok(ip);
}
if let Some(ip) = loopback_fallback {
log::warn!(
"No non-loopback interface suitable for mDNS advertisement; falling back to {ip}"
);
return Ok(ip);
}
eyre::bail!("No usable network interface found for mDNS advertisement");
}
fn classify_interface(interface: &Interface) -> Option<(u8, IpAddr)> {
match interface.addr {
IfAddr::V4(ref v4) => {
let ip = v4.ip;
if ip.is_unspecified() || ip.is_link_local() {
return None;
}
let mut rank = if ip.is_private() { 0 } else { 2 };
if is_virtual_interface(&interface.name) {
rank += 2;
}
Some((rank, IpAddr::V4(ip)))
}
IfAddr::V6(_) => None,
}
}
fn is_virtual_interface(name: &str) -> bool {
const VIRTUAL_HINTS: &[&str] = &[
"awdl",
"br-",
"bridge",
"docker",
"ham",
"llw",
"tap",
"tailscale",
"tun",
"utun",
"vbox",
"veth",
"virbr",
"vmnet",
"wg",
"zt",
];
let lower = name.to_ascii_lowercase();
VIRTUAL_HINTS.iter().any(|hint| lower.contains(hint))
}
async fn get_game_file_descriptions(
game_id: &str,
game_dir: &str,