Add IPv6 prefix delegation and PPPoE uplink

IPv6: router.wan.ipv6.enable mirrors the existing uplink config (DHCPv6 with
WithoutRA=solicit, delegated prefix). The ISP delegates a single /64, so
exactly one LAN may claim it via router.lans.<name>.ipv6.pd (asserted); other
LANs stay IPv4-only. SLAAC only, no NAT66.

PPPoE: router.wan.ipv4.type = "pppoe" runs pppd on an optional VLAN of the
uplink, with the password read from a file at service start (never the Nix
store). NAT, forwarding and MSS clamping (MTU 1492) use the derived PPP
device; native IPv6 stays on the untagged link.

Tests: basic.nix is parameterised by uplink type; the pppoe variant uses a
fake ISP with pppoe-server (VLAN 7, CHAP). Both variants cover IPv6 PD, SLAAC,
routing without NAT and WAN-deny. Assertion tests gain PD/PPPoE cases and a
full-system evaluation step.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 16:29:52 +01:00
co-authored by Claude Sonnet 5.5
parent 96e5b7b859
commit 6038367252
13 changed files with 671 additions and 72 deletions
+16
View File
@@ -79,6 +79,22 @@ in
assertion = !(lib.elem cfg.wan.interface (map (l: l.interface) lans));
message = "router: the WAN interface `${cfg.wan.interface}` is also used by a LAN.";
}
{
assertion = cfg.wan.ipv4.type != "pppoe" || cfg.wan.ipv4.pppoe.username != "";
message = "router: router.wan.ipv4.pppoe.username must be set for a PPPoE uplink.";
}
{
assertion = cfg.wan.ipv4.type != "pppoe" || cfg.wan.ipv4.pppoe.passwordFile != null;
message = "router: router.wan.ipv4.pppoe.passwordFile must be set for a PPPoE uplink.";
}
{
assertion = lib.length (lib.filter (l: l.ipv6.pd) lans) <= 1;
message = "router: more than one LAN sets ipv6.pd, but the delegated /64 cannot be split.";
}
{
assertion = !(lib.any (l: l.ipv6.pd) lans) || cfg.wan.ipv6.enable;
message = "router: a LAN sets ipv6.pd but router.wan.ipv6.enable is false.";
}
{
assertion =
let