{ 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 = "Name of the uplink interface."; }; }; 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"; }; }; }; 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."; }; }; }