1 import ./make-test-python.nix ({ pkgs, lib, ... }: let
2 inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
5 virtualisation.vlans = [ vlan ];
14 networks."10-eth${toString vlan}" = {
15 matchConfig.Name = "eth${toString vlan}";
16 linkConfig.RequiredForOnline = "no";
18 Address = "192.168.${toString vlan}.${toString id}/24";
19 IPv4Forwarding = "yes";
20 IPv6Forwarding = "yes";
26 name = "systemd-networkd-vrf";
27 meta.maintainers = with lib.maintainers; [ ma27 ];
30 client = { pkgs, ... }: {
31 virtualisation.vlans = [ 1 2 ];
36 firewall.checkReversePath = "loose";
59 networks."10-vrf1" = {
60 matchConfig.Name = "vrf1";
61 networkConfig.IPv4Forwarding = "yes";
62 networkConfig.IPv6Forwarding = "yes";
64 { Destination = "192.168.1.2"; Metric = 100; }
67 networks."10-vrf2" = {
68 matchConfig.Name = "vrf2";
69 networkConfig.IPv4Forwarding = "yes";
70 networkConfig.IPv6Forwarding = "yes";
72 { Destination = "192.168.2.3"; Metric = 100; }
76 networks."10-eth1" = {
77 matchConfig.Name = "eth1";
78 linkConfig.RequiredForOnline = "no";
81 Address = "192.168.1.1/24";
82 IPv4Forwarding = "yes";
83 IPv6Forwarding = "yes";
86 networks."10-eth2" = {
87 matchConfig.Name = "eth2";
88 linkConfig.RequiredForOnline = "no";
91 Address = "192.168.2.1/24";
92 IPv4Forwarding = "yes";
93 IPv6Forwarding = "yes";
102 services.openssh.enable = true;
103 users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
114 def compare(raw_json, to_compare):
115 data = json.loads(raw_json)
116 assert len(raw_json) >= len(to_compare)
117 for i, row in enumerate(to_compare):
119 assert len(row.keys()) > 0
120 for key, value in row.items():
121 assert value == actual[key], f"""
122 In entry {i}, value {key}: got: {actual[key]}, expected {value}
128 client.wait_for_unit("network.target")
129 node1.wait_for_unit("network.target")
130 node2.wait_for_unit("network.target")
131 node3.wait_for_unit("network.target")
133 # Check that networkd properly configures the main routing table
134 # and the routing tables for the VRF.
135 with subtest("check vrf routing tables"):
137 client.succeed("ip --json -4 route list"),
139 {"dst": "192.168.1.2", "dev": "vrf1", "metric": 100},
140 {"dst": "192.168.2.3", "dev": "vrf2", "metric": 100}
144 client.succeed("ip --json -4 route list table 23"),
146 {"dst": "192.168.1.0/24", "dev": "eth1", "prefsrc": "192.168.1.1"},
147 {"type": "local", "dst": "192.168.1.1", "dev": "eth1", "prefsrc": "192.168.1.1"},
148 {"type": "broadcast", "dev": "eth1", "prefsrc": "192.168.1.1", "dst": "192.168.1.255"}
152 client.succeed("ip --json -4 route list table 42"),
154 {"dst": "192.168.2.0/24", "dev": "eth2", "prefsrc": "192.168.2.1"},
155 {"type": "local", "dst": "192.168.2.1", "dev": "eth2", "prefsrc": "192.168.2.1"},
156 {"type": "broadcast", "dev": "eth2", "prefsrc": "192.168.2.1", "dst": "192.168.2.255"}
160 # Ensure that other nodes are reachable via ICMP through the VRF.
161 with subtest("icmp through vrf works"):
162 client.succeed("ping -c5 192.168.1.2")
163 client.succeed("ping -c5 192.168.2.3")
165 # Test whether TCP through a VRF IP is possible.
166 with subtest("tcp traffic through vrf works"):
167 node1.wait_for_open_port(22)
169 "cat ${snakeOilPrivateKey} > privkey.snakeoil"
171 client.succeed("chmod 600 privkey.snakeoil")
173 "ulimit -l 2048; ip vrf exec vrf1 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true"
176 # Only configured routes through the VRF from the main routing table should
177 # work. Additional IPs are only reachable when binding to the vrf interface.
178 with subtest("only routes from main routing table work by default"):
179 client.fail("ping -c5 192.168.2.4")
180 client.succeed("ping -I vrf2 -c5 192.168.2.4")