router/tests: cover the staging uplink as NATed fallback exit
Neither regression fixed by the previous commit was observable: the VM test had no staging port, and pinging the ISP's PPPoE address only needs ppp0's connected route, not the default route pppd refused to install. Add a fifth node, `oldlan`: a networkd DHCP server on vlan 4 handing gw its staging lease, with a second address (203.0.113.1) that gw can only reach through that lease's default route. It has no route back to the VLANs, so client pings only work if gw masquerades. Proven: pppd's metric-0 default wins over the DHCP one while ppp0 is up; lan reaches the old LAN via the staging port and iot (allowWan = false) does not; after stopping pppd the staging route carries WAN traffic with the same allowWan split; restarting pppd makes ppp0 preferred again.
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
# End-to-end VM test of the router service: a PPPoE access concentrator plays
|
# End-to-end VM test of the router service: a PPPoE access concentrator plays
|
||||||
# the ISP on the WAN port, a trunk carries tagged lan/iot VLANs to `client`,
|
# the ISP on the WAN port, a trunk carries tagged lan/iot VLANs to `client`,
|
||||||
# and an untagged access port carries mgmt to `admin`.
|
# an untagged access port carries mgmt to `admin`, and `oldlan` is the DHCP
|
||||||
|
# network the box is staged in before cutover.
|
||||||
#
|
#
|
||||||
# isp ---(vlan 1: PPPoE)--- wan [gw] trunk ---(vlan 2: tagged 20/40)--- client
|
# isp ---(vlan 1: PPPoE)--- wan [gw] trunk ---(vlan 2: tagged 20/40)--- client
|
||||||
# access --(vlan 3: untagged mgmt)--- admin
|
# access --(vlan 3: untagged mgmt)--- admin
|
||||||
|
# staging -(vlan 4: DHCP client)--- oldlan
|
||||||
#
|
#
|
||||||
# What is proven: PPPoE dial-in with the vars-provided credentials, bridge
|
# What is proven: PPPoE dial-in with the vars-provided credentials, bridge
|
||||||
# VLAN tagging/untagging, Kea leases and reservations per VLAN, Blocky
|
# VLAN tagging/untagging, Kea leases and reservations per VLAN, Blocky
|
||||||
# answering on the VLAN with the blocklist active, NAT to the WAN, and the
|
# answering on the VLAN with the blocklist active, NAT to the WAN, the
|
||||||
# firewall trust model (allowWan, mgmt-only SSH, no inter-VLAN forwarding).
|
# firewall trust model (allowWan, mgmt-only SSH, no inter-VLAN forwarding),
|
||||||
|
# and the staging uplink as NATed fallback exit behind ppp0.
|
||||||
{ pkgs, lib, ... }:
|
{ pkgs, lib, ... }:
|
||||||
let
|
let
|
||||||
# The vars mock answers every prompt with "mock-prompt-value-<name>"; the
|
# The vars mock answers every prompt with "mock-prompt-value-<name>"; the
|
||||||
@@ -20,6 +23,10 @@ let
|
|||||||
clientAddress = "10.9.20.50";
|
clientAddress = "10.9.20.50";
|
||||||
adminMac = "02:00:00:00:00:10";
|
adminMac = "02:00:00:00:00:10";
|
||||||
adminAddress = "10.9.10.50";
|
adminAddress = "10.9.10.50";
|
||||||
|
oldlanAddress = "192.168.88.1";
|
||||||
|
# Only reachable through oldlan's router role, i.e. via gw's staging
|
||||||
|
# default route (metric 1024); ppp0's metric-0 default must win while up.
|
||||||
|
beyondStaging = "203.0.113.1";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
name = "router";
|
name = "router";
|
||||||
@@ -36,6 +43,7 @@ in
|
|||||||
isp = { };
|
isp = { };
|
||||||
client = { };
|
client = { };
|
||||||
admin = { };
|
admin = { };
|
||||||
|
oldlan = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
instances.router = {
|
instances.router = {
|
||||||
@@ -48,6 +56,7 @@ in
|
|||||||
wan.interface = "wan";
|
wan.interface = "wan";
|
||||||
trunkPorts = [ "trunk" ];
|
trunkPorts = [ "trunk" ];
|
||||||
accessPorts.access = "mgmt";
|
accessPorts.access = "mgmt";
|
||||||
|
stagingPort = "staging";
|
||||||
vlans = {
|
vlans = {
|
||||||
mgmt = {
|
mgmt = {
|
||||||
id = 10;
|
id = 10;
|
||||||
@@ -111,6 +120,10 @@ in
|
|||||||
vlan = 3;
|
vlan = 3;
|
||||||
assignIP = false;
|
assignIP = false;
|
||||||
};
|
};
|
||||||
|
staging = {
|
||||||
|
vlan = 4;
|
||||||
|
assignIP = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Something must listen on 22 for the mgmt-only SSH rule to be observable
|
# Something must listen on 22 for the mgmt-only SSH rule to be observable
|
||||||
@@ -251,6 +264,32 @@ in
|
|||||||
};
|
};
|
||||||
environment.systemPackages = [ pkgs.netcat ];
|
environment.systemPackages = [ pkgs.netcat ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# The LAN the box is staged in: a DHCP server handing gw its uplink
|
||||||
|
# lease, plus an address that is only reachable via that uplink's
|
||||||
|
# default route. No route back to 10.9.0.0/16: replies only reach the
|
||||||
|
# clients if gw masquerades them.
|
||||||
|
oldlan = {
|
||||||
|
virtualisation.interfaces.staging = {
|
||||||
|
vlan = 4;
|
||||||
|
assignIP = false;
|
||||||
|
};
|
||||||
|
networking.useDHCP = false;
|
||||||
|
networking.useNetworkd = true;
|
||||||
|
systemd.network.networks."10-staging" = {
|
||||||
|
matchConfig.Name = "staging";
|
||||||
|
address = [
|
||||||
|
"${oldlanAddress}/24"
|
||||||
|
"${beyondStaging}/32"
|
||||||
|
];
|
||||||
|
networkConfig.DHCPServer = true;
|
||||||
|
dhcpServerConfig = {
|
||||||
|
PoolOffset = 100;
|
||||||
|
PoolSize = 50;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networking.firewall.allowedUDPPorts = [ 67 ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
@@ -302,5 +341,21 @@ in
|
|||||||
gw.wait_until_succeeds("ip netns exec sta wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED")
|
gw.wait_until_succeeds("ip netns exec sta wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED")
|
||||||
gw.succeed("timeout 60 sta-dhcp")
|
gw.succeed("timeout 60 sta-dhcp")
|
||||||
gw.succeed("ip netns exec sta ip -4 addr show wlan1 | grep -q 'inet 10.9.20.1[0-9][0-9]/24'")
|
gw.succeed("ip netns exec sta ip -4 addr show wlan1 | grep -q 'inet 10.9.20.1[0-9][0-9]/24'")
|
||||||
|
|
||||||
|
with subtest("Staging uplink: NATed exit for allowWan VLANs, behind ppp0 while it is up"):
|
||||||
|
gw.wait_until_succeeds("ip -4 route show default dev staging | grep -q 'via ${oldlanAddress}'")
|
||||||
|
# pppd installs its default route despite the DHCP one (defaultroute-metric 0).
|
||||||
|
gw.succeed("ip route get ${beyondStaging} | grep -q 'dev ppp0'")
|
||||||
|
# On-link old-LAN hosts are reached through the staging port regardless.
|
||||||
|
client.succeed("ping -c1 -W2 -I lan0 ${oldlanAddress}")
|
||||||
|
client.fail("ping -c1 -W2 -I iot0 ${oldlanAddress}")
|
||||||
|
# ppp0 down: the staging route carries the WAN traffic, allowWan still holds.
|
||||||
|
gw.systemctl("stop pppd-wan.service")
|
||||||
|
gw.wait_until_succeeds("ip route get ${beyondStaging} | grep -q 'dev staging'")
|
||||||
|
client.succeed("ping -c1 -W2 -I lan0 ${beyondStaging}")
|
||||||
|
client.fail("ping -c1 -W2 -I iot0 ${beyondStaging}")
|
||||||
|
# ppp0 back: preferred again.
|
||||||
|
gw.systemctl("start pppd-wan.service")
|
||||||
|
gw.wait_until_succeeds("ip route get ${beyondStaging} | grep -q 'dev ppp0'")
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user