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>
163 lines
3.7 KiB
Nix
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
|
|
''
|
|
)
|