{ config, lib, ... }: let inherit (lib) mkOption mkEnableOption types; rlib = import ./lib.nix { inherit lib; }; in { options.router = { enable = mkEnableOption "the router role"; wan = { interface = mkOption { type = types.str; example = "eth0"; description = '' Physical uplink interface. Native IPv6 (DHCPv6) runs on it untagged; with PPPoE it also carries the (optionally tagged) PPPoE session. ''; }; ipv4 = { type = mkOption { type = types.enum [ "dhcp" "pppoe" ]; default = "dhcp"; description = "How the router gets its IPv4 uplink."; }; pppoe = { vlan = mkOption { type = types.nullOr (types.ints.between 1 4094); default = null; description = "VLAN ID the ISP requires for PPPoE, or null for untagged."; }; username = mkOption { type = types.str; default = ""; description = "PPP authentication username."; }; passwordFile = mkOption { type = types.nullOr ( types.pathWith { absolute = true; inStore = false; } ); default = null; example = "/var/lib/secrets/pppoe-password"; description = '' File containing the PPP password, read at service start. Must not be in the Nix store (it would be world-readable). ''; }; mtu = mkOption { type = types.ints.between 576 1500; default = 1492; description = "PPP MTU/MRU; TCP MSS is clamped to match on forwarded traffic."; }; }; interface = mkOption { type = types.str; readOnly = true; internal = true; description = "Device IPv4 traffic leaves through (derived)."; }; }; ipv6.enable = mkEnableOption '' IPv6 on the uplink: DHCPv6 (soliciting even without RA flags) plus prefix delegation. The ISP is expected to delegate a single /64 ''; }; lans = mkOption { default = { }; description = "LAN segments served by this router, keyed by name."; type = types.attrsOf ( types.submodule ( { config, ... }: { options = { interface = mkOption { type = types.str; example = "eth1"; description = "Interface (or bridge/VLAN device) carrying this LAN."; }; ipv4 = { address = mkOption { type = types.str; example = "192.168.10.1"; description = "Router address on this LAN."; }; prefixLength = mkOption { type = types.ints.between 8 30; default = 24; }; subnet = mkOption { type = types.str; readOnly = true; description = "Network in CIDR notation, derived from `address` and `prefixLength`."; }; }; dhcp = { enable = mkOption { type = types.bool; default = true; }; pool = { start = mkOption { type = types.str; example = "192.168.10.100"; }; end = mkOption { type = types.str; example = "192.168.10.199"; }; }; }; ipv6.pd = mkOption { type = types.bool; default = false; description = '' Whether this LAN receives the /64 delegated on the uplink (assigned to the interface and announced via SLAAC). A /64 cannot be split, so at most one LAN may set this; the others stay IPv4-only. ''; }; allowManagement = mkOption { type = types.bool; default = true; description = "Whether clients on this LAN may reach SSH on the router."; }; }; # "invalid" is only ever seen when assertions.nix is already failing. config.ipv4.subnet = if rlib.ipv4.parse config.ipv4.address == null then "invalid" else rlib.ipv4.cidr config.ipv4.address config.ipv4.prefixLength; } ) ); }; dns.forwarders = mkOption { type = types.listOf types.str; default = [ ]; description = "Upstream resolvers. When empty, Unbound resolves recursively."; }; }; config.router.wan.ipv4.interface = if config.router.wan.ipv4.type == "pppoe" then "pppoe-wan" else config.router.wan.interface; }