vmsilo/modules/networking.nix
Davíð Steinn Geirsson 0f52236f80 Disable systemd-networkd-wait-online.service
systemd-networkd-wait-online interacts badly with non-routable interfaces. It
ends up slowing down system config activation waiting for routes that never
appear. Ignoring interfaces does not reliably fix this. Just disable the damn
thing until I find a better solution.
2026-03-17 11:20:26 +00:00

196 lines
6.4 KiB
Nix

# Host network configuration for vmsilo NixOS module
# TAP interfaces and NAT
{
config,
lib,
...
}:
let
cfg = config.programs.vmsilo;
helpers = import ./lib/helpers.nix { inherit lib; };
inherit (helpers)
parseCIDR
idxToIfIndex
generateMac
sortedInterfaceList
makeTapName
assignVmIds
;
vms = assignVmIds cfg.nixosVms;
# NOTE: getEffectiveInterfaces is intentionally duplicated in scripts.nix and services.nix.
# It cannot live in helpers.nix (which has no config access) and the three modules
# don't share a common let-binding scope. Keep the copies in sync.
getEffectiveInterfaces =
vm: vm.network.interfaces // (cfg._internal.netvmInjections.${vm.name}.interfaces or { });
# Get effective MAC for an interface (uses user-specified interface name)
getEffectiveIfaceMac =
vm: ifName: iface:
if iface.macAddress != null then iface.macAddress else generateMac vm.name ifName;
# Collect all TAP interfaces across all VMs
# Uses sortedInterfaceList for deterministic PCI slot assignment
allTapInterfaces = lib.flatten (
map (
vm:
lib.imap0 (
idx: entry:
let
ifName = entry.name;
iface = entry.value;
in
lib.optional (iface.type == "tap") {
inherit
vm
idx
ifName
iface
;
tapName =
if iface.tap != null && iface.tap.name != null then
iface.tap.name
else
makeTapName vm.name vm.id (idxToIfIndex idx);
}
) (sortedInterfaceList (getEffectiveInterfaces vm))
) vms
);
in
{
config = lib.mkIf cfg.enable {
# Enable IP forwarding
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
# Bridge changes are not reliable with the scripted approach
networking.useNetworkd = true;
# systemd-networkd-wait-online interacts badly with non-routable interfaces. It
# ends up slowing down system config activation waiting for routes that never
# appear. Ignoring interfaces does not reliably fix this. Just disable the damn
# thing until I find a better solution.
systemd.network.wait-online.enable = false;
# Configure vmsilo TAP/bridge network units:
# - Not required for online (VMs may not be running during nixos-rebuild)
# - ConfigureWithoutCarrier: assign static addresses even when link has no
# carrier (VM not running), so networkd reaches "configured" state
# immediately instead of getting stuck in "configuring"
# - No IPv6 autoconfiguration (prevents unwanted host addresses on
# VM-to-VM interfaces)
systemd.network.networks =
let
bridgedTaps = builtins.filter (
t: t.iface.tap != null && t.iface.tap.bridge != null
) allTapInterfaces;
bridgeNames = lib.unique (map (t: t.iface.tap.bridge) bridgedTaps);
unitConfig = {
linkConfig.RequiredForOnline = "no";
networkConfig.ConfigureWithoutCarrier = true;
networkConfig.IPv6AcceptRA = false;
networkConfig.LinkLocalAddressing = "no";
};
in
lib.listToAttrs (
map (t: lib.nameValuePair "40-${t.tapName}" unitConfig) allTapInterfaces
++ map (brName: lib.nameValuePair "40-${brName}" unitConfig) bridgeNames
);
# TAP interfaces for VMs with tap-type network interfaces.
# Also includes empty entries for bridge devices so that networkd generates
# .network files for them (without a .network file, networkd won't bring
# the bridge link up).
networking.interfaces =
let
tapEntries = map (
t:
lib.nameValuePair t.tapName (
{
virtual = true;
virtualOwner = cfg.user;
}
// lib.optionalAttrs (t.iface.tap != null && t.iface.tap.hostAddress != null) {
ipv4.addresses =
let
parsed = parseCIDR t.iface.tap.hostAddress;
in
[
{
address = parsed.ip;
prefixLength = parsed.prefix;
}
];
}
)
) allTapInterfaces;
bridgedTaps = builtins.filter (
t: t.iface.tap != null && t.iface.tap.bridge != null
) allTapInterfaces;
bridgeEntries = map (brName: lib.nameValuePair brName { }) (
lib.unique (map (t: t.iface.tap.bridge) bridgedTaps)
);
in
lib.listToAttrs (tapEntries ++ bridgeEntries);
# Bridge configuration for TAP interfaces with tap.bridge set
networking.bridges =
let
bridgedTaps = builtins.filter (
t: t.iface.tap != null && t.iface.tap.bridge != null
) allTapInterfaces;
bridgeNames = lib.unique (map (t: t.iface.tap.bridge) bridgedTaps);
in
lib.listToAttrs (
map (
brName:
lib.nameValuePair brName {
interfaces = map (t: t.tapName) (builtins.filter (t: t.iface.tap.bridge == brName) bridgedTaps);
}
) bridgeNames
);
# nftables NAT and forward rules for VMs routing through the host (network.netvm = "host").
# Same rules as mkNetvmGuestConfig in netvm.nix, but applied on the host using TAP names.
networking.nftables =
let
hostNetvmTaps = builtins.filter (
t: t.vm.network.netvm == "host" && t.ifName == "upstream"
) allTapInterfaces;
in
lib.mkIf (hostNetvmTaps != [ ]) {
enable = true;
tables = {
vmsilo-netvm-nat = {
family = "ip";
content =
let
ifSet = "{ ${lib.concatMapStringsSep ", " (t: ''"${t.tapName}"'') hostNetvmTaps} }";
in
''
chain postrouting {
type nat hook postrouting priority srcnat;
iifname ${ifSet} oifname != "lo" masquerade
}
'';
};
vmsilo-netvm-filter = {
family = "inet";
content =
let
ifSet = "{ ${lib.concatMapStringsSep ", " (t: ''"${t.tapName}"'') hostNetvmTaps} }";
in
''
chain forward {
type filter hook forward priority filter;
policy accept;
ct state established,related accept
iifname ${ifSet} oifname ${ifSet} drop
}
'';
};
};
};
};
}