Add a static ULA per LAN, announced via SLAAC and RDNSS

lans.<n>.ipv6.address (a ULA /64) is assigned to the LAN, announced
with an explicit prefix (networkd only announces the delegated prefix
when prefix delegation is on) and advertised as RDNSS; Unbound listens
and answers on it. Checked at build time: valid IPv6 CIDR, /64, fc00::/7.

Covered by the onestick VM test (client SLAAC address, ping and DNS over
the ULA, RA captured on the wire), assertion cases and a unit test for
the /64 prefix helper.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 15:48:47 +01:00
co-authored by Claude Sonnet 5.5
parent 70dddad206
commit c395a43a78
9 changed files with 150 additions and 7 deletions
+30 -3
View File
@@ -129,7 +129,10 @@ in
lib.nameValuePair "20-lan-${name}" (
{
matchConfig.Name = lan.device;
address = [ "${lan.ipv4.address}/${toString lan.ipv4.prefixLength}" ];
address = [
"${lan.ipv4.address}/${toString lan.ipv4.prefixLength}"
]
++ lib.optional (lan.ipv6.address != null) lan.ipv6.address;
networkConfig = {
ConfigureWithoutCarrier = true;
# Never accept RAs from the inside.
@@ -140,9 +143,22 @@ in
}
// lib.optionalAttrs lan.ipv6.pd {
DHCPPrefixDelegation = true;
}
// lib.optionalAttrs (lan.ipv6.pd || lan.ipv6.address != null) {
IPv6SendRA = true;
};
}
// lib.optionalAttrs (lan.ipv6.address != null) {
# With prefix delegation networkd announces only the delegated
# prefix, so announce the ULA explicitly (SLAAC, on-link).
ipv6Prefixes = [
{
Prefix = (import ./lib.nix { inherit lib; }).ipv6.prefix64 lan.ipv6.address;
OnLink = true;
AddressAutoconfiguration = true;
}
];
}
// lib.optionalAttrs lan.ipv6.pd {
dhcpPrefixDelegationConfig = {
UplinkInterface = v6dev;
@@ -150,8 +166,19 @@ in
Announce = true;
Assign = true;
};
# No RDNSS yet: the global address is unknown at evaluation time.
ipv6SendRAConfig.EmitDNS = false;
}
// lib.optionalAttrs (lan.ipv6.pd || lan.ipv6.address != null) {
# RDNSS points hosts at the router's resolver on its stable ULA; the
# delegated global address is unknown at evaluation time, so without
# a static `ipv6.address` there is nothing to announce.
ipv6SendRAConfig =
if lan.ipv6.address != null then
{
EmitDNS = true;
DNS = [ (builtins.head (lib.splitString "/" lan.ipv6.address)) ];
}
else
{ EmitDNS = false; };
}
)
) cfg.lans