Fix the TUN setup recipe: the overlay address was being flushed

The setup this tool printed did not work, and the agent then correctly
refused to start. A persistent TUN interface has no carrier until a process
attaches to it, and Linux flushes IPv6 addresses from an interface that
loses carrier unless net.ipv6.conf.<dev>.keep_addr_on_down is set, which it
is not by default. So `ip -6 address add` on a freshly created interface
silently lost the address before the agent ever ran.

The recipe now brings the link up first, sets keep_addr_on_down, and adds
the address with `nodad` — without which duplicate address detection can
never finish on an interface with no carrier and the address stays
tentative and unusable.

The agent's own retry loop made this worse: it attached, failed the address
check, dropped the device and toggled the carrier, which flushed the
address again. The check now runs before attaching to an existing
interface, so looking is not destructive.

Failures are self-diagnosing now: the check parses the IFA_F_* flags, tells
tentative and DAD-failed apart from missing, and lists the addresses the
interface actually has.

Four new tests, including one that reads this host's real /proc/net/if_inet6
and one that pins the ordering of the setup commands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
tsunagi
2026-09-21 12:36:16 +01:00
co-authored by Claude Opus 5
parent 0f8a4eb485
commit 38beb762d8
5 changed files with 256 additions and 28 deletions
+13 -2
View File
@@ -328,10 +328,21 @@ async fn tun_setup(args: TunSetupArgs) -> Result<(), Box<dyn std::error::Error>>
println!("# Network {name} ({network})");
println!("# Interface {interface}, address {address}/{OVERLAY_PREFIX_LEN}, mtu {mtu}");
println!("# Run once as root; then run `tsunagi up` as {user}.\n");
println!("# Run once as root; then run `tsunagi up` as {user}.");
println!(
"#\n\
# keep_addr_on_down matters: a persistent TUN interface has no carrier\n\
# until a process attaches, and Linux flushes IPv6 addresses from an\n\
# interface that loses carrier unless it is set. `nodad` matters for the\n\
# same reason: duplicate address detection can never finish without a\n\
# carrier, leaving the address tentative and unusable.\n"
);
println!("sudo ip tuntap add dev {interface} mode tun user {user}");
println!("sudo ip -6 address add {address}/{OVERLAY_PREFIX_LEN} dev {interface}");
println!("sudo ip link set dev {interface} mtu {mtu} up");
println!("sudo sysctl -qw net.ipv6.conf.{interface}.keep_addr_on_down=1");
println!("sudo ip -6 address add {address}/{OVERLAY_PREFIX_LEN} dev {interface} nodad");
println!("\n# To check it afterwards:");
println!("ip -6 addr show dev {interface}");
println!("\n# To remove it again:");
println!("sudo ip link del dev {interface}");
Ok(())