222 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| import ./make-test-python.nix ({ pkgs, lib, ... }: let
 | |
|   inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
 | |
| in {
 | |
|   name = "systemd-networkd-vrf";
 | |
|   meta.maintainers = with lib.maintainers; [ ma27 ];
 | |
| 
 | |
|   nodes = {
 | |
|     client = { pkgs, ... }: {
 | |
|       virtualisation.vlans = [ 1 2 ];
 | |
| 
 | |
|       networking = {
 | |
|         useDHCP = false;
 | |
|         useNetworkd = true;
 | |
|         firewall.checkReversePath = "loose";
 | |
|       };
 | |
| 
 | |
|       systemd.network = {
 | |
|         enable = true;
 | |
| 
 | |
|         netdevs."10-vrf1" = {
 | |
|           netdevConfig = {
 | |
|             Kind = "vrf";
 | |
|             Name = "vrf1";
 | |
|             MTUBytes = "1300";
 | |
|           };
 | |
|           vrfConfig.Table = 23;
 | |
|         };
 | |
|         netdevs."10-vrf2" = {
 | |
|           netdevConfig = {
 | |
|             Kind = "vrf";
 | |
|             Name = "vrf2";
 | |
|             MTUBytes = "1300";
 | |
|           };
 | |
|           vrfConfig.Table = 42;
 | |
|         };
 | |
| 
 | |
|         networks."10-vrf1" = {
 | |
|           matchConfig.Name = "vrf1";
 | |
|           networkConfig.IPForward = "yes";
 | |
|           routes = [
 | |
|             { routeConfig = { Destination = "192.168.1.2"; Metric = 100; }; }
 | |
|           ];
 | |
|         };
 | |
|         networks."10-vrf2" = {
 | |
|           matchConfig.Name = "vrf2";
 | |
|           networkConfig.IPForward = "yes";
 | |
|           routes = [
 | |
|             { routeConfig = { Destination = "192.168.2.3"; Metric = 100; }; }
 | |
|           ];
 | |
|         };
 | |
| 
 | |
|         networks."10-eth1" = {
 | |
|           matchConfig.Name = "eth1";
 | |
|           linkConfig.RequiredForOnline = "no";
 | |
|           networkConfig = {
 | |
|             VRF = "vrf1";
 | |
|             Address = "192.168.1.1";
 | |
|             IPForward = "yes";
 | |
|           };
 | |
|         };
 | |
|         networks."10-eth2" = {
 | |
|           matchConfig.Name = "eth2";
 | |
|           linkConfig.RequiredForOnline = "no";
 | |
|           networkConfig = {
 | |
|             VRF = "vrf2";
 | |
|             Address = "192.168.2.1";
 | |
|             IPForward = "yes";
 | |
|           };
 | |
|         };
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     node1 = { pkgs, ... }: {
 | |
|       virtualisation.vlans = [ 1 ];
 | |
|       networking = {
 | |
|         useDHCP = false;
 | |
|         useNetworkd = true;
 | |
|       };
 | |
| 
 | |
|       services.openssh.enable = true;
 | |
|       users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
 | |
| 
 | |
|       systemd.network = {
 | |
|         enable = true;
 | |
| 
 | |
|         networks."10-eth1" = {
 | |
|           matchConfig.Name = "eth1";
 | |
|           linkConfig.RequiredForOnline = "no";
 | |
|           networkConfig = {
 | |
|             Address = "192.168.1.2";
 | |
|             IPForward = "yes";
 | |
|           };
 | |
|         };
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     node2 = { pkgs, ... }: {
 | |
|       virtualisation.vlans = [ 2 ];
 | |
|       networking = {
 | |
|         useDHCP = false;
 | |
|         useNetworkd = true;
 | |
|       };
 | |
| 
 | |
|       systemd.network = {
 | |
|         enable = true;
 | |
| 
 | |
|         networks."10-eth2" = {
 | |
|           matchConfig.Name = "eth2";
 | |
|           linkConfig.RequiredForOnline = "no";
 | |
|           networkConfig = {
 | |
|             Address = "192.168.2.3";
 | |
|             IPForward = "yes";
 | |
|           };
 | |
|         };
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     node3 = { pkgs, ... }: {
 | |
|       virtualisation.vlans = [ 2 ];
 | |
|       networking = {
 | |
|         useDHCP = false;
 | |
|         useNetworkd = true;
 | |
|       };
 | |
| 
 | |
|       systemd.network = {
 | |
|         enable = true;
 | |
| 
 | |
|         networks."10-eth2" = {
 | |
|           matchConfig.Name = "eth2";
 | |
|           linkConfig.RequiredForOnline = "no";
 | |
|           networkConfig = {
 | |
|             Address = "192.168.2.4";
 | |
|             IPForward = "yes";
 | |
|           };
 | |
|         };
 | |
|       };
 | |
|     };
 | |
|   };
 | |
| 
 | |
|   testScript = ''
 | |
|     def compare_tables(expected, actual):
 | |
|         assert (
 | |
|             expected == actual
 | |
|         ), """
 | |
|         Routing tables don't match!
 | |
|         Expected:
 | |
|           {}
 | |
|         Actual:
 | |
|           {}
 | |
|         """.format(
 | |
|             expected, actual
 | |
|         )
 | |
| 
 | |
| 
 | |
|     start_all()
 | |
| 
 | |
|     client.wait_for_unit("network.target")
 | |
|     node1.wait_for_unit("network.target")
 | |
|     node2.wait_for_unit("network.target")
 | |
|     node3.wait_for_unit("network.target")
 | |
| 
 | |
|     # NOTE: please keep in mind that the trailing whitespaces in the following strings
 | |
|     # are intentional as the output is compared against the raw `iproute2`-output.
 | |
|     client_ipv4_table = """
 | |
|     192.168.1.2 dev vrf1 proto static metric 100 
 | |
|     192.168.2.3 dev vrf2 proto static metric 100
 | |
|     """.strip()
 | |
|     vrf1_table = """
 | |
|     broadcast 192.168.1.0 dev eth1 proto kernel scope link src 192.168.1.1 
 | |
|     192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1 
 | |
|     local 192.168.1.1 dev eth1 proto kernel scope host src 192.168.1.1 
 | |
|     broadcast 192.168.1.255 dev eth1 proto kernel scope link src 192.168.1.1
 | |
|     """.strip()
 | |
|     vrf2_table = """
 | |
|     broadcast 192.168.2.0 dev eth2 proto kernel scope link src 192.168.2.1 
 | |
|     192.168.2.0/24 dev eth2 proto kernel scope link src 192.168.2.1 
 | |
|     local 192.168.2.1 dev eth2 proto kernel scope host src 192.168.2.1 
 | |
|     broadcast 192.168.2.255 dev eth2 proto kernel scope link src 192.168.2.1
 | |
|     """.strip()
 | |
| 
 | |
|     # Check that networkd properly configures the main routing table
 | |
|     # and the routing tables for the VRF.
 | |
|     with subtest("check vrf routing tables"):
 | |
|         compare_tables(
 | |
|             client_ipv4_table, client.succeed("ip -4 route list | head -n2").strip()
 | |
|         )
 | |
|         compare_tables(
 | |
|             vrf1_table, client.succeed("ip -4 route list table 23 | head -n4").strip()
 | |
|         )
 | |
|         compare_tables(
 | |
|             vrf2_table, client.succeed("ip -4 route list table 42 | head -n4").strip()
 | |
|         )
 | |
| 
 | |
|     # Ensure that other nodes are reachable via ICMP through the VRF.
 | |
|     with subtest("icmp through vrf works"):
 | |
|         client.succeed("ping -c5 192.168.1.2")
 | |
|         client.succeed("ping -c5 192.168.2.3")
 | |
| 
 | |
|     # Test whether TCP through a VRF IP is possible.
 | |
|     with subtest("tcp traffic through vrf works"):
 | |
|         node1.wait_for_open_port(22)
 | |
|         client.succeed(
 | |
|             "cat ${snakeOilPrivateKey} > privkey.snakeoil"
 | |
|         )
 | |
|         client.succeed("chmod 600 privkey.snakeoil")
 | |
|         client.succeed(
 | |
|             "ulimit -l 2048; ip vrf exec vrf1 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true"
 | |
|         )
 | |
| 
 | |
|     # Only configured routes through the VRF from the main routing table should
 | |
|     # work. Additional IPs are only reachable when binding to the vrf interface.
 | |
|     with subtest("only routes from main routing table work by default"):
 | |
|         client.fail("ping -c5 192.168.2.4")
 | |
|         client.succeed("ping -I vrf2 -c5 192.168.2.4")
 | |
| 
 | |
|     client.shutdown()
 | |
|     node1.shutdown()
 | |
|     node2.shutdown()
 | |
|     node3.shutdown()
 | |
|   '';
 | |
| })
 | 
