Files
nixos-router/modules/router/lib.nix
T
nilsandClaude Sonnet 5.5 96e5b7b859 Derive LAN subnets and validate addressing at build time
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>
2026-10-27 15:00:24 +01:00

50 lines
1.4 KiB
Nix

# Small IPv4 helpers (addresses are plain 32-bit integers inside Nix).
# Parsing returns null on malformed input so callers can turn that into an
# assertion message instead of an evaluation crash.
{ lib }:
let
pow2 = n: if n == 0 then 1 else 2 * pow2 (n - 1);
in
rec {
ipv4 = rec {
parse =
s:
let
m = builtins.match "([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})" s;
octets = map lib.toInt m;
in
if m == null || lib.any (o: o > 255) octets then
null
else
lib.foldl' (acc: o: acc * 256 + o) 0 octets;
format =
n:
lib.concatMapStringsSep "." (shift: toString (lib.mod (n / pow2 shift) 256)) [
24
16
8
0
];
netMask = prefixLength: 4294967295 - (pow2 (32 - prefixLength) - 1);
# Network address (integer) of `address` under `prefixLength`.
networkOf = address: prefixLength: builtins.bitAnd address (netMask prefixLength);
# "192.168.10.1" /24 -> "192.168.10.0/24"
cidr = address: prefixLength: "${format (networkOf (parse address) prefixLength)}/${toString prefixLength}";
inSubnet =
{ address, prefixLength }: ip: networkOf ip prefixLength == networkOf address prefixLength;
# Whether two subnets share any address (one contains the other's network).
overlaps =
a: b:
let
p = lib.min a.prefixLength b.prefixLength;
in
networkOf a.address p == networkOf b.address p;
};
}