diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix index 5173c33cab7..e850c1f6cdd 100644 --- a/nixos/release-combined.nix +++ b/nixos/release-combined.nix @@ -66,6 +66,14 @@ in rec { (all nixos.tests.misc) (all nixos.tests.nat.firewall) (all nixos.tests.nat.standalone) + (all nixos.tests.networking.scripted.static) + (all nixos.tests.networking.scripted.dhcpSimple) + (all nixos.tests.networking.scripted.dhcpOneIf) + (all nixos.tests.networking.scripted.bond) + (all nixos.tests.networking.scripted.bridge) + (all nixos.tests.networking.scripted.macvlan) + (all nixos.tests.networking.scripted.sit) + (all nixos.tests.networking.scripted.vlan) (all nixos.tests.nfs3) (all nixos.tests.openssh) (all nixos.tests.printing) diff --git a/nixos/release.nix b/nixos/release.nix index 5c08b26c3cf..bae03130163 100644 --- a/nixos/release.nix +++ b/nixos/release.nix @@ -268,6 +268,22 @@ in rec { tests.mysqlReplication = callTest tests/mysql-replication.nix {}; tests.nat.firewall = callTest tests/nat.nix { withFirewall = true; }; tests.nat.standalone = callTest tests/nat.nix { withFirewall = false; }; + tests.networking.networkd.static = callTest tests/networking.nix { networkd = true; test = "static"; }; + tests.networking.networkd.dhcpSimple = callTest tests/networking.nix { networkd = true; test = "dhcpSimple"; }; + tests.networking.networkd.dhcpOneIf = callTest tests/networking.nix { networkd = true; test = "dhcpOneIf"; }; + tests.networking.networkd.bond = callTest tests/networking.nix { networkd = true; test = "bond"; }; + tests.networking.networkd.bridge = callTest tests/networking.nix { networkd = true; test = "bridge"; }; + tests.networking.networkd.macvlan = callTest tests/networking.nix { networkd = true; test = "macvlan"; }; + tests.networking.networkd.sit = callTest tests/networking.nix { networkd = true; test = "sit"; }; + tests.networking.networkd.vlan = callTest tests/networking.nix { networkd = true; test = "vlan"; }; + tests.networking.scripted.static = callTest tests/networking.nix { networkd = false; test = "static"; }; + tests.networking.scripted.dhcpSimple = callTest tests/networking.nix { networkd = false; test = "dhcpSimple"; }; + tests.networking.scripted.dhcpOneIf = callTest tests/networking.nix { networkd = false; test = "dhcpOneIf"; }; + tests.networking.scripted.bond = callTest tests/networking.nix { networkd = false; test = "bond"; }; + tests.networking.scripted.bridge = callTest tests/networking.nix { networkd = false; test = "bridge"; }; + tests.networking.scripted.macvlan = callTest tests/networking.nix { networkd = false; test = "macvlan"; }; + tests.networking.scripted.sit = callTest tests/networking.nix { networkd = false; test = "sit"; }; + tests.networking.scripted.vlan = callTest tests/networking.nix { networkd = false; test = "vlan"; }; tests.nfs3 = callTest tests/nfs.nix { version = 3; }; tests.nsd = callTest tests/nsd.nix {}; tests.openssh = callTest tests/openssh.nix {}; diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix new file mode 100644 index 00000000000..b5e09cc3950 --- /dev/null +++ b/nixos/tests/networking.nix @@ -0,0 +1,365 @@ +import ./make-test.nix ({ networkd, test, ... }: + let + router = { config, pkgs, ... }: + with pkgs.lib; + let + vlanIfs = range 1 (length config.virtualisation.vlans); + in { + virtualisation.vlans = [ 1 2 3 ]; + networking = { + useDHCP = false; + useNetworkd = networkd; + firewall.allowPing = true; + interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n: + nameValuePair "eth${toString n}" { + ipAddress = "192.168.${toString n}.1"; + prefixLength = 24; + }))); + }; + services.dhcpd = { + enable = true; + interfaces = map (n: "eth${toString n}") vlanIfs; + extraConfig = '' + option subnet-mask 255.255.255.0; + '' + flip concatMapStrings vlanIfs (n: '' + subnet 192.168.${toString n}.0 netmask 255.255.255.0 { + option broadcast-address 192.168.${toString n}.255; + option routers 192.168.${toString n}.1; + range 192.168.${toString n}.2 192.168.${toString n}.254; + } + ''); + }; + }; + testCases = { + static = { + name = "Static"; + nodes.router = router; + nodes.client = { config, pkgs, ... }: with pkgs.lib; { + virtualisation.vlans = [ 1 2 ]; + networking = { + useNetworkd = networkd; + firewall.allowPing = true; + useDHCP = false; + defaultGateway = "192.168.1.1"; + interfaces.eth1.ip4 = 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.ip4 = mkOverride 0 [ + { address = "192.168.2.2"; prefixLength = 24; } + ]; + }; + }; + testScript = { nodes, ... }: + '' + startAll; + + $client->waitForUnit("network.target"); + $router->waitForUnit("network.target"); + + # Make sure dhcpcd is not started + $client->fail("systemctl status dhcpcd.service"); + + # Test vlan 1 + $client->succeed("ping -c 1 192.168.1.1"); + $client->succeed("ping -c 1 192.168.1.2"); + $client->succeed("ping -c 1 192.168.1.3"); + $client->succeed("ping -c 1 192.168.1.10"); + + $router->succeed("ping -c 1 192.168.1.1"); + $router->succeed("ping -c 1 192.168.1.2"); + $router->succeed("ping -c 1 192.168.1.3"); + $router->succeed("ping -c 1 192.168.1.10"); + + # Test vlan 2 + $client->succeed("ping -c 1 192.168.2.1"); + $client->succeed("ping -c 1 192.168.2.2"); + + $router->succeed("ping -c 1 192.168.2.1"); + $router->succeed("ping -c 1 192.168.2.2"); + + # Test default gateway + $router->succeed("ping -c 1 192.168.3.1"); + $client->succeed("ping -c 1 192.168.3.1"); + ''; + }; + dhcpSimple = { + name = "SimpleDHCP"; + nodes.router = router; + nodes.client = { config, pkgs, ... }: with pkgs.lib; { + virtualisation.vlans = [ 1 2 ]; + networking = { + useNetworkd = networkd; + firewall.allowPing = true; + useDHCP = true; + interfaces.eth1.ip4 = mkOverride 0 [ ]; + interfaces.eth2.ip4 = mkOverride 0 [ ]; + }; + }; + testScript = { nodes, ... }: + '' + startAll; + + $client->waitForUnit("network.target"); + $router->waitForUnit("network.target"); + $client->waitForUnit("dhcpcd.service"); + + # Wait until we have an ip address on each interface + $client->succeed("while ! ip addr show dev eth1 | grep '192.168.1'; do true; done"); + $client->succeed("while ! ip addr show dev eth2 | grep '192.168.2'; do true; done"); + + # Test vlan 1 + $client->succeed("ping -c 1 192.168.1.1"); + $client->succeed("ping -c 1 192.168.1.2"); + + $router->succeed("ping -c 1 192.168.1.1"); + $router->succeed("ping -c 1 192.168.1.2"); + + # Test vlan 2 + $client->succeed("ping -c 1 192.168.2.1"); + $client->succeed("ping -c 1 192.168.2.2"); + + $router->succeed("ping -c 1 192.168.2.1"); + $router->succeed("ping -c 1 192.168.2.2"); + ''; + }; + dhcpOneIf = { + name = "OneInterfaceDHCP"; + nodes.router = router; + nodes.client = { config, pkgs, ... }: with pkgs.lib; { + virtualisation.vlans = [ 1 2 ]; + networking = { + useNetworkd = networkd; + firewall.allowPing = true; + useDHCP = false; + interfaces.eth1 = { + ip4 = mkOverride 0 [ ]; + useDHCP = true; + }; + interfaces.eth2.ip4 = mkOverride 0 [ ]; + }; + }; + testScript = { nodes, ... }: + '' + startAll; + + $client->waitForUnit("network.target"); + $router->waitForUnit("network.target"); + $client->waitForUnit("dhcpcd.service"); + + # Wait until we have an ip address on each interface + $client->succeed("while ! ip addr show dev eth1 | grep '192.168.1'; do true; done"); + + # Test vlan 1 + $client->succeed("ping -c 1 192.168.1.1"); + $client->succeed("ping -c 1 192.168.1.2"); + + $router->succeed("ping -c 1 192.168.1.1"); + $router->succeed("ping -c 1 192.168.1.2"); + + # Test vlan 2 + $client->succeed("ping -c 1 192.168.2.1"); + $client->fail("ping -c 1 192.168.2.2"); + + $router->succeed("ping -c 1 192.168.2.1"); + $router->fail("ping -c 1 192.168.2.2"); + ''; + }; + bond = let + node = address: { config, pkgs, ... }: with pkgs.lib; { + virtualisation.vlans = [ 1 2 ]; + networking = { + useNetworkd = networkd; + firewall.allowPing = true; + useDHCP = false; + bonds.bond = { + mode = "balance-rr"; + interfaces = [ "eth1" "eth2" ]; + }; + interfaces.bond.ip4 = mkOverride 0 + [ { inherit address; prefixLength = 30; } ]; + }; + }; + in { + name = "Bond"; + nodes.client1 = node "192.168.1.1"; + nodes.client2 = node "192.168.1.2"; + testScript = { nodes, ... }: + '' + startAll; + + $client1->waitForUnit("network.target"); + $client2->waitForUnit("network.target"); + + # Test bonding + $client1->succeed("ping -c 2 192.168.1.1"); + $client1->succeed("ping -c 2 192.168.1.2"); + + $client2->succeed("ping -c 2 192.168.1.1"); + $client2->succeed("ping -c 2 192.168.1.2"); + ''; + }; + bridge = let + node = { address, vlan }: { config, pkgs, ... }: with pkgs.lib; { + virtualisation.vlans = [ vlan ]; + networking = { + useNetworkd = networkd; + firewall.allowPing = true; + useDHCP = false; + interfaces.eth1.ip4 = 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 = { config, pkgs, ... }: with pkgs.lib; { + virtualisation.vlans = [ 1 2 ]; + networking = { + useNetworkd = networkd; + firewall.allowPing = true; + useDHCP = false; + bridges.bridge.interfaces = [ "eth1" "eth2" ]; + interfaces.eth1.ip4 = mkOverride 0 [ ]; + interfaces.eth2.ip4 = mkOverride 0 [ ]; + interfaces.bridge.ip4 = mkOverride 0 + [ { address = "192.168.1.1"; prefixLength = 24; } ]; + }; + }; + testScript = { nodes, ... }: + '' + startAll; + + $client1->waitForUnit("network.target"); + $client2->waitForUnit("network.target"); + $router->waitForUnit("network.target"); + + # Test bridging + $client1->succeed("ping -c 1 192.168.1.1"); + $client1->succeed("ping -c 1 192.168.1.2"); + $client1->succeed("ping -c 1 192.168.1.3"); + + $client2->succeed("ping -c 1 192.168.1.1"); + $client2->succeed("ping -c 1 192.168.1.2"); + $client2->succeed("ping -c 1 192.168.1.3"); + + $router->succeed("ping -c 1 192.168.1.1"); + $router->succeed("ping -c 1 192.168.1.2"); + $router->succeed("ping -c 1 192.168.1.3"); + ''; + }; + macvlan = { + name = "MACVLAN"; + nodes.router = router; + nodes.client = { config, pkgs, ... }: with pkgs.lib; { + virtualisation.vlans = [ 1 ]; + networking = { + useNetworkd = networkd; + firewall.allowPing = true; + useDHCP = true; + macvlans.macvlan.interface = "eth1"; + interfaces.eth1.ip4 = mkOverride 0 [ ]; + }; + }; + testScript = { nodes, ... }: + '' + startAll; + + $client->waitForUnit("network.target"); + $router->waitForUnit("network.target"); + $client->waitForUnit("dhcpcd.service"); + + # Wait until we have an ip address on each interface + $client->succeed("while ! ip addr show dev eth1 | grep '192.168.1'; do true; done"); + $client->succeed("while ! ip addr show dev macvlan | grep '192.168.1'; do true; done"); + + # Test macvlan + $client->succeed("ping -c 1 192.168.1.1"); + $client->succeed("ping -c 1 192.168.1.2"); + $client->succeed("ping -c 1 192.168.1.3"); + + $router->succeed("ping -c 1 192.168.1.1"); + $router->succeed("ping -c 1 192.168.1.2"); + $router->succeed("ping -c 1 192.168.1.3"); + ''; + }; + sit = let + node = { address4, remote, address6 }: { config, 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.ip4 = mkOverride 0 + [ { address = address4; prefixLength = 24; } ]; + interfaces.sit.ip6 = 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 = { nodes, ... }: + '' + startAll; + + $client1->waitForUnit("network.target"); + $client2->waitForUnit("network.target"); + + $client1->succeed("ip addr >&2"); + $client2->succeed("ip addr >&2"); + + # Test ipv6 + $client1->succeed("ping6 -c 1 fc00::1"); + $client1->succeed("ping6 -c 1 fc00::2"); + + $client2->succeed("ping6 -c 1 fc00::1"); + $client2->succeed("ping6 -c 1 fc00::2"); + ''; + }; + vlan = let + node = address: { config, pkgs, ... }: with pkgs.lib; { + #virtualisation.vlans = [ 1 ]; + networking = { + useNetworkd = networkd; + firewall.allowPing = true; + useDHCP = false; + vlans.vlan = { + id = 1; + interface = "eth0"; + }; + interfaces.eth0.ip4 = mkOverride 0 [ ]; + interfaces.eth1.ip4 = mkOverride 0 [ ]; + interfaces.vlan.ip4 = mkOverride 0 + [ { inherit address; prefixLength = 24; } ]; + }; + }; + in { + name = "vlan"; + nodes.client1 = node "192.168.1.1"; + nodes.client2 = node "192.168.1.2"; + testScript = { nodes, ... }: + '' + startAll; + + $client1->waitForUnit("network.target"); + $client2->waitForUnit("network.target"); + + # Test vlan is setup + $client1->succeed("ip addr show dev vlan >&2"); + $client2->succeed("ip addr show dev vlan >&2"); + ''; + }; + }; + case = testCases.${test}; + in case // { + name = "${case.name}-Networking-${if networkd then "Networkd" else "Scripted"}"; + })