at 23.11-pre 5.6 kB view raw
1import ./make-test-python.nix ({ pkgs, lib, ... }: let 2 inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; 3 4 mkNode = vlan: id: { 5 virtualisation.vlans = [ vlan ]; 6 networking = { 7 useDHCP = false; 8 useNetworkd = true; 9 }; 10 11 systemd.network = { 12 enable = true; 13 14 networks."10-eth${toString vlan}" = { 15 matchConfig.Name = "eth${toString vlan}"; 16 linkConfig.RequiredForOnline = "no"; 17 networkConfig = { 18 Address = "192.168.${toString vlan}.${toString id}/24"; 19 IPForward = "yes"; 20 }; 21 }; 22 }; 23 }; 24in { 25 name = "systemd-networkd-vrf"; 26 meta.maintainers = with lib.maintainers; [ ma27 ]; 27 28 nodes = { 29 client = { pkgs, ... }: { 30 virtualisation.vlans = [ 1 2 ]; 31 32 networking = { 33 useDHCP = false; 34 useNetworkd = true; 35 firewall.checkReversePath = "loose"; 36 }; 37 38 systemd.network = { 39 enable = true; 40 41 netdevs."10-vrf1" = { 42 netdevConfig = { 43 Kind = "vrf"; 44 Name = "vrf1"; 45 MTUBytes = "1300"; 46 }; 47 vrfConfig.Table = 23; 48 }; 49 netdevs."10-vrf2" = { 50 netdevConfig = { 51 Kind = "vrf"; 52 Name = "vrf2"; 53 MTUBytes = "1300"; 54 }; 55 vrfConfig.Table = 42; 56 }; 57 58 networks."10-vrf1" = { 59 matchConfig.Name = "vrf1"; 60 networkConfig.IPForward = "yes"; 61 routes = [ 62 { routeConfig = { Destination = "192.168.1.2"; Metric = 100; }; } 63 ]; 64 }; 65 networks."10-vrf2" = { 66 matchConfig.Name = "vrf2"; 67 networkConfig.IPForward = "yes"; 68 routes = [ 69 { routeConfig = { Destination = "192.168.2.3"; Metric = 100; }; } 70 ]; 71 }; 72 73 networks."10-eth1" = { 74 matchConfig.Name = "eth1"; 75 linkConfig.RequiredForOnline = "no"; 76 networkConfig = { 77 VRF = "vrf1"; 78 Address = "192.168.1.1/24"; 79 IPForward = "yes"; 80 }; 81 }; 82 networks."10-eth2" = { 83 matchConfig.Name = "eth2"; 84 linkConfig.RequiredForOnline = "no"; 85 networkConfig = { 86 VRF = "vrf2"; 87 Address = "192.168.2.1/24"; 88 IPForward = "yes"; 89 }; 90 }; 91 }; 92 }; 93 94 node1 = lib.mkMerge [ 95 (mkNode 1 2) 96 { 97 services.openssh.enable = true; 98 users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; 99 } 100 ]; 101 102 node2 = mkNode 2 3; 103 node3 = mkNode 2 4; 104 }; 105 106 testScript = '' 107 import json 108 109 def compare(raw_json, to_compare): 110 data = json.loads(raw_json) 111 assert len(raw_json) >= len(to_compare) 112 for i, row in enumerate(to_compare): 113 actual = data[i] 114 assert len(row.keys()) > 0 115 for key, value in row.items(): 116 assert value == actual[key], f""" 117 In entry {i}, value {key}: got: {actual[key]}, expected {value} 118 """ 119 120 121 start_all() 122 123 client.wait_for_unit("network.target") 124 node1.wait_for_unit("network.target") 125 node2.wait_for_unit("network.target") 126 node3.wait_for_unit("network.target") 127 128 # Check that networkd properly configures the main routing table 129 # and the routing tables for the VRF. 130 with subtest("check vrf routing tables"): 131 compare( 132 client.succeed("ip --json -4 route list"), 133 [ 134 {"dst": "192.168.1.2", "dev": "vrf1", "metric": 100}, 135 {"dst": "192.168.2.3", "dev": "vrf2", "metric": 100} 136 ] 137 ) 138 compare( 139 client.succeed("ip --json -4 route list table 23"), 140 [ 141 {"dst": "192.168.1.0/24", "dev": "eth1", "prefsrc": "192.168.1.1"}, 142 {"type": "local", "dst": "192.168.1.1", "dev": "eth1", "prefsrc": "192.168.1.1"}, 143 {"type": "broadcast", "dev": "eth1", "prefsrc": "192.168.1.1", "dst": "192.168.1.255"} 144 ] 145 ) 146 compare( 147 client.succeed("ip --json -4 route list table 42"), 148 [ 149 {"dst": "192.168.2.0/24", "dev": "eth2", "prefsrc": "192.168.2.1"}, 150 {"type": "local", "dst": "192.168.2.1", "dev": "eth2", "prefsrc": "192.168.2.1"}, 151 {"type": "broadcast", "dev": "eth2", "prefsrc": "192.168.2.1", "dst": "192.168.2.255"} 152 ] 153 ) 154 155 # Ensure that other nodes are reachable via ICMP through the VRF. 156 with subtest("icmp through vrf works"): 157 client.succeed("ping -c5 192.168.1.2") 158 client.succeed("ping -c5 192.168.2.3") 159 160 # Test whether TCP through a VRF IP is possible. 161 with subtest("tcp traffic through vrf works"): 162 node1.wait_for_open_port(22) 163 client.succeed( 164 "cat ${snakeOilPrivateKey} > privkey.snakeoil" 165 ) 166 client.succeed("chmod 600 privkey.snakeoil") 167 client.succeed( 168 "ulimit -l 2048; ip vrf exec vrf1 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true" 169 ) 170 171 # Only configured routes through the VRF from the main routing table should 172 # work. Additional IPs are only reachable when binding to the vrf interface. 173 with subtest("only routes from main routing table work by default"): 174 client.fail("ping -c5 192.168.2.4") 175 client.succeed("ping -I vrf2 -c5 192.168.2.4") 176 177 client.shutdown() 178 node1.shutdown() 179 node2.shutdown() 180 node3.shutdown() 181 ''; 182})