Add LAN service discovery via UDP multicast #7

Merged
razvandimescu merged 6 commits from feat/lan-discovery into main 2026-03-22 14:03:32 +08:00
3 changed files with 28 additions and 18 deletions
Showing only changes of commit d355f8d005 - Show all commits

View File

@@ -11,12 +11,12 @@ use crate::cache::DnsCache;
use crate::config::ZoneMap;
use crate::forward::forward_query;
use crate::header::ResultCode;
use crate::lan::PeerStore;
use crate::override_store::OverrideStore;
use crate::packet::DnsPacket;
use crate::query_log::{QueryLog, QueryLogEntry};
use crate::question::QueryType;
use crate::record::DnsRecord;
use crate::lan::PeerStore;
use crate::service_store::ServiceStore;
use crate::stats::{QueryPath, ServerStats};
use crate::system_dns::ForwardingRule;
@@ -70,9 +70,7 @@ pub async fn handle_query(
&& (qname.ends_with(&ctx.proxy_tld_suffix) || qname == ctx.proxy_tld)
{
// Resolve .numa: local services → 127.0.0.1, LAN peers → peer IP
let service_name = qname
.strip_suffix(&ctx.proxy_tld_suffix)
.unwrap_or(&qname);
let service_name = qname.strip_suffix(&ctx.proxy_tld_suffix).unwrap_or(&qname);
let resolve_ip = {
let local = ctx.services.lock().unwrap();
if local.lookup(service_name).is_some() {

View File

@@ -27,8 +27,7 @@ impl PeerStore {
pub fn update(&mut self, host: IpAddr, services: &[(String, u16)]) {
let now = Instant::now();
for (name, port) in services {
self.peers
.insert(name.to_lowercase(), (host, *port, now));
self.peers.insert(name.to_lowercase(), (host, *port, now));
}
}
@@ -44,11 +43,17 @@ impl PeerStore {
pub fn list(&mut self) -> Vec<(String, IpAddr, u16, u64)> {
let now = Instant::now();
self.peers.retain(|_, (_, _, seen)| now.duration_since(*seen) < self.timeout);
self.peers
.retain(|_, (_, _, seen)| now.duration_since(*seen) < self.timeout);
self.peers
.iter()
.map(|(name, (ip, port, seen))| {
(name.clone(), *ip, *port, now.duration_since(*seen).as_secs())
(
name.clone(),
*ip,
*port,
now.duration_since(*seen).as_secs(),
)
})
.collect()
}
@@ -81,7 +86,10 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
let multicast_group: Ipv4Addr = match config.multicast_group.parse() {
Ok(g) => g,
Err(e) => {
warn!("LAN: invalid multicast group {}: {}", config.multicast_group, e);
warn!(
"LAN: invalid multicast group {}: {}",
config.multicast_group, e
);
return;
}
};
@@ -89,13 +97,19 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
let interval = Duration::from_secs(config.broadcast_interval_secs);
let local_ip = detect_lan_ip().unwrap_or(Ipv4Addr::LOCALHOST);
info!("LAN discovery on {}:{}, local IP {}", multicast_group, port, local_ip);
info!(
"LAN discovery on {}:{}, local IP {}",
multicast_group, port, local_ip
);
// Create socket with SO_REUSEADDR for multicast
let std_socket = match create_multicast_socket(multicast_group, port) {
Ok(s) => s,
Err(e) => {
warn!("LAN: could not bind multicast socket: {} — LAN discovery disabled", e);
warn!(
"LAN: could not bind multicast socket: {} — LAN discovery disabled",
e
);
return;
}
};
@@ -178,10 +192,7 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
}
}
fn create_multicast_socket(
group: Ipv4Addr,
port: u16,
) -> std::io::Result<std::net::UdpSocket> {
fn create_multicast_socket(group: Ipv4Addr, port: u16) -> std::io::Result<std::net::UdpSocket> {
use std::net::SocketAddrV4;
let addr = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port);

View File

@@ -273,9 +273,10 @@ pre .str {{ color: #d48a5a }}
.path_and_query()
.map(|pq| pq.as_str())
.unwrap_or("/");
let target_uri: hyper::Uri = format!("http://{}:{}{}", target_host, target_port, path_and_query)
.parse()
.unwrap();
let target_uri: hyper::Uri =
format!("http://{}:{}{}", target_host, target_port, path_and_query)
.parse()
.unwrap();
// Check for upgrade request (WebSocket, etc.)
let is_upgrade = req.headers().get(hyper::header::UPGRADE).is_some();