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.
This commit is contained in:
Ryan Yin committed 2026-10-09 18:30:13 +08:00
1 parent 4c165ad7da
commit 9cf0a92a30
2 files changed
+21 -8

No files matched your search

+19 -8
View File
@@ -14,7 +14,7 @@
... ...
}: }:
let let
inherit (networking) proxyGateway proxyGateway6; inherit (networking) proxyGateway proxyGateway6 clusterULA6;
inherit (networking.hostsAddr.${hostName}) ipv4; inherit (networking.hostsAddr.${hostName}) ipv4;
ipv4WithMask = "${ipv4}/24"; ipv4WithMask = "${ipv4}/24";
@@ -29,6 +29,9 @@ let
if builtins.stringLength h == 1 then "0${h}" else h; 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)}"; 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 # 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. # are 192.168.5.0/24, so the last octet is unique per host.
tapId = "vm${builtins.elemAt octets 3}"; tapId = "vm${builtins.elemAt octets 3}";
@@ -94,17 +97,18 @@ in
systemd.network.networks."10-${hostName}" = { systemd.network.networks."10-${hostName}" = {
matchConfig.MACAddress = [ mac ]; matchConfig.MACAddress = [ mac ];
networkConfig = { networkConfig = {
Address = [ ipv4WithMask ]; Address = [
ipv4WithMask
ula
];
DNS = [ proxyGateway ]; DNS = [ proxyGateway ];
DHCP = "ipv6"; # No SLAAC/DHCPv6: the WAN /64 churns and Cilium keeps probing whatever it
IPv6AcceptRA = true; # registered at agent start. The ULA above is the node's IPv6 identity.
DHCP = false;
IPv6AcceptRA = false;
IPv6PrivacyExtensions = false; IPv6PrivacyExtensions = false;
LinkLocalAddressing = "ipv6"; 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 = [ routes = [
{ {
Destination = "0.0.0.0/0"; Destination = "0.0.0.0/0";
@@ -119,6 +123,13 @@ in
linkConfig.RequiredForOnline = "routable"; 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 # journald on tmpfs -- keep it small
services.journald.settings.Journal = { services.journald.settings.Journal = {
SystemMaxUse = "512M"; SystemMaxUse = "512M";
+2
View File
@@ -8,6 +8,8 @@ rec {
# it's a subrouter with a transparent proxy # it's a subrouter with a transparent proxy
proxyGateway = "192.168.5.178"; proxyGateway = "192.168.5.178";
proxyGateway6 = "fe80::8"; proxyGateway6 = "fe80::8";
# node identity must not ride the WAN /64: the ISP retires it, Cilium never refreshes it.
clusterULA6 = "fd05:5::";
nameservers = [ nameservers = [
# IPv4 # IPv4
"119.29.29.29" # DNSPod https://www.dnspod.cn/Products/publicdns "119.29.29.29" # DNSPod https://www.dnspod.cn/Products/publicdns