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