add LAN service discovery via UDP multicast
Numa instances on the same network auto-discover each other's .numa services. No config, no cloud — just multicast on 239.255.70.78:5390. - PeerStore with lazy expiry (90s timeout, 30s broadcast interval) - DNS resolves remote .numa services to peer's LAN IP (not localhost) - Proxy forwards to peer IP for remote services - Graceful degradation if multicast bind fails Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
29
src/ctx.rs
29
src/ctx.rs
@@ -16,6 +16,7 @@ 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;
|
||||
@@ -29,6 +30,7 @@ pub struct ServerCtx {
|
||||
pub blocklist: Mutex<BlocklistStore>,
|
||||
pub query_log: Mutex<QueryLog>,
|
||||
pub services: Mutex<ServiceStore>,
|
||||
pub lan_peers: Mutex<PeerStore>,
|
||||
pub forwarding_rules: Vec<ForwardingRule>,
|
||||
pub upstream: SocketAddr,
|
||||
pub timeout: Duration,
|
||||
@@ -67,16 +69,39 @@ pub async fn handle_query(
|
||||
} else if !ctx.proxy_tld_suffix.is_empty()
|
||||
&& (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 resolve_ip = {
|
||||
let local = ctx.services.lock().unwrap();
|
||||
if local.lookup(service_name).is_some() {
|
||||
std::net::Ipv4Addr::LOCALHOST
|
||||
} else {
|
||||
let mut peers = ctx.lan_peers.lock().unwrap();
|
||||
peers
|
||||
.lookup(service_name)
|
||||
.and_then(|(ip, _)| match ip {
|
||||
std::net::IpAddr::V4(v4) => Some(v4),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(std::net::Ipv4Addr::LOCALHOST)
|
||||
}
|
||||
};
|
||||
let mut resp = DnsPacket::response_from(&query, ResultCode::NOERROR);
|
||||
match qtype {
|
||||
QueryType::AAAA => resp.answers.push(DnsRecord::AAAA {
|
||||
domain: qname.clone(),
|
||||
addr: std::net::Ipv6Addr::LOCALHOST,
|
||||
addr: if resolve_ip == std::net::Ipv4Addr::LOCALHOST {
|
||||
std::net::Ipv6Addr::LOCALHOST
|
||||
} else {
|
||||
resolve_ip.to_ipv6_mapped()
|
||||
},
|
||||
ttl: 300,
|
||||
}),
|
||||
_ => resp.answers.push(DnsRecord::A {
|
||||
domain: qname.clone(),
|
||||
addr: std::net::Ipv4Addr::LOCALHOST,
|
||||
addr: resolve_ip,
|
||||
ttl: 300,
|
||||
}),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user