generalize upstream re-detection into network change watcher
Always detect network changes (LAN IP, upstream, peers) regardless of upstream config. LAN IP is now tracked in ServerCtx and updated every 30s — multicast announcements use the current IP instead of the startup IP. Upstream re-detection still only runs when auto-detected. Peer flush triggers on any network change. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,8 +33,9 @@ pub struct ServerCtx {
|
|||||||
pub lan_peers: Mutex<PeerStore>,
|
pub lan_peers: Mutex<PeerStore>,
|
||||||
pub forwarding_rules: Vec<ForwardingRule>,
|
pub forwarding_rules: Vec<ForwardingRule>,
|
||||||
pub upstream: Mutex<SocketAddr>,
|
pub upstream: Mutex<SocketAddr>,
|
||||||
pub upstream_auto: bool, // true = auto-detected, false = explicitly configured
|
pub upstream_auto: bool,
|
||||||
pub upstream_port: u16,
|
pub upstream_port: u16,
|
||||||
|
pub lan_ip: Mutex<std::net::Ipv4Addr>,
|
||||||
pub timeout: Duration,
|
pub timeout: Duration,
|
||||||
pub proxy_tld: String,
|
pub proxy_tld: String,
|
||||||
pub proxy_tld_suffix: String, // pre-computed ".{tld}" to avoid per-query allocation
|
pub proxy_tld_suffix: String, // pre-computed ".{tld}" to avoid per-query allocation
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
|
|||||||
.as_nanos() as u64;
|
.as_nanos() as u64;
|
||||||
pid ^ ts
|
pid ^ ts
|
||||||
};
|
};
|
||||||
let local_ip = detect_lan_ip().unwrap_or(Ipv4Addr::LOCALHOST);
|
let local_ip = *ctx.lan_ip.lock().unwrap();
|
||||||
info!(
|
info!(
|
||||||
"LAN discovery on {}:{}, local IP {}, instance {:016x}",
|
"LAN discovery on {}:{}, local IP {}, instance {:016x}",
|
||||||
multicast_group, port, local_ip, instance_id
|
multicast_group, port, local_ip, instance_id
|
||||||
@@ -142,7 +142,6 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
|
|||||||
// Spawn sender
|
// Spawn sender
|
||||||
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 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);
|
||||||
@@ -162,9 +161,10 @@ pub async fn start_lan_discovery(ctx: Arc<ServerCtx>, config: &LanConfig) {
|
|||||||
if services.is_empty() {
|
if services.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let current_ip = sender_ctx.lan_ip.lock().unwrap().to_string();
|
||||||
let announcement = Announcement {
|
let announcement = Announcement {
|
||||||
instance_id,
|
instance_id,
|
||||||
host: local_ip_str.clone(),
|
host: current_ip,
|
||||||
services,
|
services,
|
||||||
};
|
};
|
||||||
if let Ok(json) = serde_json::to_vec(&announcement) {
|
if let Ok(json) = serde_json::to_vec(&announcement) {
|
||||||
|
|||||||
53
src/main.rs
53
src/main.rs
@@ -132,6 +132,7 @@ async fn main() -> numa::Result<()> {
|
|||||||
upstream: Mutex::new(upstream),
|
upstream: Mutex::new(upstream),
|
||||||
upstream_auto: config.upstream.address.is_empty(),
|
upstream_auto: config.upstream.address.is_empty(),
|
||||||
upstream_port: config.upstream.port,
|
upstream_port: config.upstream.port,
|
||||||
|
lan_ip: Mutex::new(numa::lan::detect_lan_ip().unwrap_or(std::net::Ipv4Addr::LOCALHOST)),
|
||||||
timeout: Duration::from_millis(config.upstream.timeout_ms),
|
timeout: Duration::from_millis(config.upstream.timeout_ms),
|
||||||
proxy_tld_suffix: if config.proxy.tld.is_empty() {
|
proxy_tld_suffix: if config.proxy.tld.is_empty() {
|
||||||
String::new()
|
String::new()
|
||||||
@@ -242,11 +243,11 @@ async fn main() -> numa::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn upstream re-detection (only for auto-detected upstream)
|
// Spawn network change watcher (upstream re-detection, LAN IP update, peer flush)
|
||||||
if ctx.upstream_auto {
|
{
|
||||||
let redetect_ctx = Arc::clone(&ctx);
|
let watch_ctx = Arc::clone(&ctx);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
upstream_redetect_loop(redetect_ctx).await;
|
network_watch_loop(watch_ctx).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,33 +275,43 @@ async fn main() -> numa::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upstream_redetect_loop(ctx: Arc<numa::ctx::ServerCtx>) {
|
async fn network_watch_loop(ctx: Arc<numa::ctx::ServerCtx>) {
|
||||||
use numa::system_dns::discover_system_dns;
|
|
||||||
|
|
||||||
let mut interval = tokio::time::interval(Duration::from_secs(30));
|
let mut interval = tokio::time::interval(Duration::from_secs(30));
|
||||||
interval.tick().await; // skip immediate tick
|
interval.tick().await; // skip immediate tick
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
interval.tick().await;
|
interval.tick().await;
|
||||||
|
let mut changed = false;
|
||||||
|
|
||||||
let dns_info = discover_system_dns();
|
// Check LAN IP change
|
||||||
let new_addr = match dns_info.default_upstream {
|
if let Some(new_ip) = numa::lan::detect_lan_ip() {
|
||||||
Some(addr) => addr,
|
let mut current_ip = ctx.lan_ip.lock().unwrap();
|
||||||
None => continue,
|
if new_ip != *current_ip {
|
||||||
};
|
info!("LAN IP changed: {} → {}", current_ip, new_ip);
|
||||||
let new_upstream: SocketAddr = match format!("{}:{}", new_addr, ctx.upstream_port).parse() {
|
*current_ip = new_ip;
|
||||||
Ok(addr) => addr,
|
changed = true;
|
||||||
Err(_) => continue,
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
// Check upstream change (only for auto-detected upstream)
|
||||||
|
if ctx.upstream_auto {
|
||||||
|
let dns_info = numa::system_dns::discover_system_dns();
|
||||||
|
if let Some(new_addr) = dns_info.default_upstream {
|
||||||
|
if let Ok(new_upstream) =
|
||||||
|
format!("{}:{}", new_addr, ctx.upstream_port).parse::<SocketAddr>()
|
||||||
|
{
|
||||||
let mut upstream = ctx.upstream.lock().unwrap();
|
let mut upstream = ctx.upstream.lock().unwrap();
|
||||||
let current = *upstream;
|
if new_upstream != *upstream {
|
||||||
if new_upstream != current {
|
info!("upstream changed: {} → {}", *upstream, new_upstream);
|
||||||
*upstream = new_upstream;
|
*upstream = new_upstream;
|
||||||
drop(upstream);
|
changed = true;
|
||||||
info!("upstream changed: {} → {}", current, new_upstream);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Flush stale LAN peers from old network
|
// Flush stale LAN peers on any network change
|
||||||
|
if changed {
|
||||||
ctx.lan_peers.lock().unwrap().clear();
|
ctx.lan_peers.lock().unwrap().clear();
|
||||||
info!("flushed LAN peers after network change");
|
info!("flushed LAN peers after network change");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user