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>
93 lines
3.3 KiB
Nix
93 lines
3.3 KiB
Nix
{ config, lib, ... }:
|
|
let
|
|
cfg = config.router;
|
|
lans = lib.attrValues cfg.lans;
|
|
|
|
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
|
|
{
|
|
config = lib.mkIf cfg.enable {
|
|
# The generated ruleset replaces networking.firewall entirely.
|
|
networking.firewall.enable = false;
|
|
networking.nftables.enable = true;
|
|
|
|
networking.nftables.tables.router = {
|
|
family = "inet";
|
|
content = ''
|
|
chain input {
|
|
type filter hook input priority filter; policy drop;
|
|
|
|
iifname "lo" accept
|
|
ct state established,related accept
|
|
ct state invalid drop
|
|
|
|
# Error types and ND are required on every interface (WAN included);
|
|
# echo is only answered on LANs.
|
|
icmp type { destination-unreachable, time-exceeded, parameter-problem } accept
|
|
icmpv6 type {
|
|
destination-unreachable, packet-too-big, time-exceeded, parameter-problem,
|
|
nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert
|
|
} accept
|
|
iifname ${set lanIfaces} icmp type echo-request accept
|
|
iifname ${set lanIfaces} icmpv6 type echo-request 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"}
|
|
}
|
|
|
|
chain forward {
|
|
type filter hook forward priority filter; policy drop;
|
|
|
|
ct state established,related accept
|
|
ct state invalid drop
|
|
|
|
icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } 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
|
|
}
|
|
'';
|
|
};
|
|
|
|
networking.nftables.tables.router-nat = {
|
|
family = "ip";
|
|
content = ''
|
|
chain postrouting {
|
|
type nat hook postrouting priority srcnat; policy accept;
|
|
oifname "${uplink4}" masquerade
|
|
}
|
|
'';
|
|
};
|
|
|
|
services.openssh = {
|
|
enable = true;
|
|
openFirewall = false;
|
|
settings = {
|
|
PasswordAuthentication = false;
|
|
KbdInteractiveAuthentication = false;
|
|
PermitRootLogin = "prohibit-password";
|
|
};
|
|
};
|
|
};
|
|
}
|