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
+19 -5
View File
@@ -6,6 +6,14 @@ let
set = ifaces: "{ ${lib.concatMapStringsSep ", " (i: ''"${i}"'') ifaces} }";
lanIfaces = map (l: l.interface) lans;
mgmtIfaces = map (l: l.interface) (lib.filter (l: l.allowManagement) lans);
uplink4 = cfg.wan.ipv4.interface;
pppoe = cfg.wan.ipv4.type == "pppoe";
# IPv4 leaves via the PPP device, IPv6 via the physical uplink.
uplinks = lib.unique [
cfg.wan.interface
uplink4
];
mss = cfg.wan.ipv4.pppoe.mtu - 40;
dhcpIfaces = map (l: l.interface) (lib.filter (l: l.dhcp.enable) lans);
in
{
@@ -34,12 +42,13 @@ in
iifname ${set lanIfaces} icmp type echo-request accept
iifname ${set lanIfaces} icmpv6 type echo-request accept
iifname "${cfg.wan.interface}" udp dport 68 accept
${lib.optionalString (!pppoe) ''iifname "${cfg.wan.interface}" udp dport 68 accept''}
${lib.optionalString cfg.wan.ipv6.enable ''iifname "${cfg.wan.interface}" udp dport 546 accept''}
iifname ${set lanIfaces} udp dport 53 accept
iifname ${set lanIfaces} tcp dport 53 accept
${lib.optionalString (dhcpIfaces != [ ]) ''iifname ${set dhcpIfaces} udp dport 67 accept''}
${lib.optionalString (mgmtIfaces != [ ]) ''iifname ${set mgmtIfaces} tcp dport 22 accept''}
${lib.optionalString (dhcpIfaces != [ ]) "iifname ${set dhcpIfaces} udp dport 67 accept"}
${lib.optionalString (mgmtIfaces != [ ]) "iifname ${set mgmtIfaces} tcp dport 22 accept"}
}
chain forward {
@@ -50,7 +59,12 @@ in
icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
iifname ${set lanIfaces} oifname "${cfg.wan.interface}" accept
${lib.optionalString pppoe ''
# Clamp MSS to the PPP MTU in both directions (never raised).
iifname "${uplink4}" tcp flags syn tcp option maxseg size > ${toString mss} tcp option maxseg size set ${toString mss}
oifname "${uplink4}" tcp flags syn tcp option maxseg size > ${toString mss} tcp option maxseg size set ${toString mss}
''}
iifname ${set lanIfaces} oifname ${set uplinks} accept
}
'';
};
@@ -60,7 +74,7 @@ in
content = ''
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "${cfg.wan.interface}" masquerade
oifname "${uplink4}" masquerade
}
'';
};