router: let allowWan VLANs out through the staging uplink
With stagingPort set, the box itself had internet over the staging DHCP
uplink but LAN/Wi-Fi clients had none: forward and masquerade were scoped
to ppp0 only. Worse, pppd's `defaultroute` refuses to install its route
while the staging DHCP default route (metric 1024) exists ("not replacing
existing default route"), so even a live PPPoE session was never used.
- firewall: forward-allow + masquerade allowWan VLANs -> stagingPort in a
separate `router-staging-nat` postrouting chain (networking.nat only
takes one external interface). Same allowWan set as nixos-nat.
- pppoe: `defaultroute-metric 0`, so pppd only checks for a metric-0
default route, installs ppp0 as the preferred exit and removes it on
hangup, leaving the staging route as the fallback.
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
# other VLANs -> DNS/DHCP on the router + WAN (if allowWan); no inter-VLAN
|
# other VLANs -> DNS/DHCP on the router + WAN (if allowWan); no inter-VLAN
|
||||||
# WAN (ppp0) -> nothing inbound beyond established/related
|
# WAN (ppp0) -> nothing inbound beyond established/related
|
||||||
# mesh -> admin SSH + metrics scrapes (same trust boundary as the fleet)
|
# mesh -> admin SSH + metrics scrapes (same trust boundary as the fleet)
|
||||||
# staging -> admin SSH only (pre-cutover uplink into the old LAN)
|
# staging -> admin SSH only inbound (pre-cutover uplink into the old LAN);
|
||||||
|
# allowWan VLANs are NATed out through it while ppp0 is down
|
||||||
{ settings }:
|
{ settings }:
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
let
|
let
|
||||||
@@ -14,6 +15,13 @@ let
|
|||||||
lib.filterAttrs (_: vlan: vlan.allowWan) cfg.vlans
|
lib.filterAttrs (_: vlan: vlan.allowWan) cfg.vlans
|
||||||
);
|
);
|
||||||
nonMgmtIfs = lib.filter (i: i != "vlan-mgmt") vlanIfs;
|
nonMgmtIfs = lib.filter (i: i != "vlan-mgmt") vlanIfs;
|
||||||
|
|
||||||
|
# allowWan VLANs may also leave through the staging uplink. Same set as
|
||||||
|
# networking.nat.internalInterfaces below, so `allowWan` holds on both
|
||||||
|
# exits. The kernel picks the exit: ppp0 (metric 0, see pppoe.nix) while
|
||||||
|
# the session is up, the staging DHCP route (metric 1024) otherwise.
|
||||||
|
stagingExit = cfg.stagingPort != null && wanVlanIfs != [ ];
|
||||||
|
wanVlanSet = "{ ${lib.concatMapStringsSep ", " (i: ''"${i}"'') wanVlanIfs} }";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
networking.nftables.enable = true;
|
networking.nftables.enable = true;
|
||||||
@@ -50,6 +58,9 @@ in
|
|||||||
extraForwardRules = ''
|
extraForwardRules = ''
|
||||||
tcp flags syn tcp option maxseg size set rt mtu comment "MSS clamp for PPPoE mtu 1492"
|
tcp flags syn tcp option maxseg size set rt mtu comment "MSS clamp for PPPoE mtu 1492"
|
||||||
iifname "vlan-mgmt" accept comment "mgmt reaches all VLANs and the WAN"
|
iifname "vlan-mgmt" accept comment "mgmt reaches all VLANs and the WAN"
|
||||||
|
''
|
||||||
|
+ lib.optionalString stagingExit ''
|
||||||
|
iifname ${wanVlanSet} oifname "${cfg.stagingPort}" accept comment "allowWan VLANs out via the staging uplink"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,4 +73,19 @@ in
|
|||||||
externalInterface = "ppp0";
|
externalInterface = "ppp0";
|
||||||
internalInterfaces = wanVlanIfs;
|
internalInterfaces = wanVlanIfs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# networking.nat only masquerades on its single externalInterface; the
|
||||||
|
# staging uplink needs its own postrouting chain (nixos-nat's is
|
||||||
|
# oifname-scoped to ppp0, so the two never both apply).
|
||||||
|
networking.nftables.tables = lib.optionalAttrs stagingExit {
|
||||||
|
router-staging-nat = {
|
||||||
|
family = "ip";
|
||||||
|
content = ''
|
||||||
|
chain post {
|
||||||
|
type nat hook postrouting priority srcnat;
|
||||||
|
iifname ${wanVlanSet} oifname "${cfg.stagingPort}" masquerade comment "allowWan VLANs out via the staging uplink"
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -256,13 +256,15 @@ in
|
|||||||
example = "enp3s0";
|
example = "enp3s0";
|
||||||
description = ''
|
description = ''
|
||||||
Temporary DHCPv4-client uplink into the existing LAN while the box
|
Temporary DHCPv4-client uplink into the existing LAN while the box
|
||||||
runs alongside the router it replaces: gives it internet + mesh
|
runs alongside the router it replaces: gives it (and, NATed, the
|
||||||
before the WAN port is cabled (PPPoE simply retries until then). The
|
allowWan VLANs) internet + mesh before the WAN port is cabled; once
|
||||||
port is in no VLAN zone; the firewall admits only SSH on it. Do NOT
|
the PPPoE session is up its default route wins, and the staging
|
||||||
connect the trunk ports to the production switch while staging —
|
route only carries traffic again if the session drops (PPPoE simply
|
||||||
Kea on the mgmt tag would fight the old router's DHCP in one
|
retries until then). The port is in no VLAN zone; inbound, the
|
||||||
broadcast domain. Set to null at cutover (and usually hand the port
|
firewall admits only SSH on it. Do NOT connect the trunk ports to
|
||||||
back to `trunkPorts`).
|
the production switch while staging — Kea on the mgmt tag would
|
||||||
|
fight the old router's DHCP in one broadcast domain. Set to null at
|
||||||
|
cutover (and usually hand the port back to `trunkPorts`).
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# defaultroute-metric 0: pppd refuses `defaultroute` while any other
|
||||||
|
# default route exists (e.g. the staging uplink's DHCP route, metric 1024,
|
||||||
|
# network.nix) unless given a metric; with 0 it only checks for a metric-0
|
||||||
|
# route, installs its own as the preferred exit, and removes it again on
|
||||||
|
# hangup so the staging route takes over.
|
||||||
services.pppd = {
|
services.pppd = {
|
||||||
enable = true;
|
enable = true;
|
||||||
peers.wan = {
|
peers.wan = {
|
||||||
@@ -40,6 +45,7 @@ in
|
|||||||
file ${creds.files."user-opts".path}
|
file ${creds.files."user-opts".path}
|
||||||
noipdefault
|
noipdefault
|
||||||
defaultroute
|
defaultroute
|
||||||
|
defaultroute-metric 0
|
||||||
noauth
|
noauth
|
||||||
hide-password
|
hide-password
|
||||||
persist
|
persist
|
||||||
|
|||||||
Reference in New Issue
Block a user