729 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			729 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ system ? builtins.currentSystem
 | 
						|
, config ? {}
 | 
						|
, pkgs ? import ../.. { inherit system config; }
 | 
						|
# bool: whether to use networkd in the tests
 | 
						|
, networkd }:
 | 
						|
 | 
						|
with import ../lib/testing-python.nix { inherit system pkgs; };
 | 
						|
with pkgs.lib;
 | 
						|
 | 
						|
let
 | 
						|
  qemu-flags = import ../lib/qemu-flags.nix { inherit pkgs; };
 | 
						|
 | 
						|
  router = { config, pkgs, lib, ... }:
 | 
						|
    with pkgs.lib;
 | 
						|
    let
 | 
						|
      vlanIfs = range 1 (length config.virtualisation.vlans);
 | 
						|
    in {
 | 
						|
      environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
 | 
						|
      virtualisation.vlans = [ 1 2 3 ];
 | 
						|
      boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
 | 
						|
      networking = {
 | 
						|
        useDHCP = false;
 | 
						|
        useNetworkd = networkd;
 | 
						|
        firewall.checkReversePath = true;
 | 
						|
        firewall.allowedUDPPorts = [ 547 ];
 | 
						|
        interfaces = mkOverride 0 (listToAttrs (forEach vlanIfs (n:
 | 
						|
          nameValuePair "eth${toString n}" {
 | 
						|
            ipv4.addresses = [ { address = "192.168.${toString n}.1"; prefixLength = 24; } ];
 | 
						|
            ipv6.addresses = [ { address = "fd00:1234:5678:${toString n}::1"; prefixLength = 64; } ];
 | 
						|
          })));
 | 
						|
      };
 | 
						|
      services.dhcpd4 = {
 | 
						|
        enable = true;
 | 
						|
        interfaces = map (n: "eth${toString n}") vlanIfs;
 | 
						|
        extraConfig = flip concatMapStrings vlanIfs (n: ''
 | 
						|
          subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
 | 
						|
            option routers 192.168.${toString n}.1;
 | 
						|
            range 192.168.${toString n}.3 192.168.${toString n}.254;
 | 
						|
          }
 | 
						|
        '')
 | 
						|
        ;
 | 
						|
        machines = flip map vlanIfs (vlan:
 | 
						|
          {
 | 
						|
            hostName = "client${toString vlan}";
 | 
						|
            ethernetAddress = qemu-flags.qemuNicMac vlan 1;
 | 
						|
            ipAddress = "192.168.${toString vlan}.2";
 | 
						|
          }
 | 
						|
        );
 | 
						|
      };
 | 
						|
      services.radvd = {
 | 
						|
        enable = true;
 | 
						|
        config = flip concatMapStrings vlanIfs (n: ''
 | 
						|
          interface eth${toString n} {
 | 
						|
            AdvSendAdvert on;
 | 
						|
            AdvManagedFlag on;
 | 
						|
            AdvOtherConfigFlag on;
 | 
						|
 | 
						|
            prefix fd00:1234:5678:${toString n}::/64 {
 | 
						|
              AdvAutonomous off;
 | 
						|
            };
 | 
						|
          };
 | 
						|
        '');
 | 
						|
      };
 | 
						|
      services.dhcpd6 = {
 | 
						|
        enable = true;
 | 
						|
        interfaces = map (n: "eth${toString n}") vlanIfs;
 | 
						|
        extraConfig = ''
 | 
						|
          authoritative;
 | 
						|
        '' + flip concatMapStrings vlanIfs (n: ''
 | 
						|
          subnet6 fd00:1234:5678:${toString n}::/64 {
 | 
						|
            range6 fd00:1234:5678:${toString n}::2 fd00:1234:5678:${toString n}::2;
 | 
						|
          }
 | 
						|
        '');
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
  testCases = {
 | 
						|
    loopback = {
 | 
						|
      name = "Loopback";
 | 
						|
      machine.networking.useDHCP = false;
 | 
						|
      machine.networking.useNetworkd = networkd;
 | 
						|
      testScript = ''
 | 
						|
        start_all()
 | 
						|
        machine.wait_for_unit("network.target")
 | 
						|
        loopback_addresses = machine.succeed("ip addr show lo")
 | 
						|
        assert "inet 127.0.0.1/8" in loopback_addresses
 | 
						|
        assert "inet6 ::1/128" in loopback_addresses
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    static = {
 | 
						|
      name = "Static";
 | 
						|
      nodes.router = router;
 | 
						|
      nodes.client = { pkgs, ... }: with pkgs.lib; {
 | 
						|
        virtualisation.vlans = [ 1 2 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          defaultGateway = "192.168.1.1";
 | 
						|
          interfaces.eth1.ipv4.addresses = mkOverride 0 [
 | 
						|
            { address = "192.168.1.2"; prefixLength = 24; }
 | 
						|
            { address = "192.168.1.3"; prefixLength = 32; }
 | 
						|
            { address = "192.168.1.10"; prefixLength = 32; }
 | 
						|
          ];
 | 
						|
          interfaces.eth2.ipv4.addresses = mkOverride 0 [
 | 
						|
            { address = "192.168.2.2"; prefixLength = 24; }
 | 
						|
          ];
 | 
						|
        };
 | 
						|
      };
 | 
						|
      testScript = { ... }:
 | 
						|
        ''
 | 
						|
          start_all()
 | 
						|
 | 
						|
          client.wait_for_unit("network.target")
 | 
						|
          router.wait_for_unit("network-online.target")
 | 
						|
 | 
						|
          with subtest("Make sure dhcpcd is not started"):
 | 
						|
              client.fail("systemctl status dhcpcd.service")
 | 
						|
 | 
						|
          with subtest("Test vlan 1"):
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.3")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.10")
 | 
						|
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.3")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.10")
 | 
						|
 | 
						|
          with subtest("Test vlan 2"):
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.2.1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.2.2")
 | 
						|
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.2.1")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.2.2")
 | 
						|
 | 
						|
          with subtest("Test default gateway"):
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.3.1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.3.1")
 | 
						|
        '';
 | 
						|
    };
 | 
						|
    dhcpSimple = {
 | 
						|
      name = "SimpleDHCP";
 | 
						|
      nodes.router = router;
 | 
						|
      nodes.client = { pkgs, ... }: with pkgs.lib; {
 | 
						|
        virtualisation.vlans = [ 1 2 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          interfaces.eth1 = {
 | 
						|
            ipv4.addresses = mkOverride 0 [ ];
 | 
						|
            ipv6.addresses = mkOverride 0 [ ];
 | 
						|
            useDHCP = true;
 | 
						|
          };
 | 
						|
          interfaces.eth2 = {
 | 
						|
            ipv4.addresses = mkOverride 0 [ ];
 | 
						|
            ipv6.addresses = mkOverride 0 [ ];
 | 
						|
            useDHCP = true;
 | 
						|
          };
 | 
						|
        };
 | 
						|
      };
 | 
						|
      testScript = { ... }:
 | 
						|
        ''
 | 
						|
          start_all()
 | 
						|
 | 
						|
          client.wait_for_unit("network.target")
 | 
						|
          router.wait_for_unit("network-online.target")
 | 
						|
 | 
						|
          with subtest("Wait until we have an ip address on each interface"):
 | 
						|
              client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
 | 
						|
              client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'")
 | 
						|
              client.wait_until_succeeds("ip addr show dev eth2 | grep -q '192.168.2'")
 | 
						|
              client.wait_until_succeeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'")
 | 
						|
 | 
						|
          with subtest("Test vlan 1"):
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
 | 
						|
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
 | 
						|
              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
 | 
						|
 | 
						|
          with subtest("Test vlan 2"):
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.2.1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.2.2")
 | 
						|
              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
 | 
						|
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.2.1")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.2.2")
 | 
						|
              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
 | 
						|
              router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
 | 
						|
        '';
 | 
						|
    };
 | 
						|
    dhcpOneIf = {
 | 
						|
      name = "OneInterfaceDHCP";
 | 
						|
      nodes.router = router;
 | 
						|
      nodes.client = { pkgs, ... }: with pkgs.lib; {
 | 
						|
        virtualisation.vlans = [ 1 2 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          interfaces.eth1 = {
 | 
						|
            ipv4.addresses = mkOverride 0 [ ];
 | 
						|
            mtu = 1343;
 | 
						|
            useDHCP = true;
 | 
						|
          };
 | 
						|
          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
 | 
						|
        };
 | 
						|
      };
 | 
						|
      testScript = { ... }:
 | 
						|
        ''
 | 
						|
          start_all()
 | 
						|
 | 
						|
          with subtest("Wait for networking to come up"):
 | 
						|
              client.wait_for_unit("network.target")
 | 
						|
              router.wait_for_unit("network.target")
 | 
						|
 | 
						|
          with subtest("Wait until we have an ip address on each interface"):
 | 
						|
              client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
 | 
						|
 | 
						|
          with subtest("ensure MTU is set"):
 | 
						|
              assert "mtu 1343" in client.succeed("ip link show dev eth1")
 | 
						|
 | 
						|
          with subtest("Test vlan 1"):
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
 | 
						|
          with subtest("Test vlan 2"):
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.2.1")
 | 
						|
              client.fail("ping -c 1 192.168.2.2")
 | 
						|
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.2.1")
 | 
						|
              router.fail("ping -c 1 192.168.2.2")
 | 
						|
        '';
 | 
						|
    };
 | 
						|
    bond = let
 | 
						|
      node = address: { pkgs, ... }: with pkgs.lib; {
 | 
						|
        virtualisation.vlans = [ 1 2 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          bonds.bond = {
 | 
						|
            interfaces = [ "eth1" "eth2" ];
 | 
						|
            driverOptions.mode = "balance-rr";
 | 
						|
          };
 | 
						|
          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
 | 
						|
          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
 | 
						|
          interfaces.bond.ipv4.addresses = mkOverride 0
 | 
						|
            [ { inherit address; prefixLength = 30; } ];
 | 
						|
        };
 | 
						|
      };
 | 
						|
    in {
 | 
						|
      name = "Bond";
 | 
						|
      nodes.client1 = node "192.168.1.1";
 | 
						|
      nodes.client2 = node "192.168.1.2";
 | 
						|
      testScript = { ... }:
 | 
						|
        ''
 | 
						|
          start_all()
 | 
						|
 | 
						|
          with subtest("Wait for networking to come up"):
 | 
						|
              client1.wait_for_unit("network.target")
 | 
						|
              client2.wait_for_unit("network.target")
 | 
						|
 | 
						|
          with subtest("Test bonding"):
 | 
						|
              client1.wait_until_succeeds("ping -c 2 192.168.1.1")
 | 
						|
              client1.wait_until_succeeds("ping -c 2 192.168.1.2")
 | 
						|
 | 
						|
              client2.wait_until_succeeds("ping -c 2 192.168.1.1")
 | 
						|
              client2.wait_until_succeeds("ping -c 2 192.168.1.2")
 | 
						|
        '';
 | 
						|
    };
 | 
						|
    bridge = let
 | 
						|
      node = { address, vlan }: { pkgs, ... }: with pkgs.lib; {
 | 
						|
        virtualisation.vlans = [ vlan ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          interfaces.eth1.ipv4.addresses = mkOverride 0
 | 
						|
            [ { inherit address; prefixLength = 24; } ];
 | 
						|
        };
 | 
						|
      };
 | 
						|
    in {
 | 
						|
      name = "Bridge";
 | 
						|
      nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
 | 
						|
      nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
 | 
						|
      nodes.router = { pkgs, ... }: with pkgs.lib; {
 | 
						|
        virtualisation.vlans = [ 1 2 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          bridges.bridge.interfaces = [ "eth1" "eth2" ];
 | 
						|
          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
 | 
						|
          interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
 | 
						|
          interfaces.bridge.ipv4.addresses = mkOverride 0
 | 
						|
            [ { address = "192.168.1.1"; prefixLength = 24; } ];
 | 
						|
        };
 | 
						|
      };
 | 
						|
      testScript = { ... }:
 | 
						|
        ''
 | 
						|
          start_all()
 | 
						|
 | 
						|
          with subtest("Wait for networking to come up"):
 | 
						|
              for machine in client1, client2, router:
 | 
						|
                  machine.wait_for_unit("network.target")
 | 
						|
 | 
						|
          with subtest("Test bridging"):
 | 
						|
              client1.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              client1.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
              client1.wait_until_succeeds("ping -c 1 192.168.1.3")
 | 
						|
 | 
						|
              client2.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              client2.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
              client2.wait_until_succeeds("ping -c 1 192.168.1.3")
 | 
						|
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.3")
 | 
						|
        '';
 | 
						|
    };
 | 
						|
    macvlan = {
 | 
						|
      name = "MACVLAN";
 | 
						|
      nodes.router = router;
 | 
						|
      nodes.client = { pkgs, ... }: with pkgs.lib; {
 | 
						|
        environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
 | 
						|
        virtualisation.vlans = [ 1 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          firewall.logReversePathDrops = true; # to debug firewall rules
 | 
						|
          # reverse path filtering rules for the macvlan interface seem
 | 
						|
          # to be incorrect, causing the test to fail. Disable temporarily.
 | 
						|
          firewall.checkReversePath = false;
 | 
						|
          macvlans.macvlan.interface = "eth1";
 | 
						|
          interfaces.eth1 = {
 | 
						|
            ipv4.addresses = mkOverride 0 [ ];
 | 
						|
            useDHCP = true;
 | 
						|
          };
 | 
						|
          interfaces.macvlan = {
 | 
						|
            useDHCP = true;
 | 
						|
          };
 | 
						|
        };
 | 
						|
      };
 | 
						|
      testScript = { ... }:
 | 
						|
        ''
 | 
						|
          start_all()
 | 
						|
 | 
						|
          with subtest("Wait for networking to come up"):
 | 
						|
              client.wait_for_unit("network.target")
 | 
						|
              router.wait_for_unit("network.target")
 | 
						|
 | 
						|
          with subtest("Wait until we have an ip address on each interface"):
 | 
						|
              client.wait_until_succeeds("ip addr show dev eth1 | grep -q '192.168.1'")
 | 
						|
              client.wait_until_succeeds("ip addr show dev macvlan | grep -q '192.168.1'")
 | 
						|
 | 
						|
          with subtest("Print lots of diagnostic information"):
 | 
						|
              router.log("**********************************************")
 | 
						|
              router.succeed("ip addr >&2")
 | 
						|
              router.succeed("ip route >&2")
 | 
						|
              router.execute("iptables-save >&2")
 | 
						|
              client.log("==============================================")
 | 
						|
              client.succeed("ip addr >&2")
 | 
						|
              client.succeed("ip route >&2")
 | 
						|
              client.execute("iptables-save >&2")
 | 
						|
              client.log("##############################################")
 | 
						|
 | 
						|
          with subtest("Test macvlan creates routable ips"):
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
              client.wait_until_succeeds("ping -c 1 192.168.1.3")
 | 
						|
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.1")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.2")
 | 
						|
              router.wait_until_succeeds("ping -c 1 192.168.1.3")
 | 
						|
        '';
 | 
						|
    };
 | 
						|
    sit = let
 | 
						|
      node = { address4, remote, address6 }: { pkgs, ... }: with pkgs.lib; {
 | 
						|
        virtualisation.vlans = [ 1 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          firewall.enable = false;
 | 
						|
          useDHCP = false;
 | 
						|
          sits.sit = {
 | 
						|
            inherit remote;
 | 
						|
            local = address4;
 | 
						|
            dev = "eth1";
 | 
						|
          };
 | 
						|
          interfaces.eth1.ipv4.addresses = mkOverride 0
 | 
						|
            [ { address = address4; prefixLength = 24; } ];
 | 
						|
          interfaces.sit.ipv6.addresses = mkOverride 0
 | 
						|
            [ { address = address6; prefixLength = 64; } ];
 | 
						|
        };
 | 
						|
      };
 | 
						|
    in {
 | 
						|
      name = "Sit";
 | 
						|
      nodes.client1 = node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; };
 | 
						|
      nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; };
 | 
						|
      testScript = { ... }:
 | 
						|
        ''
 | 
						|
          start_all()
 | 
						|
 | 
						|
          with subtest("Wait for networking to be configured"):
 | 
						|
              client1.wait_for_unit("network.target")
 | 
						|
              client2.wait_for_unit("network.target")
 | 
						|
 | 
						|
              # Print diagnostic information
 | 
						|
              client1.succeed("ip addr >&2")
 | 
						|
              client2.succeed("ip addr >&2")
 | 
						|
 | 
						|
          with subtest("Test ipv6"):
 | 
						|
              client1.wait_until_succeeds("ping -c 1 fc00::1")
 | 
						|
              client1.wait_until_succeeds("ping -c 1 fc00::2")
 | 
						|
 | 
						|
              client2.wait_until_succeeds("ping -c 1 fc00::1")
 | 
						|
              client2.wait_until_succeeds("ping -c 1 fc00::2")
 | 
						|
        '';
 | 
						|
    };
 | 
						|
    vlan = let
 | 
						|
      node = address: { pkgs, ... }: with pkgs.lib; {
 | 
						|
        #virtualisation.vlans = [ 1 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          vlans.vlan = {
 | 
						|
            id = 1;
 | 
						|
            interface = "eth0";
 | 
						|
          };
 | 
						|
          interfaces.eth0.ipv4.addresses = mkOverride 0 [ ];
 | 
						|
          interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
 | 
						|
          interfaces.vlan.ipv4.addresses = mkOverride 0
 | 
						|
            [ { inherit address; prefixLength = 24; } ];
 | 
						|
        };
 | 
						|
      };
 | 
						|
    in {
 | 
						|
      name = "vlan";
 | 
						|
      nodes.client1 = node "192.168.1.1";
 | 
						|
      nodes.client2 = node "192.168.1.2";
 | 
						|
      testScript = { ... }:
 | 
						|
        ''
 | 
						|
          start_all()
 | 
						|
 | 
						|
          with subtest("Wait for networking to be configured"):
 | 
						|
              client1.wait_for_unit("network.target")
 | 
						|
              client2.wait_for_unit("network.target")
 | 
						|
 | 
						|
          with subtest("Test vlan is setup"):
 | 
						|
              client1.succeed("ip addr show dev vlan >&2")
 | 
						|
              client2.succeed("ip addr show dev vlan >&2")
 | 
						|
        '';
 | 
						|
    };
 | 
						|
    virtual = {
 | 
						|
      name = "Virtual";
 | 
						|
      machine = {
 | 
						|
        networking.useNetworkd = networkd;
 | 
						|
        networking.useDHCP = false;
 | 
						|
        networking.interfaces.tap0 = {
 | 
						|
          ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
 | 
						|
          ipv6.addresses = [ { address = "2001:1470:fffd:2096::"; prefixLength = 64; } ];
 | 
						|
          virtual = true;
 | 
						|
          mtu = 1342;
 | 
						|
          macAddress = "02:de:ad:be:ef:01";
 | 
						|
        };
 | 
						|
        networking.interfaces.tun0 = {
 | 
						|
          ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
 | 
						|
          ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
 | 
						|
          virtual = true;
 | 
						|
          mtu = 1343;
 | 
						|
        };
 | 
						|
      };
 | 
						|
 | 
						|
      testScript = ''
 | 
						|
        targetList = """
 | 
						|
        tap0: tap persist user 0
 | 
						|
        tun0: tun persist user 0
 | 
						|
        """.strip()
 | 
						|
 | 
						|
        with subtest("Wait for networking to come up"):
 | 
						|
            machine.start()
 | 
						|
            machine.wait_for_unit("network.target")
 | 
						|
 | 
						|
        with subtest("Test interfaces set up"):
 | 
						|
            list = machine.succeed("ip tuntap list | sort").strip()
 | 
						|
            assert (
 | 
						|
                list == targetList
 | 
						|
            ), """
 | 
						|
            The list of virtual interfaces does not match the expected one:
 | 
						|
            Result:
 | 
						|
              {}
 | 
						|
            Expected:
 | 
						|
              {}
 | 
						|
            """.format(
 | 
						|
                list, targetList
 | 
						|
            )
 | 
						|
        with subtest("Test MTU and MAC Address are configured"):
 | 
						|
            machine.wait_until_succeeds("ip link show dev tap0 | grep 'mtu 1342'")
 | 
						|
            machine.wait_until_succeeds("ip link show dev tun0 | grep 'mtu 1343'")
 | 
						|
            assert "02:de:ad:be:ef:01" in machine.succeed("ip link show dev tap0")
 | 
						|
      '' # network-addresses-* only exist in scripted networking
 | 
						|
      + optionalString (!networkd) ''
 | 
						|
        with subtest("Test interfaces clean up"):
 | 
						|
            machine.succeed("systemctl stop network-addresses-tap0")
 | 
						|
            machine.sleep(10)
 | 
						|
            machine.succeed("systemctl stop network-addresses-tun0")
 | 
						|
            machine.sleep(10)
 | 
						|
            residue = machine.succeed("ip tuntap list")
 | 
						|
            assert (
 | 
						|
                residue is ""
 | 
						|
            ), "Some virtual interface has not been properly cleaned:\n{}".format(residue)
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    privacy = {
 | 
						|
      name = "Privacy";
 | 
						|
      nodes.router = { ... }: {
 | 
						|
        virtualisation.vlans = [ 1 ];
 | 
						|
        boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          interfaces.eth1.ipv6.addresses = singleton {
 | 
						|
            address = "fd00:1234:5678:1::1";
 | 
						|
            prefixLength = 64;
 | 
						|
          };
 | 
						|
        };
 | 
						|
        services.radvd = {
 | 
						|
          enable = true;
 | 
						|
          config = ''
 | 
						|
            interface eth1 {
 | 
						|
              AdvSendAdvert on;
 | 
						|
              AdvManagedFlag on;
 | 
						|
              AdvOtherConfigFlag on;
 | 
						|
 | 
						|
              prefix fd00:1234:5678:1::/64 {
 | 
						|
                AdvAutonomous on;
 | 
						|
                AdvOnLink on;
 | 
						|
              };
 | 
						|
            };
 | 
						|
          '';
 | 
						|
        };
 | 
						|
      };
 | 
						|
      nodes.client_with_privacy = { pkgs, ... }: with pkgs.lib; {
 | 
						|
        virtualisation.vlans = [ 1 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          interfaces.eth1 = {
 | 
						|
            tempAddress = "default";
 | 
						|
            ipv4.addresses = mkOverride 0 [ ];
 | 
						|
            ipv6.addresses = mkOverride 0 [ ];
 | 
						|
            useDHCP = true;
 | 
						|
          };
 | 
						|
        };
 | 
						|
      };
 | 
						|
      nodes.client = { pkgs, ... }: with pkgs.lib; {
 | 
						|
        virtualisation.vlans = [ 1 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
          interfaces.eth1 = {
 | 
						|
            tempAddress = "enabled";
 | 
						|
            ipv4.addresses = mkOverride 0 [ ];
 | 
						|
            ipv6.addresses = mkOverride 0 [ ];
 | 
						|
            useDHCP = true;
 | 
						|
          };
 | 
						|
        };
 | 
						|
      };
 | 
						|
      testScript = { ... }:
 | 
						|
        ''
 | 
						|
          start_all()
 | 
						|
 | 
						|
          client.wait_for_unit("network.target")
 | 
						|
          client_with_privacy.wait_for_unit("network.target")
 | 
						|
          router.wait_for_unit("network-online.target")
 | 
						|
 | 
						|
          with subtest("Wait until we have an ip address"):
 | 
						|
              client_with_privacy.wait_until_succeeds(
 | 
						|
                  "ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'"
 | 
						|
              )
 | 
						|
              client.wait_until_succeeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'")
 | 
						|
 | 
						|
          with subtest("Test vlan 1"):
 | 
						|
              client_with_privacy.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
 | 
						|
              client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
 | 
						|
 | 
						|
          with subtest("Test address used is temporary"):
 | 
						|
              client_with_privacy.wait_until_succeeds(
 | 
						|
                  "! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
 | 
						|
              )
 | 
						|
 | 
						|
          with subtest("Test address used is EUI-64"):
 | 
						|
              client.wait_until_succeeds(
 | 
						|
                  "ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
 | 
						|
              )
 | 
						|
        '';
 | 
						|
    };
 | 
						|
    routes = {
 | 
						|
      name = "routes";
 | 
						|
      machine = {
 | 
						|
        networking.useDHCP = false;
 | 
						|
        networking.interfaces.eth0 = {
 | 
						|
          ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
 | 
						|
          ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
 | 
						|
          ipv6.routes = [
 | 
						|
            { address = "fdfd:b3f0::"; prefixLength = 48; }
 | 
						|
            { address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; }
 | 
						|
          ];
 | 
						|
          ipv4.routes = [
 | 
						|
            { address = "10.0.0.0"; prefixLength = 16; options = { mtu = "1500"; }; }
 | 
						|
            { address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; }
 | 
						|
          ];
 | 
						|
        };
 | 
						|
        virtualisation.vlans = [ ];
 | 
						|
      };
 | 
						|
 | 
						|
      testScript = ''
 | 
						|
        targetIPv4Table = [
 | 
						|
            "10.0.0.0/16 proto static scope link mtu 1500",
 | 
						|
            "192.168.1.0/24 proto kernel scope link src 192.168.1.2",
 | 
						|
            "192.168.2.0/24 via 192.168.1.1 proto static",
 | 
						|
        ]
 | 
						|
 | 
						|
        targetIPv6Table = [
 | 
						|
            "2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium",
 | 
						|
            "2001:1470:fffd:2098::/64 via fdfd:b3f0::1 proto static metric 1024 pref medium",
 | 
						|
            "fdfd:b3f0::/48 proto static metric 1024 pref medium",
 | 
						|
        ]
 | 
						|
 | 
						|
        machine.start()
 | 
						|
        machine.wait_for_unit("network.target")
 | 
						|
 | 
						|
        with subtest("test routing tables"):
 | 
						|
            ipv4Table = machine.succeed("ip -4 route list dev eth0 | head -n3").strip()
 | 
						|
            ipv6Table = machine.succeed("ip -6 route list dev eth0 | head -n3").strip()
 | 
						|
            assert [
 | 
						|
                l.strip() for l in ipv4Table.splitlines()
 | 
						|
            ] == targetIPv4Table, """
 | 
						|
              The IPv4 routing table does not match the expected one:
 | 
						|
                Result:
 | 
						|
                  {}
 | 
						|
                Expected:
 | 
						|
                  {}
 | 
						|
              """.format(
 | 
						|
                ipv4Table, targetIPv4Table
 | 
						|
            )
 | 
						|
            assert [
 | 
						|
                l.strip() for l in ipv6Table.splitlines()
 | 
						|
            ] == targetIPv6Table, """
 | 
						|
              The IPv6 routing table does not match the expected one:
 | 
						|
                Result:
 | 
						|
                  {}
 | 
						|
                Expected:
 | 
						|
                  {}
 | 
						|
              """.format(
 | 
						|
                ipv6Table, targetIPv6Table
 | 
						|
            )
 | 
						|
 | 
						|
        with subtest("test clean-up of the tables"):
 | 
						|
            machine.succeed("systemctl stop network-addresses-eth0")
 | 
						|
            ipv4Residue = machine.succeed("ip -4 route list dev eth0 | head -n-3").strip()
 | 
						|
            ipv6Residue = machine.succeed("ip -6 route list dev eth0 | head -n-3").strip()
 | 
						|
            assert (
 | 
						|
                ipv4Residue is ""
 | 
						|
            ), "The IPv4 routing table has not been properly cleaned:\n{}".format(ipv4Residue)
 | 
						|
            assert (
 | 
						|
                ipv6Residue is ""
 | 
						|
            ), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue)
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    rename = {
 | 
						|
      name = "RenameInterface";
 | 
						|
      machine = { pkgs, ... }: {
 | 
						|
        virtualisation.vlans = [ 1 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
        };
 | 
						|
      } //
 | 
						|
      (if networkd
 | 
						|
       then { systemd.network.links."10-custom_name" = {
 | 
						|
                matchConfig.MACAddress = "52:54:00:12:01:01";
 | 
						|
                linkConfig.Name = "custom_name";
 | 
						|
              };
 | 
						|
            }
 | 
						|
       else { services.udev.initrdRules = ''
 | 
						|
               SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="52:54:00:12:01:01", KERNEL=="eth*", NAME="custom_name"
 | 
						|
              '';
 | 
						|
            });
 | 
						|
      testScript = ''
 | 
						|
        machine.succeed("udevadm settle")
 | 
						|
        print(machine.succeed("ip link show dev custom_name"))
 | 
						|
      '';
 | 
						|
    };
 | 
						|
    # even with disabled networkd, systemd.network.links should work
 | 
						|
    # (as it's handled by udev, not networkd)
 | 
						|
    link = {
 | 
						|
      name = "Link";
 | 
						|
      nodes.client = { pkgs, ... }: {
 | 
						|
        virtualisation.vlans = [ 1 ];
 | 
						|
        networking = {
 | 
						|
          useNetworkd = networkd;
 | 
						|
          useDHCP = false;
 | 
						|
        };
 | 
						|
        systemd.network.links."50-foo" = {
 | 
						|
          matchConfig = {
 | 
						|
            Name = "foo";
 | 
						|
            Driver = "dummy";
 | 
						|
          };
 | 
						|
          linkConfig.MTUBytes = "1442";
 | 
						|
        };
 | 
						|
      };
 | 
						|
      testScript = ''
 | 
						|
        print(client.succeed("ip l add name foo type dummy"))
 | 
						|
        print(client.succeed("stat /etc/systemd/network/50-foo.link"))
 | 
						|
        client.succeed("udevadm settle")
 | 
						|
        assert "mtu 1442" in client.succeed("ip l show dummy0")
 | 
						|
      '';
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
in mapAttrs (const (attrs: makeTest (attrs // {
 | 
						|
  name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
 | 
						|
}))) testCases
 |