at 22.05-pre 6.4 kB view raw
1import ./make-test-python.nix ({ pkgs, lib, ... }: let 2 inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; 3in { 4 name = "systemd-networkd-vrf"; 5 meta.maintainers = with lib.maintainers; [ ma27 ]; 6 7 nodes = { 8 client = { pkgs, ... }: { 9 virtualisation.vlans = [ 1 2 ]; 10 11 networking = { 12 useDHCP = false; 13 useNetworkd = true; 14 firewall.checkReversePath = "loose"; 15 }; 16 17 systemd.network = { 18 enable = true; 19 20 netdevs."10-vrf1" = { 21 netdevConfig = { 22 Kind = "vrf"; 23 Name = "vrf1"; 24 MTUBytes = "1300"; 25 }; 26 vrfConfig.Table = 23; 27 }; 28 netdevs."10-vrf2" = { 29 netdevConfig = { 30 Kind = "vrf"; 31 Name = "vrf2"; 32 MTUBytes = "1300"; 33 }; 34 vrfConfig.Table = 42; 35 }; 36 37 networks."10-vrf1" = { 38 matchConfig.Name = "vrf1"; 39 networkConfig.IPForward = "yes"; 40 routes = [ 41 { routeConfig = { Destination = "192.168.1.2"; Metric = 100; }; } 42 ]; 43 }; 44 networks."10-vrf2" = { 45 matchConfig.Name = "vrf2"; 46 networkConfig.IPForward = "yes"; 47 routes = [ 48 { routeConfig = { Destination = "192.168.2.3"; Metric = 100; }; } 49 ]; 50 }; 51 52 networks."10-eth1" = { 53 matchConfig.Name = "eth1"; 54 linkConfig.RequiredForOnline = "no"; 55 networkConfig = { 56 VRF = "vrf1"; 57 Address = "192.168.1.1"; 58 IPForward = "yes"; 59 }; 60 }; 61 networks."10-eth2" = { 62 matchConfig.Name = "eth2"; 63 linkConfig.RequiredForOnline = "no"; 64 networkConfig = { 65 VRF = "vrf2"; 66 Address = "192.168.2.1"; 67 IPForward = "yes"; 68 }; 69 }; 70 }; 71 }; 72 73 node1 = { pkgs, ... }: { 74 virtualisation.vlans = [ 1 ]; 75 networking = { 76 useDHCP = false; 77 useNetworkd = true; 78 }; 79 80 services.openssh.enable = true; 81 users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; 82 83 systemd.network = { 84 enable = true; 85 86 networks."10-eth1" = { 87 matchConfig.Name = "eth1"; 88 linkConfig.RequiredForOnline = "no"; 89 networkConfig = { 90 Address = "192.168.1.2"; 91 IPForward = "yes"; 92 }; 93 }; 94 }; 95 }; 96 97 node2 = { pkgs, ... }: { 98 virtualisation.vlans = [ 2 ]; 99 networking = { 100 useDHCP = false; 101 useNetworkd = true; 102 }; 103 104 systemd.network = { 105 enable = true; 106 107 networks."10-eth2" = { 108 matchConfig.Name = "eth2"; 109 linkConfig.RequiredForOnline = "no"; 110 networkConfig = { 111 Address = "192.168.2.3"; 112 IPForward = "yes"; 113 }; 114 }; 115 }; 116 }; 117 118 node3 = { pkgs, ... }: { 119 virtualisation.vlans = [ 2 ]; 120 networking = { 121 useDHCP = false; 122 useNetworkd = true; 123 }; 124 125 systemd.network = { 126 enable = true; 127 128 networks."10-eth2" = { 129 matchConfig.Name = "eth2"; 130 linkConfig.RequiredForOnline = "no"; 131 networkConfig = { 132 Address = "192.168.2.4"; 133 IPForward = "yes"; 134 }; 135 }; 136 }; 137 }; 138 }; 139 140 testScript = '' 141 def compare_tables(expected, actual): 142 assert ( 143 expected == actual 144 ), """ 145 Routing tables don't match! 146 Expected: 147 {} 148 Actual: 149 {} 150 """.format( 151 expected, actual 152 ) 153 154 155 start_all() 156 157 client.wait_for_unit("network.target") 158 node1.wait_for_unit("network.target") 159 node2.wait_for_unit("network.target") 160 node3.wait_for_unit("network.target") 161 162 # NOTE: please keep in mind that the trailing whitespaces in the following strings 163 # are intentional as the output is compared against the raw `iproute2`-output. 164 client_ipv4_table = """ 165 192.168.1.2 dev vrf1 proto static metric 100 166 192.168.2.3 dev vrf2 proto static metric 100 167 """.strip() 168 vrf1_table = """ 169 broadcast 192.168.1.0 dev eth1 proto kernel scope link src 192.168.1.1 170 192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1 171 local 192.168.1.1 dev eth1 proto kernel scope host src 192.168.1.1 172 broadcast 192.168.1.255 dev eth1 proto kernel scope link src 192.168.1.1 173 """.strip() 174 vrf2_table = """ 175 broadcast 192.168.2.0 dev eth2 proto kernel scope link src 192.168.2.1 176 192.168.2.0/24 dev eth2 proto kernel scope link src 192.168.2.1 177 local 192.168.2.1 dev eth2 proto kernel scope host src 192.168.2.1 178 broadcast 192.168.2.255 dev eth2 proto kernel scope link src 192.168.2.1 179 """.strip() 180 181 # Check that networkd properly configures the main routing table 182 # and the routing tables for the VRF. 183 with subtest("check vrf routing tables"): 184 compare_tables( 185 client_ipv4_table, client.succeed("ip -4 route list | head -n2").strip() 186 ) 187 compare_tables( 188 vrf1_table, client.succeed("ip -4 route list table 23 | head -n4").strip() 189 ) 190 compare_tables( 191 vrf2_table, client.succeed("ip -4 route list table 42 | head -n4").strip() 192 ) 193 194 # Ensure that other nodes are reachable via ICMP through the VRF. 195 with subtest("icmp through vrf works"): 196 client.succeed("ping -c5 192.168.1.2") 197 client.succeed("ping -c5 192.168.2.3") 198 199 # Test whether TCP through a VRF IP is possible. 200 with subtest("tcp traffic through vrf works"): 201 node1.wait_for_open_port(22) 202 client.succeed( 203 "cat ${snakeOilPrivateKey} > privkey.snakeoil" 204 ) 205 client.succeed("chmod 600 privkey.snakeoil") 206 client.succeed( 207 "ulimit -l 2048; ip vrf exec vrf1 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true" 208 ) 209 210 # Only configured routes through the VRF from the main routing table should 211 # work. Additional IPs are only reachable when binding to the vrf interface. 212 with subtest("only routes from main routing table work by default"): 213 client.fail("ping -c5 192.168.2.4") 214 client.succeed("ping -I vrf2 -c5 192.168.2.4") 215 216 client.shutdown() 217 node1.shutdown() 218 node2.shutdown() 219 node3.shutdown() 220 ''; 221})