From 9cf0a92a30e8f33a83b14d302537ee1a7e107a24 Mon Sep 17 00:00:00 2001 From: Ryan Yin Date: Fri, 9 Oct 2026 17:07:43 +0800 Subject: [PATCH] fix(microvm): pin guest IPv6 identity to a cluster ULA The ISP retires delegated prefixes, so a guest that registers its WAN prefix loses it and Cilium never refreshes the node address. Derive the identity from the IPv4 octets inside fd05:5::/64 and trust that ULA in the guest firewall. --- lib/genMicrovmGuestModule.nix | 27 +++++++++++++++++++-------- vars/networking.nix | 2 ++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/lib/genMicrovmGuestModule.nix b/lib/genMicrovmGuestModule.nix index e4d9cbc5..878e990d 100644 --- a/lib/genMicrovmGuestModule.nix +++ b/lib/genMicrovmGuestModule.nix @@ -14,7 +14,7 @@ ... }: let - inherit (networking) proxyGateway proxyGateway6; + inherit (networking) proxyGateway proxyGateway6 clusterULA6; inherit (networking.hostsAddr.${hostName}) ipv4; ipv4WithMask = "${ipv4}/24"; @@ -29,6 +29,9 @@ let if builtins.stringLength h == 1 then "0${h}" else h; mac = "02:00:00:00:${toHex (builtins.elemAt octets 2)}:${toHex (builtins.elemAt octets 3)}"; + # Same octets as the MAC: 192.168.5.111 -> fd05:5::05:6f. Survives ISP prefix changes. + ula = pkgs.lib.toLower "${clusterULA6}${toHex (builtins.elemAt octets 2)}:${toHex (builtins.elemAt octets 3)}/64"; + # tap iface name on the host (IFNAMSIZ is 16, so <= 15 chars). All homelab VMs # are 192.168.5.0/24, so the last octet is unique per host. tapId = "vm${builtins.elemAt octets 3}"; @@ -94,17 +97,18 @@ in systemd.network.networks."10-${hostName}" = { matchConfig.MACAddress = [ mac ]; networkConfig = { - Address = [ ipv4WithMask ]; + Address = [ + ipv4WithMask + ula + ]; DNS = [ proxyGateway ]; - DHCP = "ipv6"; - IPv6AcceptRA = true; + # No SLAAC/DHCPv6: the WAN /64 churns and Cilium keeps probing whatever it + # registered at agent start. The ULA above is the node's IPv6 identity. + DHCP = false; + IPv6AcceptRA = false; IPv6PrivacyExtensions = false; LinkLocalAddressing = "ipv6"; }; - # Keep the static resolver below as the only DNS server; router advertisements and DHCPv6 - # still provide addresses and routes but must not add their own resolver entries. - dhcpV6Config.UseDNS = false; - ipv6AcceptRAConfig.UseDNS = false; routes = [ { Destination = "0.0.0.0/0"; @@ -119,6 +123,13 @@ in linkConfig.RequiredForOnline = "routable"; }; + # No ULA counterpart to the LAN IPv4 accept in + # modules/nixos/base/networking/firewall.nix, so Cilium's node health probe + # (TCP 4240) between nodes is dropped. Trust the node plane like the LAN. + networking.firewall.extraInputRules = pkgs.lib.mkAfter '' + ip6 saddr ${clusterULA6}/64 accept + ''; + # journald on tmpfs -- keep it small services.journald.settings.Journal = { SystemMaxUse = "512M"; diff --git a/vars/networking.nix b/vars/networking.nix index 70986e26..cc281402 100644 --- a/vars/networking.nix +++ b/vars/networking.nix @@ -8,6 +8,8 @@ rec { # it's a subrouter with a transparent proxy proxyGateway = "192.168.5.178"; proxyGateway6 = "fe80::8"; + # node identity must not ride the WAN /64: the ISP retires it, Cilium never refreshes it. + clusterULA6 = "fd05:5::"; nameservers = [ # IPv4 "119.29.29.29" # DNSPod https://www.dnspod.cn/Products/publicdns