Add IPv6 prefix delegation and PPPoE uplink
IPv6: router.wan.ipv6.enable mirrors the existing uplink config (DHCPv6 with WithoutRA=solicit, delegated prefix). The ISP delegates a single /64, so exactly one LAN may claim it via router.lans.<name>.ipv6.pd (asserted); other LANs stay IPv4-only. SLAAC only, no NAT66. PPPoE: router.wan.ipv4.type = "pppoe" runs pppd on an optional VLAN of the uplink, with the password read from a file at service start (never the Nix store). NAT, forwarding and MSS clamping (MTU 1492) use the derived PPP device; native IPv6 stays on the untagged link. Tests: basic.nix is parameterised by uplink type; the pppoe variant uses a fake ISP with pppoe-server (VLAN 7, CHAP). Both variants cover IPv6 PD, SLAAC, routing without NAT and WAN-deny. Assertion tests gain PD/PPPoE cases and a full-system evaluation step. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+71
-4
@@ -17,10 +17,10 @@ let
|
||||
fsType = "ext4";
|
||||
};
|
||||
system.stateVersion = "25.05";
|
||||
router = {
|
||||
router = lib.recursiveUpdate {
|
||||
enable = true;
|
||||
wan.interface = "eth0";
|
||||
} // router;
|
||||
} router;
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
@@ -73,6 +73,59 @@ let
|
||||
};
|
||||
expect = [ "`a` (192.168.10.0/24) and `b` (192.168.10.0/24) overlap" ];
|
||||
};
|
||||
pdOnOneLan = {
|
||||
router = {
|
||||
wan.ipv6.enable = true;
|
||||
lans.main = lan { ipv6.pd = true; };
|
||||
};
|
||||
expect = [ ];
|
||||
};
|
||||
pdOnTwoLans = {
|
||||
router = {
|
||||
wan.ipv6.enable = true;
|
||||
lans = {
|
||||
a = lan { ipv6.pd = true; };
|
||||
b = lan {
|
||||
interface = "eth2";
|
||||
ipv4.address = "192.168.20.1";
|
||||
dhcp.pool = { start = "192.168.20.100"; end = "192.168.20.199"; };
|
||||
ipv6.pd = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
expect = [ "delegated /64 cannot be split" ];
|
||||
};
|
||||
pdWithoutWanIpv6 = {
|
||||
router.lans.main = lan { ipv6.pd = true; };
|
||||
expect = [ "router.wan.ipv6.enable is false" ];
|
||||
};
|
||||
pppoeMissingCredentials = {
|
||||
router = {
|
||||
wan.ipv4.type = "pppoe";
|
||||
lans.main = lan { };
|
||||
};
|
||||
expect = [
|
||||
"pppoe.username must be set"
|
||||
"pppoe.passwordFile must be set"
|
||||
];
|
||||
};
|
||||
pppoeValid = {
|
||||
router = {
|
||||
wan = {
|
||||
ipv4 = {
|
||||
type = "pppoe";
|
||||
pppoe = {
|
||||
vlan = 7;
|
||||
username = "user@isp";
|
||||
passwordFile = "/var/lib/secrets/pppoe";
|
||||
};
|
||||
};
|
||||
ipv6.enable = true;
|
||||
};
|
||||
lans.main = lan { ipv6.pd = true; };
|
||||
};
|
||||
expect = [ ];
|
||||
};
|
||||
nonOverlappingLans = {
|
||||
router.lans = {
|
||||
a = lan { };
|
||||
@@ -94,15 +147,29 @@ let
|
||||
) cases
|
||||
);
|
||||
|
||||
# Assertions alone do not force every option (e.g. networkd unit checks), so
|
||||
# fully evaluate the system for the configs that are meant to be valid.
|
||||
fullEval =
|
||||
name:
|
||||
let
|
||||
r = builtins.tryEval (builtins.seq (evalRouter cases.${name}.router).system.build.toplevel.drvPath true);
|
||||
in
|
||||
lib.optional (!r.success) "${name}: full system evaluation failed";
|
||||
fullEvalProblems = lib.concatMap fullEval [
|
||||
"valid"
|
||||
"pdOnOneLan"
|
||||
"pppoeValid"
|
||||
];
|
||||
|
||||
subnet = (evalRouter { lans.main = lan { ipv4.prefixLength = 20; ipv4.address = "172.16.37.1"; dhcp.enable = false; }; }).router.lans.main.ipv4.subnet;
|
||||
subnetProblem = lib.optional (subnet != "172.16.32.0/20") "derived subnet: expected 172.16.32.0/20, got ${subnet}";
|
||||
in
|
||||
pkgs.runCommand "router-assertion-tests" { } (
|
||||
if problems ++ subnetProblem == [ ] then
|
||||
if problems ++ subnetProblem ++ fullEvalProblems == [ ] then
|
||||
"touch $out"
|
||||
else
|
||||
''
|
||||
printf '%s\n' ${lib.escapeShellArgs (problems ++ subnetProblem)} >&2
|
||||
printf '%s\n' ${lib.escapeShellArgs (problems ++ subnetProblem ++ fullEvalProblems)} >&2
|
||||
exit 1
|
||||
''
|
||||
)
|
||||
|
||||
+290
-47
@@ -1,57 +1,255 @@
|
||||
# Single WAN (DHCP) + single LAN: DHCP, DNS, NAT and the default firewall policy.
|
||||
# Single WAN + single LAN: DHCP, DNS, NAT, SLAAC and the default firewall
|
||||
# policy for both address families. `wan4` selects how the IPv4 uplink works:
|
||||
#
|
||||
# upstream (ISP, 203.0.113.1) -- vlan 1 -- router -- vlan 2 -- client
|
||||
{ self, pkgs }:
|
||||
# dhcp - DHCPv4 on the uplink (ISP 203.0.113.1)
|
||||
# pppoe - PPPoE over VLAN 7 on the uplink (ISP 198.51.100.1, PPP MTU 1492)
|
||||
#
|
||||
# IPv6 is native and untagged on the uplink in both variants.
|
||||
#
|
||||
# upstream (ISP) -- vlan 1 -- router -- vlan 2 -- client
|
||||
#
|
||||
# The fake ISP mimics the real uplink: RAs carry only a default route (no
|
||||
# prefix to autoconfigure, no DHCPv6 flags), and Kea hands out one address
|
||||
# plus a single delegated /64 out of 2001:db8:2::/48.
|
||||
{
|
||||
self,
|
||||
pkgs,
|
||||
wan4 ? "dhcp",
|
||||
}:
|
||||
let
|
||||
pppoe = wan4 == "pppoe";
|
||||
ispV4 = if pppoe then "198.51.100.1" else "203.0.113.1";
|
||||
routerV4 = if pppoe then "198.51.100.100" else "203.0.113.100";
|
||||
pppoeVlan = 7;
|
||||
|
||||
inherit (pkgs) lib;
|
||||
# Indent every line but the first (the interpolation site supplies that one).
|
||||
indent =
|
||||
n: text:
|
||||
lib.concatStringsSep "\n${lib.fixedWidthString n " " ""}" (
|
||||
lib.splitString "\n" (lib.removeSuffix "\n" text)
|
||||
);
|
||||
|
||||
uplinkWait =
|
||||
if pppoe then
|
||||
''
|
||||
upstream.wait_for_unit("pppoe-server.service")
|
||||
router.wait_for_unit("router-pppoe.service")
|
||||
router.wait_until_succeeds("ip -4 addr show dev pppoe-wan | grep -q ${routerV4}")
|
||||
router.wait_until_succeeds("ip -4 route show default | grep -q 'dev pppoe-wan'")
|
||||
# The ISP's route to the LAN needs the session to exist first.
|
||||
upstream.wait_until_succeeds("ip -4 addr show | grep -q ${ispV4}")
|
||||
upstream.succeed("ip route replace 192.168.10.0/24 via ${routerV4}")
|
||||
''
|
||||
else
|
||||
''
|
||||
router.wait_until_succeeds("ip -4 addr show dev eth1 | grep -q ${routerV4}")
|
||||
'';
|
||||
|
||||
mssSubtest = ''
|
||||
with subtest("PPP link has MTU 1492 and forwarded TCP MSS is clamped"):
|
||||
assert "mtu 1492" in router.succeed("ip link show pppoe-wan"), "unexpected PPP MTU"
|
||||
# Capture the SYN as it leaves the router; the client itself offers MSS 1460.
|
||||
router.succeed("tcpdump -nn -i pppoe-wan -c 1 -w /tmp/syn.pcap 'tcp[tcpflags] & (tcp-syn|tcp-ack) = tcp-syn' >/dev/null 2>&1 &")
|
||||
router.wait_until_succeeds("pgrep tcpdump")
|
||||
router.sleep(1)
|
||||
client.succeed("curl -sf --max-time 5 --resolve example.test:80:${ispV4} http://example.test/")
|
||||
mss = router.wait_until_succeeds("tcpdump -nn -v -r /tmp/syn.pcap 2>&1 | grep -o 'mss [0-9]*'")
|
||||
assert mss.strip() == "mss 1452", f"expected the SYN clamped to mss 1452, got {mss!r}"
|
||||
'';
|
||||
|
||||
# `fail` in the test script also passes on "command not found", so make sure
|
||||
# the tools the script relies on are really there.
|
||||
tools = { pkgs, ... }: {
|
||||
environment.systemPackages = with pkgs; [ dnsutils netcat-openbsd curl tcpdump ];
|
||||
environment.systemPackages = with pkgs; [
|
||||
dnsutils
|
||||
netcat-openbsd
|
||||
curl
|
||||
tcpdump
|
||||
];
|
||||
};
|
||||
in
|
||||
pkgs.testers.runNixOSTest {
|
||||
name = "router-basic";
|
||||
name = "router-${if pppoe then "pppoe" else "basic"}";
|
||||
|
||||
defaults.imports = [ tools ];
|
||||
|
||||
# `driverInteractive` uses the full QEMU, which would open a window per VM;
|
||||
# `nix run .#lab` wants the consoles in the terminal instead.
|
||||
interactive.defaults.virtualisation.qemu.options = [ "-display" "none" ];
|
||||
interactive.defaults.virtualisation.qemu.options = [
|
||||
"-display"
|
||||
"none"
|
||||
];
|
||||
|
||||
nodes = {
|
||||
# Plays the ISP: hands out one WAN lease, answers DNS, and serves a page
|
||||
# that echoes the client address it sees (to prove NAT happens).
|
||||
upstream = { lib, ... }: {
|
||||
virtualisation.vlans = [ 1 ];
|
||||
networking.useDHCP = false;
|
||||
networking.firewall.enable = false;
|
||||
networking.interfaces.eth1.ipv4 = {
|
||||
addresses = lib.mkForce [ { address = "203.0.113.1"; prefixLength = 24; } ];
|
||||
# Route back to the LAN via the router so that "unreachable" below
|
||||
# is due to the firewall and not a missing route.
|
||||
routes = [ { address = "192.168.10.0"; prefixLength = 24; via = "203.0.113.100"; } ];
|
||||
};
|
||||
# Plays the ISP: hands out one WAN lease (v4 and v6) plus a delegated /64,
|
||||
# answers DNS, and serves a page that echoes the client address it sees
|
||||
# (to prove NAT happens for v4 and does not for v6).
|
||||
upstream =
|
||||
{ lib, pkgs, ... }:
|
||||
lib.mkMerge [
|
||||
{
|
||||
virtualisation.vlans = [ 1 ];
|
||||
networking.useNetworkd = true;
|
||||
networking.useDHCP = false;
|
||||
networking.firewall.enable = false;
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ ];
|
||||
|
||||
services.dnsmasq = {
|
||||
enable = true;
|
||||
settings = {
|
||||
interface = "eth1";
|
||||
bind-interfaces = true;
|
||||
dhcp-range = "203.0.113.100,203.0.113.100,255.255.255.0,1h";
|
||||
dhcp-option = [ "option:router,203.0.113.1" ];
|
||||
address = "/example.test/203.0.113.1";
|
||||
};
|
||||
};
|
||||
systemd.network.networks."10-wan" = {
|
||||
matchConfig.Name = "eth1";
|
||||
address = [ "2001:db8:1::1/64" ] ++ lib.optional (!pppoe) "203.0.113.1/24";
|
||||
# Routes back to the LAN via the router so that "unreachable" below is
|
||||
# due to the firewall and not a missing route. (With PPPoE the IPv4
|
||||
# route can only exist once the session is up; the test adds it.)
|
||||
routes = [
|
||||
{
|
||||
Destination = "2001:db8:2::/48";
|
||||
Gateway = "2001:db8:1::100";
|
||||
}
|
||||
]
|
||||
++ lib.optional (!pppoe) {
|
||||
Destination = "192.168.10.0/24";
|
||||
Gateway = "203.0.113.100";
|
||||
};
|
||||
networkConfig = {
|
||||
IPv6SendRA = true;
|
||||
}
|
||||
// lib.optionalAttrs pppoe { VLAN = [ "eth1.${toString pppoeVlan}" ]; };
|
||||
ipv6SendRAConfig = {
|
||||
Managed = false;
|
||||
OtherInformation = false;
|
||||
RouterLifetimeSec = 1800;
|
||||
EmitDNS = false;
|
||||
};
|
||||
# On-link only: the router must not autoconfigure a second address.
|
||||
ipv6Prefixes = [
|
||||
{
|
||||
Prefix = "2001:db8:1::/64";
|
||||
OnLink = true;
|
||||
AddressAutoconfiguration = false;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts."example.test".locations."/".return = ''200 "$remote_addr"'';
|
||||
};
|
||||
};
|
||||
services.dnsmasq = {
|
||||
enable = true;
|
||||
settings = {
|
||||
address = "/example.test/${ispV4}";
|
||||
}
|
||||
// (
|
||||
if pppoe then
|
||||
# The PPP interface only exists once a session is up.
|
||||
{ bind-dynamic = true; }
|
||||
else
|
||||
{
|
||||
interface = "eth1";
|
||||
bind-interfaces = true;
|
||||
dhcp-range = "203.0.113.100,203.0.113.100,255.255.255.0,1h";
|
||||
dhcp-option = [ "option:router,203.0.113.1" ];
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
services.kea.dhcp6 = {
|
||||
enable = true;
|
||||
settings = {
|
||||
interfaces-config = {
|
||||
interfaces = [ "eth1" ];
|
||||
re-detect = true;
|
||||
};
|
||||
lease-database = {
|
||||
type = "memfile";
|
||||
persist = false;
|
||||
};
|
||||
preferred-lifetime = 3000;
|
||||
valid-lifetime = 4000;
|
||||
subnet6 = [
|
||||
{
|
||||
id = 1;
|
||||
subnet = "2001:db8:1::/64";
|
||||
interface = "eth1";
|
||||
pools = [ { pool = "2001:db8:1::100 - 2001:db8:1::100"; } ];
|
||||
pd-pools = [
|
||||
{
|
||||
prefix = "2001:db8:2::";
|
||||
prefix-len = 48;
|
||||
delegated-len = 64;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
virtualHosts."example.test".locations."/".return = ''200 "$remote_addr"'';
|
||||
};
|
||||
}
|
||||
(lib.mkIf pppoe {
|
||||
boot.kernelModules = [
|
||||
"pppoe"
|
||||
"pppox"
|
||||
"ppp_generic"
|
||||
];
|
||||
systemd.network.netdevs."05-pppoe" = {
|
||||
netdevConfig = {
|
||||
Kind = "vlan";
|
||||
Name = "eth1.${toString pppoeVlan}";
|
||||
};
|
||||
vlanConfig.Id = pppoeVlan;
|
||||
};
|
||||
systemd.network.networks."11-pppoe" = {
|
||||
matchConfig.Name = "eth1.${toString pppoeVlan}";
|
||||
networkConfig.LinkLocalAddressing = "no";
|
||||
};
|
||||
|
||||
environment.etc = {
|
||||
"ppp/options".text = "";
|
||||
"ppp/chap-secrets".text = ''
|
||||
"testuser" * "p4ss\"w0rd" *
|
||||
'';
|
||||
"ppp/pppoe-server-options".text = ''
|
||||
require-chap
|
||||
noccp
|
||||
mtu 1492
|
||||
mru 1492
|
||||
lcp-echo-interval 10
|
||||
lcp-echo-failure 3
|
||||
'';
|
||||
};
|
||||
|
||||
# The ISP's access concentrator: one session, 198.51.100.1 <-> .100.
|
||||
systemd.services.pppoe-server = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "sys-subsystem-net-devices-eth1.${toString pppoeVlan}.device" ];
|
||||
after = [
|
||||
"systemd-networkd.service"
|
||||
"sys-subsystem-net-devices-eth1.${toString pppoeVlan}.device"
|
||||
];
|
||||
serviceConfig = {
|
||||
ExecStart = lib.concatStringsSep " " [
|
||||
"${pkgs.rp-pppoe}/bin/pppoe-server -F -I eth1.${toString pppoeVlan}"
|
||||
"-L 198.51.100.1 -R 198.51.100.100 -N 1"
|
||||
"-O /etc/ppp/pppoe-server-options -q ${pkgs.ppp}/bin/pppd"
|
||||
];
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
router = { lib, ... }: {
|
||||
imports = [ self.nixosModules.router ];
|
||||
virtualisation.vlans = [ 1 2 ];
|
||||
virtualisation.vlans = [
|
||||
1
|
||||
2
|
||||
];
|
||||
|
||||
environment.etc."pppoe-test-password" = {
|
||||
text = "p4ss\"w0rd";
|
||||
mode = "0600";
|
||||
};
|
||||
|
||||
# The test driver preconfigures static addresses on every vlan interface.
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ ];
|
||||
@@ -59,13 +257,32 @@ pkgs.testers.runNixOSTest {
|
||||
|
||||
router = {
|
||||
enable = true;
|
||||
wan.interface = "eth1";
|
||||
wan = {
|
||||
interface = "eth1";
|
||||
ipv6.enable = true;
|
||||
ipv4 = {
|
||||
type = wan4;
|
||||
}
|
||||
// lib.optionalAttrs pppoe {
|
||||
pppoe = {
|
||||
vlan = pppoeVlan;
|
||||
username = "testuser";
|
||||
passwordFile = "/etc/pppoe-test-password";
|
||||
};
|
||||
};
|
||||
};
|
||||
lans.main = {
|
||||
interface = "eth2";
|
||||
ipv4 = { address = "192.168.10.1"; };
|
||||
dhcp.pool = { start = "192.168.10.100"; end = "192.168.10.199"; };
|
||||
ipv4 = {
|
||||
address = "192.168.10.1";
|
||||
};
|
||||
ipv6.pd = true;
|
||||
dhcp.pool = {
|
||||
start = "192.168.10.100";
|
||||
end = "192.168.10.199";
|
||||
};
|
||||
};
|
||||
dns.forwarders = [ "203.0.113.1" ];
|
||||
dns.forwarders = [ ispV4 ];
|
||||
};
|
||||
|
||||
# Unbound answers `.test` locally (RFC 6761) and would validate DNSSEC
|
||||
@@ -96,9 +313,9 @@ pkgs.testers.runNixOSTest {
|
||||
upstream.wait_for_unit("dnsmasq.service")
|
||||
upstream.wait_for_unit("nginx.service")
|
||||
|
||||
with subtest("router obtains its WAN lease and brings up services"):
|
||||
with subtest("router obtains its IPv4 uplink and brings up services"):
|
||||
router.wait_for_unit("network-online.target")
|
||||
router.wait_until_succeeds("ip -4 addr show dev eth1 | grep -q 203.0.113.100")
|
||||
${indent 4 uplinkWait}
|
||||
router.wait_for_unit("nftables.service")
|
||||
router.wait_for_unit("kea-dhcp4-server.service")
|
||||
router.wait_for_unit("unbound.service")
|
||||
@@ -109,22 +326,48 @@ pkgs.testers.runNixOSTest {
|
||||
client.succeed("resolvectl status eth1 | grep -q 192.168.10.1")
|
||||
|
||||
with subtest("client resolves names through the router"):
|
||||
client.wait_until_succeeds("dig +short +time=2 +tries=1 @192.168.10.1 example.test | grep -qx 203.0.113.1")
|
||||
client.wait_until_succeeds("dig +short +time=2 +tries=1 @192.168.10.1 example.test | grep -qx ${ispV4}")
|
||||
|
||||
with subtest("client reaches the 'internet' and is NATed"):
|
||||
out = client.wait_until_succeeds("curl -sf --max-time 5 --resolve example.test:80:203.0.113.1 http://example.test/")
|
||||
assert out.strip() == "203.0.113.100", f"expected NATed source 203.0.113.100, got {out!r}"
|
||||
out = client.wait_until_succeeds("curl -sf --max-time 5 --resolve example.test:80:${ispV4} http://example.test/")
|
||||
assert out.strip() == "${routerV4}", f"expected NATed source ${routerV4}, got {out!r}"
|
||||
|
||||
${lib.optionalString pppoe (indent 0 mssSubtest)}
|
||||
with subtest("WAN cannot initiate to the router or the LAN"):
|
||||
# Positive control: the probe tool works against a port that is open.
|
||||
upstream.succeed("nc -z -w 2 203.0.113.1 80")
|
||||
upstream.fail("nc -z -w 2 203.0.113.100 22")
|
||||
upstream.fail("nc -z -w 2 203.0.113.100 53")
|
||||
upstream.succeed("nc -z -w 2 ${ispV4} 80")
|
||||
upstream.fail("nc -z -w 2 ${routerV4} 22")
|
||||
upstream.fail("nc -z -w 2 ${routerV4} 53")
|
||||
upstream.fail("ping -c 1 -W 2 192.168.10.1")
|
||||
upstream.fail("ping -c 1 -W 2 203.0.113.100")
|
||||
upstream.fail("ping -c 1 -W 2 ${routerV4}")
|
||||
client_ip = client.succeed("ip -4 -o addr show dev eth1 | awk '{print $4}' | cut -d/ -f1").strip()
|
||||
upstream.fail(f"ping -c 1 -W 2 {client_ip}")
|
||||
|
||||
with subtest("router gets an IPv6 address, the delegated /64 and a default route"):
|
||||
router.wait_until_succeeds("ip -6 addr show dev eth1 scope global | grep -q 2001:db8:1::100")
|
||||
router.wait_until_succeeds("ip -6 addr show dev eth2 scope global | grep -q 2001:db8:2:")
|
||||
router.wait_until_succeeds("ip -6 route show default | grep -q 'via fe80:.* dev eth1'")
|
||||
# The WAN prefix is not autoconfigured, so DHCPv6 is the only global address there.
|
||||
assert router.succeed("ip -6 -o addr show dev eth1 scope global | wc -l").strip() == "1"
|
||||
|
||||
with subtest("client autoconfigures a global address from the delegated prefix"):
|
||||
client.wait_until_succeeds("ip -6 addr show dev eth1 scope global | grep -q 2001:db8:2:")
|
||||
client.wait_until_succeeds("ip -6 route show default | grep -q 'dev eth1'")
|
||||
client_v6 = client.succeed("ip -6 -o addr show dev eth1 scope global | awk '{print $4}' | cut -d/ -f1 | head -1").strip()
|
||||
|
||||
with subtest("IPv6 is routed without NAT"):
|
||||
client.wait_until_succeeds("ping -6 -c 1 -W 2 2001:db8:1::1")
|
||||
out = client.succeed("curl -sf -g --max-time 5 -H 'Host: example.test' 'http://[2001:db8:1::1]/'")
|
||||
assert out.strip() == client_v6, f"expected the client's own address {client_v6}, got {out!r}"
|
||||
|
||||
with subtest("WAN cannot initiate IPv6 to the router or the LAN"):
|
||||
# Positive control: the ISP really has a route to the client, so a
|
||||
# failure below is down to the firewall.
|
||||
upstream.succeed(f"ip -6 route get {client_v6} | grep -q 'via 2001:db8:1::100'")
|
||||
upstream.fail(f"ping -6 -c 1 -W 2 {client_v6}")
|
||||
upstream.fail("ping -6 -c 1 -W 2 2001:db8:1::100")
|
||||
upstream.fail("nc -6 -z -w 2 2001:db8:1::100 22")
|
||||
|
||||
with subtest("LAN can reach SSH and ping on the router"):
|
||||
client.succeed("nc -z -w 2 192.168.10.1 22")
|
||||
client.succeed("ping -c 1 -W 2 192.168.10.1")
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
{ self, pkgs }:
|
||||
{
|
||||
basic = import ./basic.nix { inherit self pkgs; };
|
||||
pppoe = import ./basic.nix {
|
||||
inherit self pkgs;
|
||||
wan4 = "pppoe";
|
||||
};
|
||||
lib = import ./lib.nix { inherit pkgs; };
|
||||
assertions = import ./assertions.nix { inherit self pkgs; };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user