Add untagged access ports and a staging uplink for gw-cnx-1
New cnx.router.accessPorts option pins a port untagged to one VLAN via bridge PVID/EgressUntagged; convention is the last copper port as an always-available mgmt recovery port. gw-cnx-1 port roles: enp1s0 WAN, enp2s0 trunk, enp3s0 temporary DHCP uplink into the old OPNsense LAN (back to trunk at cutover), enp4s0 untagged mgmt.
This commit is contained in:
@@ -13,7 +13,7 @@ Naming: `gw-<city>-<n>`, e.g. `gw-cnx-1`.
|
|||||||
| Function | Implementation |
|
| Function | Implementation |
|
||||||
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| WAN | PPPoE (`pppd`), per-site ISP credentials via clan vars prompts; `wan.vlanId` when the ISP tags the session (AIS: 10); `wan.macAddress` to clone the old router's MAC if the ISP has it pinned |
|
| WAN | PPPoE (`pppd`), per-site ISP credentials via clan vars prompts; `wan.vlanId` when the ISP tags the session (AIS: 10); `wan.macAddress` to clone the old router's MAC if the ISP has it pinned |
|
||||||
| LAN | VLAN-filtering bridge `br0` over the trunk ports (networkd) |
|
| LAN | VLAN-filtering bridge `br0` over the trunk ports (networkd); `accessPorts` pin a port untagged to one VLAN — convention: the last copper port is an untagged `mgmt` recovery port |
|
||||||
| Firewall/NAT | nftables: default-deny WAN, no inter-VLAN, MSS clamp, v4 NAT |
|
| Firewall/NAT | nftables: default-deny WAN, no inter-VLAN, MSS clamp, v4 NAT |
|
||||||
| DHCP | Kea, one subnet per VLAN |
|
| DHCP | Kea, one subnet per VLAN |
|
||||||
| DNS | Blocky (blocklist resolver), metrics on :4000 scraped by control |
|
| DNS | Blocky (blocklist resolver), metrics on :4000 scraped by control |
|
||||||
|
|||||||
@@ -28,9 +28,11 @@
|
|||||||
wan.vlanId = null; # this ISP runs PPPoE untagged on the port
|
wan.vlanId = null; # this ISP runs PPPoE untagged on the port
|
||||||
trunkPorts = [
|
trunkPorts = [
|
||||||
"enp2s0"
|
"enp2s0"
|
||||||
"enp3s0"
|
# "enp3s0" # STAGING: serves as the uplink below until cutover
|
||||||
"enp4s0"
|
|
||||||
];
|
];
|
||||||
|
# Dedicated on-site recovery port: untagged mgmt, always available even
|
||||||
|
# if the switch config is broken.
|
||||||
|
accessPorts.enp4s0 = "mgmt";
|
||||||
# Replaces the newedge.house OPNsense box; renumbered to the fleet
|
# Replaces the newedge.house OPNsense box; renumbered to the fleet
|
||||||
# convention (10.1.<vlanId>.0/24, router .1, pool .100-.199). The old
|
# convention (10.1.<vlanId>.0/24, router .1, pool .100-.199). The old
|
||||||
# untagged LAN becomes tagged mgmt — infra switch ports get PVID 10.
|
# untagged LAN becomes tagged mgmt — infra switch ports get PVID 10.
|
||||||
@@ -73,6 +75,17 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# STAGING (remove at cutover, and restore enp3s0 to trunkPorts): DHCP-client
|
||||||
|
# uplink into the existing OPNsense LAN so the box has internet + mesh while
|
||||||
|
# it runs alongside the old router. Default-deny firewall on this interface
|
||||||
|
# (it's in no VLAN zone); PPPoE simply retries until the WAN port is cabled.
|
||||||
|
# Do NOT connect the trunk ports to the production switch while staging —
|
||||||
|
# Kea on tag 10 would fight the OPNsense LAN DHCP in one broadcast domain.
|
||||||
|
systemd.network.networks."05-staging" = {
|
||||||
|
matchConfig.Name = "enp3s0";
|
||||||
|
networkConfig.DHCP = "ipv4";
|
||||||
|
};
|
||||||
|
|
||||||
time.timeZone = "Etc/GMT-7"; # UTC+7 (Thailand, fixed offset, no DST)
|
time.timeZone = "Etc/GMT-7"; # UTC+7 (Thailand, fixed offset, no DST)
|
||||||
services.chrony.enable = true;
|
services.chrony.enable = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,19 @@ in
|
|||||||
description = "LAN ports carrying all VLANs tagged (incl. any 10G SFP+ ports).";
|
description = "LAN ports carrying all VLANs tagged (incl. any 10G SFP+ ports).";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
accessPorts = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
default = { };
|
||||||
|
example = {
|
||||||
|
enp4s0 = "mgmt";
|
||||||
|
};
|
||||||
|
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.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
vlans = lib.mkOption {
|
vlans = lib.mkOption {
|
||||||
type = lib.types.attrsOf (lib.types.submodule vlanModule);
|
type = lib.types.attrsOf (lib.types.submodule vlanModule);
|
||||||
description = "VLANs served at this site; `mgmt` and `lan` are mandatory.";
|
description = "VLANs served at this site; `mgmt` and `lan` are mandatory.";
|
||||||
@@ -172,6 +185,14 @@ in
|
|||||||
assertion = cfg.vlans ? mgmt && cfg.vlans ? lan;
|
assertion = cfg.vlans ? mgmt && cfg.vlans ? lan;
|
||||||
message = "cnx.router: every site must define the `mgmt` and `lan` VLANs.";
|
message = "cnx.router: every site must define the `mgmt` and `lan` VLANs.";
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
assertion = lib.all (v: cfg.vlans ? ${v}) (lib.attrValues cfg.accessPorts);
|
||||||
|
message = "cnx.router: every accessPorts value must name a defined VLAN.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = lib.all (p: !(cfg.accessPorts ? ${p})) cfg.trunkPorts;
|
||||||
|
message = "cnx.router: a port cannot be both a trunk and an access port.";
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
# Router diagnostics toolkit: packets (tcpdump), path (mtr), link
|
# Router diagnostics toolkit: packets (tcpdump), path (mtr), link
|
||||||
@@ -270,6 +291,21 @@ in
|
|||||||
};
|
};
|
||||||
}) cfg.trunkPorts
|
}) cfg.trunkPorts
|
||||||
)
|
)
|
||||||
|
// lib.mapAttrs' (
|
||||||
|
port: vlanName:
|
||||||
|
lib.nameValuePair "25-access-${port}" {
|
||||||
|
matchConfig.Name = port;
|
||||||
|
networkConfig.Bridge = "br0";
|
||||||
|
bridgeVLANs = [
|
||||||
|
{
|
||||||
|
VLAN = cfg.vlans.${vlanName}.id;
|
||||||
|
PVID = cfg.vlans.${vlanName}.id;
|
||||||
|
EgressUntagged = cfg.vlans.${vlanName}.id;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
linkConfig.RequiredForOnline = "no";
|
||||||
|
}
|
||||||
|
) cfg.accessPorts
|
||||||
// lib.mapAttrs' (
|
// lib.mapAttrs' (
|
||||||
name: vlan:
|
name: vlan:
|
||||||
lib.nameValuePair "40-${vlanIf name}" {
|
lib.nameValuePair "40-${vlanIf name}" {
|
||||||
|
|||||||
Reference in New Issue
Block a user