nixVersions.stable: 2.15 -> 2.17
[NixPkgs.git] / nixos / tests / systemd-networkd-vrf.nix
blobd4227526a30d483e7c73970d2dcbae9905d7502f
1 import ./make-test-python.nix ({ pkgs, lib, ... }: let
2   inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
4   mkNode = vlan: id: {
5     virtualisation.vlans = [ vlan ];
6     networking = {
7       useDHCP = false;
8       useNetworkd = true;
9     };
11     systemd.network = {
12       enable = true;
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   };
24 in {
25   name = "systemd-networkd-vrf";
26   meta.maintainers = with lib.maintainers; [ ma27 ];
28   nodes = {
29     client = { pkgs, ... }: {
30       virtualisation.vlans = [ 1 2 ];
32       networking = {
33         useDHCP = false;
34         useNetworkd = true;
35         firewall.checkReversePath = "loose";
36       };
38       systemd.network = {
39         enable = true;
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         };
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         };
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     };
94     node1 = lib.mkMerge [
95       (mkNode 1 2)
96       {
97         services.openssh.enable = true;
98         users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
99       }
100     ];
102     node2 = mkNode 2 3;
103     node3 = mkNode 2 4;
104   };
106   testScript = ''
107     import json
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                 """
121     start_all()
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")
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         )
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")
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         )
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")
177     client.shutdown()
178     node1.shutdown()
179     node2.shutdown()
180     node3.shutdown()
181   '';