Files
nixos-router/tests/lib.nix
T
nilsandClaude Sonnet 5.5 6015ee2e6d Add VLANs, firewall zones and inter-zone rules
LANs can sit on an 802.1Q VLAN (`vlan` on a trunk `interface`); networkd
creates the device and brings the trunk up. LANs belong to a named `zone`
(default: their own name). Traffic inside a zone is forwarded freely, traffic
between zones is denied unless a `router.firewall.rules` entry allows it
(protocol, ports, optional destination inside the target zone).

Internet access is now opt-in per LAN (`internet`, default false), replacing
the previous allow-out default. Assertions reject unknown zones, bad
destinations, duplicate devices, over-long device names and a WAN trunk.

Kea subnet IDs now come from the network address so adding LANs does not
renumber them and orphan persistent leases.

Tests: a five-VM `vlans` check (untagged + two VLANs, shared zone, one-way
rules, per-host destination restriction), new assertion cases, parseNet unit
tests, and shared test tooling in tests/common.nix.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 17:54:52 +01:00

163 lines
3.7 KiB
Nix

# Unit tests for modules/router/lib.nix, run at evaluation time.
{ pkgs }:
let
inherit (pkgs) lib;
r = import ../modules/router/lib.nix { inherit lib; };
inherit (r) ipv4;
ip = ipv4.parse;
failures = lib.runTests {
testParse = {
expr = ip "192.168.10.1";
expected = 3232238081;
};
testParseZero = {
expr = ip "0.0.0.0";
expected = 0;
};
testParseMax = {
expr = ip "255.255.255.255";
expected = 4294967295;
};
testParseBadOctet = {
expr = ip "192.168.10.256";
expected = null;
};
testParseShort = {
expr = ip "192.168.10";
expected = null;
};
testParseJunk = {
expr = ip "192.168.10.x";
expected = null;
};
testParseNetHost = {
expr = ipv4.parseNet "10.0.0.5";
expected = {
address = ip "10.0.0.5";
prefixLength = 32;
};
};
testParseNetCidr = {
expr = ipv4.parseNet "192.168.10.0/24";
expected = {
address = ip "192.168.10.0";
prefixLength = 24;
};
};
testParseNetZeroPrefix = {
expr = ipv4.parseNet "0.0.0.0/0";
expected = {
address = 0;
prefixLength = 0;
};
};
testParseNetPrefixTooLong = {
expr = ipv4.parseNet "10.0.0.0/33";
expected = null;
};
testParseNetBadAddress = {
expr = ipv4.parseNet "10.0.0.256/24";
expected = null;
};
testParseNetJunk = {
expr = ipv4.parseNet "10.0.0.0/x";
expected = null;
};
testFormatRoundTrip = {
expr = ipv4.format (ip "10.20.30.40");
expected = "10.20.30.40";
};
testFormatZero = {
expr = ipv4.format 0;
expected = "0.0.0.0";
};
testNetMask24 = {
expr = ipv4.format (ipv4.netMask 24);
expected = "255.255.255.0";
};
testNetMask20 = {
expr = ipv4.format (ipv4.netMask 20);
expected = "255.255.240.0";
};
testNetMask8 = {
expr = ipv4.format (ipv4.netMask 8);
expected = "255.0.0.0";
};
testCidr24 = {
expr = ipv4.cidr "192.168.10.77" 24;
expected = "192.168.10.0/24";
};
testCidr20 = {
expr = ipv4.cidr "172.16.37.1" 20;
expected = "172.16.32.0/20";
};
testCidr30 = {
expr = ipv4.cidr "10.0.0.6" 30;
expected = "10.0.0.4/30";
};
testInSubnetYes = {
expr = ipv4.inSubnet {
address = ip "192.168.10.1";
prefixLength = 24;
} (ip "192.168.10.200");
expected = true;
};
testInSubnetNo = {
expr = ipv4.inSubnet {
address = ip "192.168.10.1";
prefixLength = 24;
} (ip "192.168.11.1");
expected = false;
};
testOverlapNested = {
expr =
ipv4.overlaps
{
address = ip "10.0.0.1";
prefixLength = 8;
}
{
address = ip "10.1.2.3";
prefixLength = 24;
};
expected = true;
};
testOverlapSame = {
expr =
ipv4.overlaps
{
address = ip "192.168.10.1";
prefixLength = 24;
}
{
address = ip "192.168.10.9";
prefixLength = 24;
};
expected = true;
};
testOverlapDisjoint = {
expr =
ipv4.overlaps
{
address = ip "192.168.10.1";
prefixLength = 24;
}
{
address = ip "192.168.11.1";
prefixLength = 24;
};
expected = false;
};
};
in
pkgs.runCommand "router-lib-tests" { } (
if failures == [ ] then
"touch $out"
else
''
echo ${lib.escapeShellArg (lib.generators.toPretty { } failures)} >&2
exit 1
''
)