wifi.* settings turn the gateway's own radios into the site AP (hostapd): SSIDs are defined once in wifi.networks and act as untagged access ports of their VLAN (each BSS joins br0 with the VLAN's PVID), radios pick what they broadcast, passphrases are vars prompts. Extra SSIDs on a radio get BSSIDs derived from its hardware address. A udev rule puts configured radios in AP mode on appearance, since networkd cannot bridge a station-mode interface and gives up before hostapd switches it. wifi.enable gates all of it. The VM test grows a hwsim radio with two SSIDs and a WPA3 station in its own netns that must get a Kea lease on the SSID's VLAN. Making the client deterministic (route metrics, loose rp-filter, a guard against a vacuous negative check) exposed that allowWan was never enforced: networking.nat opens forward-to-WAN for all of its internalInterfaces, which listed every VLAN. It now lists only the allowWan VLANs; the duplicate custom rule is gone. No behavioural change on gw-cnx-1 (all its VLANs allow WAN).
68 lines
2.4 KiB
Nix
68 lines
2.4 KiB
Nix
# Site gateway (OPNsense replacement) for the Topton 1U boxes, as a clan
|
|
# service: PPPoE WAN, VLAN-filtering bridge over the LAN ports, per-VLAN
|
|
# DHCP/DNS, firewall/NAT, and the optional Omada controller / internal proxy.
|
|
#
|
|
# Exported as `clan.modules.router` of this flake; used here with
|
|
# `module.input = "self"` and from other clans with `module.input =
|
|
# "<this flake's input name>"`. README.md has the consumer view. A site joins
|
|
# through the inventory:
|
|
#
|
|
# inventory.instances.router = {
|
|
# module = { name = "router"; input = "self"; };
|
|
# roles.default.settings.mesh.subnet = ...; # fleet-wide
|
|
# roles.default.machines.gw-<city>-<n>.settings = { site = ...; ... };
|
|
# };
|
|
#
|
|
# The settings schema lives in interface.nix. Implementation files that need
|
|
# the settings are functions `{ settings }: <NixOS module>`; the evaluated
|
|
# settings are handed in with importApply so nothing goes through
|
|
# machine-level options. The rest are plain NixOS modules.
|
|
{ lib, ... }:
|
|
{
|
|
_class = "clan.service";
|
|
manifest.name = "router";
|
|
manifest.description = "Site gateway: PPPoE WAN, VLAN bridge, DHCP/DNS, firewall/NAT";
|
|
manifest.categories = [ "Network" ];
|
|
manifest.readme = builtins.readFile ./README.md;
|
|
|
|
roles.default = {
|
|
description = "Turns the machine into the site's router (one instance per machine).";
|
|
interface = ./interface.nix;
|
|
|
|
perInstance =
|
|
{ settings, machine, ... }:
|
|
{
|
|
nixosModule.imports = [
|
|
./ipv6.nix
|
|
]
|
|
# The proxy's TSIG secret is shared with the nameserver (acme-secret.nix).
|
|
++ lib.optional settings.proxy.enable (import ./acme-secret.nix machine.name)
|
|
++ map (file: lib.modules.importApply file { inherit settings; }) [
|
|
./network.nix
|
|
./pppoe.nix
|
|
./firewall.nix
|
|
./dns-dhcp.nix
|
|
./crowdsec.nix
|
|
./omada.nix
|
|
./proxy.nix
|
|
./iperf.nix
|
|
./speedtest.nix
|
|
./wifi.nix
|
|
];
|
|
};
|
|
};
|
|
|
|
# A machine has exactly one WAN port and one VLAN layout; two instances would
|
|
# both claim br0/ppp0 and fight over Kea/Blocky/nftables.
|
|
perMachine =
|
|
{ instances, machine, ... }:
|
|
{
|
|
nixosModule.assertions = [
|
|
{
|
|
assertion = lib.length (lib.attrNames instances) == 1;
|
|
message = "router: ${machine.name} is a gateway in several instances (${lib.concatStringsSep ", " (lib.attrNames instances)}); a machine can only be one router.";
|
|
}
|
|
];
|
|
};
|
|
}
|