From 2f8925d4397a4eaf40546ef0ea8ecf5a8d827833 Mon Sep 17 00:00:00 2001 From: Git Sagar Date: Thu, 23 Jul 2026 20:23:54 -0300 Subject: [PATCH 1/2] fix: race in parallel wan veth creation cross-wiring namespaces All wan.net.vmix@* instances created their veth pair with the same temporary peer name 'vhost' in the host namespace before moving it into their netns. Parallel starts at boot could steal each other's peer ends, pairing a host-side vn- with another namespace's vhost (mismatched /30s, dead links) or leaving the pair stranded in the host namespace. Use a per-namespace temporary name (vh-) and rename to vhost only after the move, making concurrent creation collision-free. Co-Authored-By: Claude Fable 5 --- nixos/networks/config.nix | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nixos/networks/config.nix b/nixos/networks/config.nix index 37bf6a9..658b6ac 100644 --- a/nixos/networks/config.nix +++ b/nixos/networks/config.nix @@ -172,6 +172,10 @@ let let wanCfg = cfg // { spaceName = spaceName; }; vethInNSToHost.iface = "vhost"; + # Temporary peer name, unique per namespace. The peer briefly exists in the + # host namespace before being moved; a shared name ("vhost") lets parallel + # wan.net.vmix@* starts steal each other's peer ends, cross-wiring namespaces. + vethInNSToHost.tempIface = "vh-${wanCfg.spaceName}"; vethOnHostToNS.iface = "vn-${wanCfg.spaceName}"; vethOnHostToNS.ipv4.address = calc.cidr.host 1 wanCfg.ipv4.range; vethInNSToHost.ipv4.address = calc.cidr.host 2 wanCfg.ipv4.range; @@ -180,8 +184,9 @@ let portForwardRules = lib.concatStringsSep "\n" (lib.mapAttrsToList (hostIPnPort: nsPort: "iptables -t nat -A PREROUTING -p tcp --dport ${hostIPnPort} -j DNAT --to-destination ${vethInNSToHost.ipv4.address}:${toString nsPort}") wanCfg.forwardPorts); createWanCommands = '' - ip link add ${vethOnHostToNS.iface} type veth peer name ${vethInNSToHost.iface} - ip link set ${vethInNSToHost.iface} netns ${wanCfg.spaceName}.vmix + ip link add ${vethOnHostToNS.iface} type veth peer name ${vethInNSToHost.tempIface} + ip link set ${vethInNSToHost.tempIface} netns ${wanCfg.spaceName}.vmix + ip netns exec ${wanCfg.spaceName}.vmix ip link set ${vethInNSToHost.tempIface} name ${vethInNSToHost.iface} ip address add ${vethOnHostToNS.ipv4.address}/${networkPrefix} dev ${vethOnHostToNS.iface} ip netns exec ${wanCfg.spaceName}.vmix ip address add ${vethInNSToHost.ipv4.address}/${networkPrefix} dev ${vethInNSToHost.iface} From 9784736260c8300d5d63b3f77a4690904b2cc5c0 Mon Sep 17 00:00:00 2001 From: Git Sagar Date: Thu, 23 Jul 2026 20:23:54 -0300 Subject: [PATCH 2/2] fix: manual-net-ifaces.d ordering never applied, vmbr0 randomly unconfigured After= was placed in [Service], which systemd ignores ('Unknown key After'), so the interfaces.d merge + ifreload raced networking.service on every boot. On losing boots vmbr0 never ran DHCP and the proxmox guest came up without its LAN IP (bridging still worked, so inner VMs stayed reachable while the PVE host itself was not). Move After= to [Unit] ordering against networking.service, and mkdir /run/network in ExecStartPre: the image-build workaround for ifupdown2#276 doesn't survive boots since /run is tmpfs. Co-Authored-By: Claude Fable 5 --- lib/images/linux/debian/templates.nix | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/images/linux/debian/templates.nix b/lib/images/linux/debian/templates.nix index 1fffeb3..af90a77 100644 --- a/lib/images/linux/debian/templates.nix +++ b/lib/images/linux/debian/templates.nix @@ -50,11 +50,18 @@ with scriptsNFiles; # proxmox makes it very hard to manually add interfaces directly on /etc/network/interfaces while the pve services are not running # it also doesn't pick up files in interfaces.d # so manually do that via service after boot + # After= must live in [Unit] — in [Service] systemd ignores it, leaving the + # merge/ifreload racing networking.service (and ifreload fails outright if it + # runs before /run/network exists, see ifupdown2#276). mergeNetIfacesDService = pkgs.writeText "manual-net-ifaces.d.service" '' + [Unit] + After = networking.service + Wants = networking.service + [Service] Type = oneshot + ExecStartPre = /bin/mkdir -p /run/network ExecStart = /bin/bash -c "cat /etc/network/interfaces.d/* >> /etc/network/interfaces; rm /etc/network/interfaces.d/*; ifreload -a;" - After = network.target [Install] WantedBy = multi-user.target