{ pkgs, runTest }: let inherit (pkgs) lib; ipv6Only = { networking.useDHCP = false; networking.interfaces.eth1.ipv4.addresses = lib.mkVMOverride [ ]; }; ipv4Only = { networking.useDHCP = false; networking.interfaces.eth1.ipv6.addresses = lib.mkVMOverride [ ]; }; webserver = ip: msg: { systemd.services.webserver = { description = "Mock webserver"; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; script = '' while true; do { printf 'HTTP/1.0 200 OK\n' printf 'Content-Length: ${toString (1 + builtins.stringLength msg)}\n' printf '\n${msg}\n\n' } | ${pkgs.libressl.nc}/bin/nc -${toString ip}nvl 80 done ''; }; networking.firewall.allowedTCPPorts = [ 80 ]; }; in { siit = runTest { # This test simulates the setup described in [1] with two IPv6 and # IPv4-only devices on different subnets communicating through a border # relay running Jool in SIIT mode. # [1]: https://nicmx.github.io/Jool/en/run-vanilla.html name = "jool-siit"; meta.maintainers = with lib.maintainers; [ rnhmjoj ]; # Border relay nodes.relay = { virtualisation.vlans = [ 1 2 ]; # Enable packet routing boot.kernel.sysctl = { "net.ipv6.conf.all.forwarding" = 1; "net.ipv4.conf.all.forwarding" = 1; }; networking.useDHCP = false; networking.interfaces = lib.mkVMOverride { eth1.ipv6.addresses = [ { address = "fd::198.51.100.1"; prefixLength = 120; } ]; eth2.ipv4.addresses = [ { address = "192.0.2.1"; prefixLength = 24; } ]; }; networking.jool.enable = true; networking.jool.siit.default.global.pool6 = "fd::/96"; }; # IPv6 only node nodes.alice = { imports = [ ipv6Only (webserver 6 "Hello, Bob!") ]; virtualisation.vlans = [ 1 ]; networking.interfaces.eth1.ipv6 = { addresses = [ { address = "fd::198.51.100.8"; prefixLength = 120; } ]; routes = [ { address = "fd::192.0.2.0"; prefixLength = 120; via = "fd::198.51.100.1"; } ]; }; }; # IPv4 only node nodes.bob = { imports = [ ipv4Only (webserver 4 "Hello, Alice!") ]; virtualisation.vlans = [ 2 ]; networking.interfaces.eth1.ipv4 = { addresses = [ { address = "192.0.2.16"; prefixLength = 24; } ]; routes = [ { address = "198.51.100.0"; prefixLength = 24; via = "192.0.2.1"; } ]; }; }; testScript = '' start_all() relay.wait_for_unit("jool-siit-default.service") alice.wait_for_unit("network-addresses-eth1.service") bob.wait_for_unit("network-addresses-eth1.service") with subtest("Alice and Bob can't ping each other"): relay.systemctl("stop jool-siit-default.service") alice.fail("ping -c1 fd::192.0.2.16") bob.fail("ping -c1 198.51.100.8") with subtest("Alice and Bob can ping using the relay"): relay.systemctl("start jool-siit-default.service") alice.wait_until_succeeds("ping -c1 fd::192.0.2.16") bob.wait_until_succeeds("ping -c1 198.51.100.8") with subtest("Alice can connect to Bob's webserver"): bob.wait_for_open_port(80) alice.succeed("curl -vvv http://[fd::192.0.2.16] >&2") alice.succeed("curl --fail -s http://[fd::192.0.2.16] | grep -q Alice") with subtest("Bob can connect to Alices's webserver"): alice.wait_for_open_port(80) bob.succeed("curl --fail -s http://198.51.100.8 | grep -q Bob") ''; }; nat64 = runTest { # This test simulates the setup described in [1] with two IPv6-only nodes # (a client and a homeserver) on the LAN subnet and an IPv4 node on the WAN. # The router runs Jool in stateful NAT64 mode, masquarading the LAN and # forwarding ports using static BIB entries. # [1]: https://nicmx.github.io/Jool/en/run-nat64.html name = "jool-nat64"; meta.maintainers = with lib.maintainers; [ rnhmjoj ]; # Router nodes.router = { virtualisation.vlans = [ 1 2 ]; # Enable packet routing boot.kernel.sysctl = { "net.ipv6.conf.all.forwarding" = 1; "net.ipv4.conf.all.forwarding" = 1; }; networking.useDHCP = false; networking.interfaces = lib.mkVMOverride { eth1.ipv6.addresses = [ { address = "2001:db8::1"; prefixLength = 96; } ]; eth2.ipv4.addresses = [ { address = "203.0.113.1"; prefixLength = 24; } ]; }; networking.jool.enable = true; networking.jool.nat64.default = { bib = [ { # forward HTTP 203.0.113.1 (router) → 2001:db8::9 (homeserver) "protocol" = "TCP"; "ipv4 address" = "203.0.113.1#80"; "ipv6 address" = "2001:db8::9#80"; } ]; pool4 = [ # Ports for dynamic translation { protocol = "TCP"; prefix = "203.0.113.1/32"; "port range" = "40001-65535"; } { protocol = "UDP"; prefix = "203.0.113.1/32"; "port range" = "40001-65535"; } { protocol = "ICMP"; prefix = "203.0.113.1/32"; "port range" = "40001-65535"; } # Ports for static BIB entries { protocol = "TCP"; prefix = "203.0.113.1/32"; "port range" = "80"; } ]; }; }; # LAN client (IPv6 only) nodes.client = { imports = [ ipv6Only ]; virtualisation.vlans = [ 1 ]; networking.interfaces.eth1.ipv6 = { addresses = [ { address = "2001:db8::8"; prefixLength = 96; } ]; routes = [ { address = "64:ff9b::"; prefixLength = 96; via = "2001:db8::1"; } ]; }; }; # LAN server (IPv6 only) nodes.homeserver = { imports = [ ipv6Only (webserver 6 "Hello from IPv6!") ]; virtualisation.vlans = [ 1 ]; networking.interfaces.eth1.ipv6 = { addresses = [ { address = "2001:db8::9"; prefixLength = 96; } ]; routes = [ { address = "64:ff9b::"; prefixLength = 96; via = "2001:db8::1"; } ]; }; }; # WAN server (IPv4 only) nodes.server = { imports = [ ipv4Only (webserver 4 "Hello from IPv4!") ]; virtualisation.vlans = [ 2 ]; networking.interfaces.eth1.ipv4.addresses = [ { address = "203.0.113.16"; prefixLength = 24; } ]; }; testScript = '' start_all() for node in [client, homeserver, server]: node.wait_for_unit("network-addresses-eth1.service") with subtest("Client can ping the WAN server"): router.wait_for_unit("jool-nat64-default.service") client.succeed("ping -c1 64:ff9b::203.0.113.16") with subtest("Client can connect to the WAN webserver"): server.wait_for_open_port(80) client.succeed("curl --fail -s http://[64:ff9b::203.0.113.16] | grep -q IPv4!") with subtest("Router BIB entries are correctly populated"): router.succeed("jool bib display | grep -q 'Dynamic TCP.*2001:db8::8'") router.succeed("jool bib display | grep -q 'Static TCP.*2001:db8::9'") with subtest("WAN server can reach the LAN server"): homeserver.wait_for_open_port(80) server.succeed("curl --fail -s http://203.0.113.1 | grep -q IPv6!") ''; }; }