# router Turns a machine with several NICs into a site gateway: PPPoE WAN (ISP credentials via vars prompts), a VLAN-filtering bridge over the LAN ports with one L3 interface per VLAN, Kea DHCP and Blocky DNS per VLAN, nftables firewall/NAT, DHCPv6-PD, CrowdSec, an iperf3 server and a WAN speed-test timer. Optional: a Wi-Fi access point on the router's own radios (hostapd), the TP-Link Omada controller (podman) and an internal Caddy reverse proxy with a real wildcard certificate (ACME DNS-01). Addressing convention: a site owns `10..0.0/16`; VLAN `` defaults to `10...0/24`, router at `.1`, DHCP pool `.100-.199`. The `mgmt` and `lan` VLANs are mandatory. Trust model: mgmt reaches everything; other VLANs get router DNS/DHCP and (with `allowWan`) the internet, no inter-VLAN; WAN nothing inbound; the admin mesh (`mesh.subnet`) gets SSH, metrics, iperf3 and the Omada UI. ## Usage from another clan ```nix # flake.nix inputs.cnx-network.url = "git+https:///B4L/cnx-network-clan"; # clan.nix inventory.instances.router = { module = { name = "router"; input = "cnx-network"; }; roles.default.settings.mesh.subnet = "fd..::/88"; # your admin overlay roles.default.machines.gw-1.settings = { site = "ams"; siteId = 1; wan.interface = "enp1s0"; wan.vlanId = 10; # or null for untagged PPPoE trunkPorts = [ "enp2s0" ]; accessPorts.enp4s0 = "mgmt"; # untagged on-site recovery port # stagingPort = "enp3s0"; # DHCP uplink into the old LAN until cutover vlans = { mgmt.id = 10; lan.id = 20; iot = { id = 40; allowWan = false; }; }; }; }; ``` Then `clan vars generate gw-1` prompts for the PPPoE username/password. ### Wi-Fi access point If the box has wireless cards, the router can be the site's AP. An SSID is defined once and behaves like an untagged access port of its VLAN; radios choose what to broadcast, so a dual-band card serves the same SSID twice: ```nix wifi = { enable = true; countryCode = "TH"; networks = { home.vlan = "lan"; # WPA3 with WPA2 fallback things = { vlan = "iot"; security = "wpa2"; }; # legacy IoT guest = { vlan = "guest"; isolateClients = true; }; }; radios = { wlp5s0 = { band = "2g"; channel = 6; macAddress = "…"; networks = [ "home" "things" ]; }; wlp6s0 = { band = "5g"; channel = 36; networks = [ "home" ]; }; }; }; ``` Passphrases are vars prompts (`wifi--passphrase`), asked once at `clan vars generate`. A radio broadcasting more than one SSID needs its hardware `macAddress`: hostapd wants a fixed BSSID per extra SSID, derived from it. `security = "wpa3-transition"` (the default) offers SAE and WPA2-PSK-SHA256; devices that only speak classic WPA2-PSK need `security = "wpa2"`. ### Internal proxy `proxy.enable` serves `..` under a wildcard certificate obtained via RFC 2136 DNS-01 against `proxy.acme.nameserver`. The gateway signs updates with TSIG key `acme_`, whose secret is the shared `dns-acme--secret` generator declared by this service. The nameserver machine must declare the same generator so both sides hold one secret — import `acme-secret.nix` from this directory with the gateway's name: ```nix imports = [ (import "${inputs.cnx-network}/modules/clan/router/acme-secret.nix" "gw-1") ]; ``` and load the key with an acl scoped to `_acme-challenge.`. The service does not open the WAN to anything; reach gateways over your mesh. One instance per machine.