mdns fix: use heuristic to find suitable interface and use IP of that interface to anounce service
This commit is contained in:
@@ -29,3 +29,4 @@ tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
walkdir = { workspace = true }
|
||||
if-addrs = { workspace = true }
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user