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>
This commit is contained in:
2026-10-27 17:54:52 +01:00
co-authored by Claude Sonnet 5.5
parent 6038367252
commit 6015ee2e6d
13 changed files with 931 additions and 86 deletions
+4 -17
View File
@@ -58,28 +58,14 @@ let
assert mss.strip() == "mss 1452", f"expected the SYN clamped to mss 1452, got {mss!r}"
'';
# `fail` in the test script also passes on "command not found", so make sure
# the tools the script relies on are really there.
tools = { pkgs, ... }: {
environment.systemPackages = with pkgs; [
dnsutils
netcat-openbsd
curl
tcpdump
];
};
common = import ./common.nix;
in
pkgs.testers.runNixOSTest {
name = "router-${if pppoe then "pppoe" else "basic"}";
defaults.imports = [ tools ];
defaults.imports = [ common.tools ];
# `driverInteractive` uses the full QEMU, which would open a window per VM;
# `nix run .#lab` wants the consoles in the terminal instead.
interactive.defaults.virtualisation.qemu.options = [
"-display"
"none"
];
interactive.defaults = common.interactiveDefaults;
nodes = {
# Plays the ISP: hands out one WAN lease (v4 and v6) plus a delegated /64,
@@ -273,6 +259,7 @@ pkgs.testers.runNixOSTest {
};
lans.main = {
interface = "eth2";
internet = true;
ipv4 = {
address = "192.168.10.1";
};