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