From f21f388a5fcc1b8377faa6eee09cf14a3a494567 Mon Sep 17 00:00:00 2001 From: kurogeek Date: Mon, 3 Aug 2026 18:13:34 +0700 Subject: [PATCH] mob next [ci-skip] [ci skip] [skip ci] lastFile:modules/clan/gw-router/default.nix --- modules/clan/gw-router/default.nix | 121 +++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/modules/clan/gw-router/default.nix b/modules/clan/gw-router/default.nix index beeaca3..c0a742e 100644 --- a/modules/clan/gw-router/default.nix +++ b/modules/clan/gw-router/default.nix @@ -11,6 +11,83 @@ interface = { lib, config, ... }: + let + vlanModule = + { name, config, ... }: + let + octet = toString config.id; + in + { + options = { + id = lib.mkOption { + type = lib.types.ints.between 1 4094; + description = "802.1Q VLAN id."; + }; + address = lib.mkOption { + type = lib.types.str; + example = "10.0.10.1"; + description = "Router address on this VLAN."; + }; + prefixLength = lib.mkOption { + type = lib.types.ints.between 8 30; + default = 24; + }; + subnet = lib.mkOption { + type = lib.types.str; + example = "10.0.10.0/24"; + description = "The VLAN's network in CIDR form (must contain `address`)."; + }; + dhcp = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + pool = { + from = lib.mkOption { + type = lib.types.str; + example = "10.0.10.100"; + }; + to = lib.mkOption { + type = lib.types.str; + example = "10.0.10.199"; + }; + }; + leaseTime = lib.mkOption { + type = lib.types.ints.positive; + default = 86400; + description = '' + Lease validity in seconds. Lower it for high-churn networks, + e.g. public-WiFi guest VLANs (3600-7200), so the pool recycles. + ''; + }; + fixedIPs = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule { + options = { + hwAddress = lib.mkOption { + type = lib.types.str; + example = "aa:bb:cc:dd:ee:ff"; + description = "Client MAC address."; + }; + ipAddress = lib.mkOption { + type = lib.types.str; + description = "Fixed address handed to this client (inside the VLAN's subnet, outside the pool)."; + }; + }; + } + ); + default = { }; + description = "Static DHCP leases; the attribute name becomes the client's hostname."; + }; + }; + allowWAN = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether clients on this VLAN may reach the internet."; + }; + }; + }; + in { options = { wan = { @@ -47,6 +124,25 @@ description = "Interface pppd dials on (the WAN port or its ISP VLAN)."; }; }; + + vlans = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule vlanModule); + description = "VLANs setup for this router"; + }; + + trunkPorts = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "LAN ports carrying all VLANs tagged (incl. any 10G SFP+ ports)."; + }; + + accessPorts = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = '' + Ports acting as untagged access ports on a single VLAN (port name -> + VLAN name). Frames are untagged on the wire; the bridge tags them with + the VLAN's PVID. Use for an always-available on-site mgmt port. + ''; + }; }; }; @@ -64,6 +160,31 @@ in { + networking.useNetworkd = true; + networking.useDHCP = false; + systemd.network.enable = true; + + systemd.network.netdevs = { + "20-br0" = { + netdevConfig = { + Name = "br0"; + Kind = "bridge"; + }; + bridgeConfig = { + VLANFiltering = true; + }; + }; + } + // lib.optionalAttrs (settings.wan.vlanId != null) { + "15-wan-vlan" = { + netdevConfig = { + Name = "wan-vlan"; + Kind = "vlan"; + }; + vlanConfig.Id = settings.wan.vlanId; + }; + }; + environment.systemPackages = with pkgs; [ tcpdump mtr