fix(network): derive the host IP from the default route, not hostname -I order

On a fresh ISO node, NetBird's own WireGuard tunnel (10.44.0.1) sorted
ahead of the real NIC in hostname -I, so the NetBird launch URL (and the
{{HOST_IP}} baked into its cert/config) pointed at the tunnel instead of
the LAN address. The main routing table's default route names the
physical uplink even while a VPN is active (NetBird/Tailscale steer
traffic via policy-routing rules, not the main table), so read src/dev
from 'ip -4 route show default' first, then fall back to a connected UDP
socket's source address, then to the old hostname -I scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-15 04:21:39 -04:00
co-authored by Claude Fable 5
parent 24be2e9e69
commit 2fce4fb842
5 changed files with 144 additions and 36 deletions
+3 -11
View File
@@ -80,19 +80,11 @@ pub struct Config {
}
impl Config {
/// Detect primary host IP (first non-loopback IPv4)
/// Detect primary host IP (default-route interface, not `hostname -I` order)
async fn detect_host_ip() -> Result<String> {
let output = tokio::process::Command::new("hostname")
.args(["-I"])
.output()
Ok(crate::host_ip::primary_host_ipv4()
.await
.context("Failed to run hostname -I")?;
let s = String::from_utf8_lossy(&output.stdout);
let ip = s
.split_whitespace()
.find(|s| !s.starts_with("127.") && s.contains('.'))
.unwrap_or("127.0.0.1");
Ok(ip.to_string())
.unwrap_or_else(|| "127.0.0.1".to_string()))
}
pub async fn load() -> Result<Self> {