Add an IPv4 helper lib (parse, netmask, cidr, overlap) and derive router.lans.<name>.ipv4.subnet from address and prefixLength instead of requiring it twice. New assertions reject invalid addresses, DHCP pools that are reversed, outside the subnet or contain the router address, and overlapping LANs. Add `lib` unit tests and an `assertions` check that evaluates broken configs and compares the exact error messages. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
91 lines
2.6 KiB
Nix
91 lines
2.6 KiB
Nix
{ 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.";
|
|
};
|
|
};
|
|
}
|