Compare commits

...

2 commits

Author SHA1 Message Date
9784736260 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 <noreply@anthropic.com>
2026-07-23 20:23:54 -03:00
2f8925d439 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-<ns> 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-<ns>) and rename to vhost only
after the move, making concurrent creation collision-free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 20:23:54 -03:00
2 changed files with 15 additions and 3 deletions

View file

@ -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

View file

@ -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}