1{ system ? builtins.currentSystem
2, config ? {}
3, pkgs ? import ../.. { inherit system config; }
4# bool: whether to use networkd in the tests
5, networkd }:
6
7with import ../lib/testing-python.nix { inherit system pkgs; };
8with pkgs.lib;
9
10let
11 qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
12
13 router = { config, pkgs, lib, ... }:
14 with pkgs.lib;
15 let
16 vlanIfs = range 1 (length config.virtualisation.vlans);
17 in {
18 environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
19 virtualisation.vlans = [ 1 2 3 ];
20 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
21 networking = {
22 useDHCP = false;
23 useNetworkd = networkd;
24 firewall.checkReversePath = true;
25 firewall.allowedUDPPorts = [ 547 ];
26 interfaces = mkOverride 0 (listToAttrs (forEach vlanIfs (n:
27 nameValuePair "eth${toString n}" {
28 ipv4.addresses = [ { address = "192.168.${toString n}.1"; prefixLength = 24; } ];
29 ipv6.addresses = [ { address = "fd00:1234:5678:${toString n}::1"; prefixLength = 64; } ];
30 })));
31 };
32 services.kea = {
33 dhcp4 = {
34 enable = true;
35 settings = {
36 interfaces-config = {
37 interfaces = map (n: "eth${toString n}") vlanIfs;
38 dhcp-socket-type = "raw";
39 service-sockets-require-all = true;
40 service-sockets-max-retries = 5;
41 service-sockets-retry-wait-time = 2500;
42 };
43 subnet4 = map (n: {
44 id = n;
45 subnet = "192.168.${toString n}.0/24";
46 pools = [{ pool = "192.168.${toString n}.3 - 192.168.${toString n}.254"; }];
47 option-data = [{ name = "routers"; data = "192.168.${toString n}.1"; }];
48
49 reservations = [{
50 hw-address = qemu-common.qemuNicMac n 1;
51 hostname = "client${toString n}";
52 ip-address = "192.168.${toString n}.2";
53 }];
54 }) vlanIfs;
55 };
56 };
57 dhcp6 = {
58 enable = true;
59 settings = {
60 interfaces-config = {
61 interfaces = map (n: "eth${toString n}") vlanIfs;
62 service-sockets-require-all = true;
63 service-sockets-max-retries = 5;
64 service-sockets-retry-wait-time = 2500;
65 };
66
67 subnet6 = map (n: {
68 id = n;
69 subnet = "fd00:1234:5678:${toString n}::/64";
70 interface = "eth${toString n}";
71 pools = [{ pool = "fd00:1234:5678:${toString n}::2-fd00:1234:5678:${toString n}::2"; }];
72 }) vlanIfs;
73 };
74 };
75 };
76 services.radvd = {
77 enable = true;
78 config = flip concatMapStrings vlanIfs (n: ''
79 interface eth${toString n} {
80 AdvSendAdvert on;
81 AdvManagedFlag on;
82 AdvOtherConfigFlag on;
83
84 prefix fd00:1234:5678:${toString n}::/64 {
85 AdvAutonomous off;
86 };
87 };
88 '');
89 };
90 };
91
92 testCases = {
93 loopback = {
94 name = "Loopback";
95 nodes.client = { pkgs, ... }: with pkgs.lib; {
96 networking.useDHCP = false;
97 networking.useNetworkd = networkd;
98 };
99 testScript = ''
100 start_all()
101 client.wait_for_unit("network.target")
102 loopback_addresses = client.succeed("ip addr show lo")
103 assert "inet 127.0.0.1/8" in loopback_addresses
104 assert "inet6 ::1/128" in loopback_addresses
105 '';
106 };
107 static = {
108 name = "Static";
109 nodes.router = router;
110 nodes.client = { pkgs, ... }: with pkgs.lib; {
111 virtualisation.interfaces.enp1s0.vlan = 1;
112 virtualisation.interfaces.enp2s0.vlan = 2;
113 networking = {
114 useNetworkd = networkd;
115 useDHCP = false;
116 defaultGateway = { address = "192.168.1.1"; interface = "enp1s0"; };
117 defaultGateway6 = { address = "fd00:1234:5678:1::1"; interface = "enp1s0"; };
118 interfaces.enp1s0.ipv4.addresses = [
119 { address = "192.168.1.2"; prefixLength = 24; }
120 { address = "192.168.1.3"; prefixLength = 32; }
121 { address = "192.168.1.10"; prefixLength = 32; }
122 ];
123 interfaces.enp2s0.ipv4.addresses = [
124 { address = "192.168.2.2"; prefixLength = 24; }
125 ];
126 };
127 };
128 testScript = { ... }:
129 ''
130 start_all()
131
132 client.wait_for_unit("network.target")
133 router.wait_for_unit("network-online.target")
134
135 with subtest("Make sure DHCP server is not started"):
136 client.fail("systemctl status kea-dhcp4-server.service")
137 client.fail("systemctl status kea-dhcp6-server.service")
138
139 with subtest("Test vlan 1"):
140 client.wait_until_succeeds("ping -c 1 192.168.1.1")
141 client.wait_until_succeeds("ping -c 1 192.168.1.2")
142 client.wait_until_succeeds("ping -c 1 192.168.1.3")
143 client.wait_until_succeeds("ping -c 1 192.168.1.10")
144
145 router.wait_until_succeeds("ping -c 1 192.168.1.1")
146 router.wait_until_succeeds("ping -c 1 192.168.1.2")
147 router.wait_until_succeeds("ping -c 1 192.168.1.3")
148 router.wait_until_succeeds("ping -c 1 192.168.1.10")
149
150 with subtest("Test vlan 2"):
151 client.wait_until_succeeds("ping -c 1 192.168.2.1")
152 client.wait_until_succeeds("ping -c 1 192.168.2.2")
153
154 router.wait_until_succeeds("ping -c 1 192.168.2.1")
155 router.wait_until_succeeds("ping -c 1 192.168.2.2")
156
157 with subtest("Test default gateway"):
158 router.wait_until_succeeds("ping -c 1 192.168.3.1")
159 client.wait_until_succeeds("ping -c 1 192.168.3.1")
160 router.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1")
161 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1")
162 '';
163 };
164 routeType = {
165 name = "RouteType";
166 nodes.client = { pkgs, ... }: with pkgs.lib; {
167 networking = {
168 useDHCP = false;
169 useNetworkd = networkd;
170 interfaces.eth1.ipv4.routes = [{
171 address = "192.168.1.127";
172 prefixLength = 32;
173 type = "local";
174 }];
175 };
176 };
177 testScript = ''
178 start_all()
179 client.wait_for_unit("network.target")
180 client.succeed("ip -4 route list table local | grep 'local 192.168.1.127'")
181 '';
182 };
183 dhcpDefault = {
184 name = "useDHCP-by-default";
185 nodes.router = router;
186 nodes.client = { lib, ... }: {
187 # Disable test driver default config
188 networking.interfaces = lib.mkForce {
189 # Make sure DHCP defaults correctly even when some unrelated config
190 # is set on the interface (nothing, in this case).
191 enp1s0 = {};
192 };
193 networking.useNetworkd = networkd;
194 virtualisation.interfaces.enp1s0.vlan = 1;
195 };
196 testScript = ''
197 start_all()
198 client.wait_for_unit("multi-user.target")
199 client.wait_until_succeeds("ip addr show dev enp1s0 | grep '192.168.1'")
200 client.shell_interact()
201 client.succeed("ping -c 1 192.168.1.1")
202 router.succeed("ping -c 1 192.168.1.1")
203 router.succeed("ping -c 1 192.168.1.2")
204 client.succeed("ping -c 1 192.168.1.2")
205 '';
206 };
207 dhcpSimple = {
208 name = "SimpleDHCP";
209 nodes.router = router;
210 nodes.client = { pkgs, ... }: with pkgs.lib; {
211 virtualisation.interfaces.enp1s0.vlan = 1;
212 virtualisation.interfaces.enp2s0.vlan = 2;
213 networking = {
214 useNetworkd = networkd;
215 useDHCP = false;
216 interfaces.enp1s0.useDHCP = true;
217 interfaces.enp2s0.useDHCP = true;
218 };
219 };
220 testScript = { ... }:
221 ''
222 start_all()
223
224 client.wait_for_unit("network.target")
225 router.wait_for_unit("network-online.target")
226
227 with subtest("Wait until we have an ip address on each interface"):
228 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
229 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
230 client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q '192.168.2'")
231 client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q 'fd00:1234:5678:2:'")
232
233 with subtest("Test vlan 1"):
234 client.wait_until_succeeds("ping -c 1 192.168.1.1")
235 client.wait_until_succeeds("ping -c 1 192.168.1.2")
236 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
237 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
238
239 router.wait_until_succeeds("ping -c 1 192.168.1.1")
240 router.wait_until_succeeds("ping -c 1 192.168.1.2")
241 router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
242 router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
243
244 with subtest("Test vlan 2"):
245 client.wait_until_succeeds("ping -c 1 192.168.2.1")
246 client.wait_until_succeeds("ping -c 1 192.168.2.2")
247 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
248 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
249
250 router.wait_until_succeeds("ping -c 1 192.168.2.1")
251 router.wait_until_succeeds("ping -c 1 192.168.2.2")
252 router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
253 router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
254 '';
255 };
256 dhcpOneIf = {
257 name = "OneInterfaceDHCP";
258 nodes.router = router;
259 nodes.client = { pkgs, ... }: with pkgs.lib; {
260 virtualisation.interfaces.enp1s0.vlan = 1;
261 virtualisation.interfaces.enp2s0.vlan = 2;
262 networking = {
263 useNetworkd = networkd;
264 useDHCP = false;
265 interfaces.enp1s0 = {
266 mtu = 1343;
267 useDHCP = true;
268 };
269 };
270 };
271 testScript = { ... }:
272 ''
273 start_all()
274
275 with subtest("Wait for networking to come up"):
276 client.wait_for_unit("network.target")
277 router.wait_for_unit("network.target")
278
279 with subtest("Wait until we have an ip address on each interface"):
280 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
281
282 with subtest("ensure MTU is set"):
283 assert "mtu 1343" in client.succeed("ip link show dev enp1s0")
284
285 with subtest("Test vlan 1"):
286 client.wait_until_succeeds("ping -c 1 192.168.1.1")
287 client.wait_until_succeeds("ping -c 1 192.168.1.2")
288
289 router.wait_until_succeeds("ping -c 1 192.168.1.1")
290 router.wait_until_succeeds("ping -c 1 192.168.1.2")
291
292 with subtest("Test vlan 2"):
293 client.wait_until_succeeds("ping -c 1 192.168.2.1")
294 client.fail("ping -c 1 192.168.2.2")
295
296 router.wait_until_succeeds("ping -c 1 192.168.2.1")
297 router.fail("ping -c 1 192.168.2.2")
298 '';
299 };
300 bond = let
301 node = address: { pkgs, ... }: with pkgs.lib; {
302 virtualisation.interfaces.enp1s0.vlan = 1;
303 virtualisation.interfaces.enp2s0.vlan = 2;
304 networking = {
305 useNetworkd = networkd;
306 useDHCP = false;
307 bonds.bond0 = {
308 interfaces = [ "enp1s0" "enp2s0" ];
309 driverOptions.mode = "802.3ad";
310 };
311 interfaces.bond0.ipv4.addresses = mkOverride 0
312 [ { inherit address; prefixLength = 30; } ];
313 };
314 };
315 in {
316 name = "Bond";
317 nodes.client1 = node "192.168.1.1";
318 nodes.client2 = node "192.168.1.2";
319 testScript = { ... }:
320 ''
321 start_all()
322
323 with subtest("Wait for networking to come up"):
324 client1.wait_for_unit("network.target")
325 client2.wait_for_unit("network.target")
326
327 with subtest("Test bonding"):
328 client1.wait_until_succeeds("ping -c 2 192.168.1.1")
329 client1.wait_until_succeeds("ping -c 2 192.168.1.2")
330
331 client2.wait_until_succeeds("ping -c 2 192.168.1.1")
332 client2.wait_until_succeeds("ping -c 2 192.168.1.2")
333
334 with subtest("Verify bonding mode"):
335 for client in client1, client2:
336 client.succeed('grep -q "Bonding Mode: IEEE 802.3ad Dynamic link aggregation" /proc/net/bonding/bond0')
337 '';
338 };
339 bridge = let
340 node = { address, vlan }: { pkgs, ... }: with pkgs.lib; {
341 virtualisation.interfaces.enp1s0.vlan = vlan;
342 networking = {
343 useNetworkd = networkd;
344 useDHCP = false;
345 interfaces.enp1s0.ipv4.addresses = [ { inherit address; prefixLength = 24; } ];
346 };
347 };
348 in {
349 name = "Bridge";
350 nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
351 nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
352 nodes.router = { pkgs, ... }: with pkgs.lib; {
353 virtualisation.interfaces.enp1s0.vlan = 1;
354 virtualisation.interfaces.enp2s0.vlan = 2;
355 networking = {
356 useNetworkd = networkd;
357 useDHCP = false;
358 bridges.bridge.interfaces = [ "enp1s0" "enp2s0" ];
359 interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
360 interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
361 interfaces.bridge.ipv4.addresses = mkOverride 0
362 [ { address = "192.168.1.1"; prefixLength = 24; } ];
363 };
364 };
365 testScript = { ... }:
366 ''
367 start_all()
368
369 with subtest("Wait for networking to come up"):
370 for machine in client1, client2, router:
371 machine.wait_for_unit("network.target")
372
373 with subtest("Test bridging"):
374 client1.wait_until_succeeds("ping -c 1 192.168.1.1")
375 client1.wait_until_succeeds("ping -c 1 192.168.1.2")
376 client1.wait_until_succeeds("ping -c 1 192.168.1.3")
377
378 client2.wait_until_succeeds("ping -c 1 192.168.1.1")
379 client2.wait_until_succeeds("ping -c 1 192.168.1.2")
380 client2.wait_until_succeeds("ping -c 1 192.168.1.3")
381
382 router.wait_until_succeeds("ping -c 1 192.168.1.1")
383 router.wait_until_succeeds("ping -c 1 192.168.1.2")
384 router.wait_until_succeeds("ping -c 1 192.168.1.3")
385 '';
386 };
387 macvlan = {
388 name = "MACVLAN";
389 nodes.router = router;
390 nodes.client = { pkgs, ... }: with pkgs.lib; {
391 environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
392 virtualisation.interfaces.enp1s0.vlan = 1;
393 networking = {
394 useNetworkd = networkd;
395 useDHCP = false;
396 firewall.logReversePathDrops = true; # to debug firewall rules
397 # reverse path filtering rules for the macvlan interface seem
398 # to be incorrect, causing the test to fail. Disable temporarily.
399 firewall.checkReversePath = false;
400 macvlans.macvlan.interface = "enp1s0";
401 interfaces.enp1s0.useDHCP = true;
402 interfaces.macvlan.useDHCP = true;
403 };
404 };
405 testScript = { ... }:
406 ''
407 start_all()
408
409 with subtest("Wait for networking to come up"):
410 client.wait_for_unit("network.target")
411 router.wait_for_unit("network.target")
412
413 with subtest("Wait until we have an ip address on each interface"):
414 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
415 client.wait_until_succeeds("ip addr show dev macvlan | grep -q '192.168.1'")
416
417 with subtest("Print lots of diagnostic information"):
418 router.log("**********************************************")
419 router.succeed("ip addr >&2")
420 router.succeed("ip route >&2")
421 router.execute("iptables-save >&2")
422 client.log("==============================================")
423 client.succeed("ip addr >&2")
424 client.succeed("ip route >&2")
425 client.execute("iptables-save >&2")
426 client.log("##############################################")
427
428 with subtest("Test macvlan creates routable ips"):
429 client.wait_until_succeeds("ping -c 1 192.168.1.1")
430 client.wait_until_succeeds("ping -c 1 192.168.1.2")
431 client.wait_until_succeeds("ping -c 1 192.168.1.3")
432
433 router.wait_until_succeeds("ping -c 1 192.168.1.1")
434 router.wait_until_succeeds("ping -c 1 192.168.1.2")
435 router.wait_until_succeeds("ping -c 1 192.168.1.3")
436 '';
437 };
438 fou = {
439 name = "foo-over-udp";
440 nodes.machine = { ... }: {
441 virtualisation.interfaces.enp1s0.vlan = 1;
442 networking = {
443 useNetworkd = networkd;
444 useDHCP = false;
445 interfaces.enp1s0.ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
446 fooOverUDP = {
447 fou1 = { port = 9001; };
448 fou2 = { port = 9002; protocol = 41; };
449 fou3 = mkIf (!networkd)
450 { port = 9003; local.address = "192.168.1.1"; };
451 fou4 = mkIf (!networkd)
452 { port = 9004; local = { address = "192.168.1.1"; dev = "enp1s0"; }; };
453 };
454 };
455 systemd.services = {
456 fou3-fou-encap.after = optional (!networkd) "network-addresses-enp1s0.service";
457 };
458 };
459 testScript = { ... }:
460 ''
461 import json
462
463 machine.wait_for_unit("network.target")
464 fous = json.loads(machine.succeed("ip -json fou show"))
465 assert {"port": 9001, "gue": None, "family": "inet"} in fous, "fou1 exists"
466 assert {"port": 9002, "ipproto": 41, "family": "inet"} in fous, "fou2 exists"
467 '' + optionalString (!networkd) ''
468 assert {
469 "port": 9003,
470 "gue": None,
471 "family": "inet",
472 "local": "192.168.1.1",
473 } in fous, "fou3 exists"
474 assert {
475 "port": 9004,
476 "gue": None,
477 "family": "inet",
478 "local": "192.168.1.1",
479 "dev": "enp1s0",
480 } in fous, "fou4 exists"
481 '';
482 };
483 sit = let
484 node = { address4, remote, address6 }: { pkgs, ... }: with pkgs.lib; {
485 virtualisation.interfaces.enp1s0.vlan = 1;
486 networking = {
487 useNetworkd = networkd;
488 useDHCP = false;
489 sits.sit = {
490 inherit remote;
491 local = address4;
492 dev = "enp1s0";
493 };
494 interfaces.enp1s0.ipv4.addresses = mkOverride 0
495 [ { address = address4; prefixLength = 24; } ];
496 interfaces.sit.ipv6.addresses = mkOverride 0
497 [ { address = address6; prefixLength = 64; } ];
498 };
499 };
500 in {
501 name = "Sit";
502 # note on firewalling: the two nodes are explicitly asymmetric.
503 # client1 sends SIT packets in UDP, but accepts only proto-41 incoming.
504 # client2 does the reverse, sending in proto-41 and accepting only UDP incoming.
505 # that way we'll notice when either SIT itself or FOU breaks.
506 nodes.client1 = args@{ pkgs, ... }:
507 mkMerge [
508 (node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; } args)
509 {
510 networking = {
511 firewall.extraCommands = "iptables -A INPUT -p 41 -j ACCEPT";
512 sits.sit.encapsulation = { type = "fou"; port = 9001; };
513 };
514 }
515 ];
516 nodes.client2 = args@{ pkgs, ... }:
517 mkMerge [
518 (node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; } args)
519 {
520 networking = {
521 firewall.allowedUDPPorts = [ 9001 ];
522 fooOverUDP.fou1 = { port = 9001; protocol = 41; };
523 };
524 }
525 ];
526 testScript = { ... }:
527 ''
528 start_all()
529
530 with subtest("Wait for networking to be configured"):
531 client1.wait_for_unit("network.target")
532 client2.wait_for_unit("network.target")
533
534 # Print diagnostic information
535 client1.succeed("ip addr >&2")
536 client2.succeed("ip addr >&2")
537
538 with subtest("Test ipv6"):
539 client1.wait_until_succeeds("ping -c 1 fc00::1")
540 client1.wait_until_succeeds("ping -c 1 fc00::2")
541
542 client2.wait_until_succeeds("ping -c 1 fc00::1")
543 client2.wait_until_succeeds("ping -c 1 fc00::2")
544 '';
545 };
546 gre = let
547 node = { pkgs, ... }: with pkgs.lib; {
548 networking = {
549 useNetworkd = networkd;
550 useDHCP = false;
551 firewall.extraCommands = "ip6tables -A nixos-fw -p gre -j nixos-fw-accept";
552 };
553 };
554 in {
555 name = "GRE";
556 nodes.client1 = args@{ pkgs, ... }:
557 mkMerge [
558 (node args)
559 {
560 virtualisation.vlans = [ 1 2 4 ];
561 networking = {
562 greTunnels = {
563 greTunnel = {
564 local = "192.168.2.1";
565 remote = "192.168.2.2";
566 dev = "eth2";
567 ttl = 225;
568 type = "tap";
569 };
570 gre6Tunnel = {
571 local = "fd00:1234:5678:4::1";
572 remote = "fd00:1234:5678:4::2";
573 dev = "eth3";
574 ttl = 255;
575 type = "tun6";
576 };
577 };
578 bridges.bridge.interfaces = [ "greTunnel" "eth1" ];
579 interfaces.eth1.ipv4.addresses = mkOverride 0 [];
580 interfaces.bridge.ipv4.addresses = mkOverride 0 [
581 { address = "192.168.1.1"; prefixLength = 24; }
582 ];
583 interfaces.eth3.ipv6.addresses = [
584 { address = "fd00:1234:5678:4::1"; prefixLength = 64; }
585 ];
586 interfaces.gre6Tunnel.ipv6.addresses = mkOverride 0 [
587 { address = "fc00::1"; prefixLength = 64; }
588 ];
589 };
590 }
591 ];
592 nodes.client2 = args@{ pkgs, ... }:
593 mkMerge [
594 (node args)
595 {
596 virtualisation.vlans = [ 2 3 4 ];
597 networking = {
598 greTunnels = {
599 greTunnel = {
600 local = "192.168.2.2";
601 remote = "192.168.2.1";
602 dev = "eth1";
603 ttl = 225;
604 type = "tap";
605 };
606 gre6Tunnel = {
607 local = "fd00:1234:5678:4::2";
608 remote = "fd00:1234:5678:4::1";
609 dev = "eth3";
610 ttl = 255;
611 type = "tun6";
612 };
613 };
614 bridges.bridge.interfaces = [ "greTunnel" "eth2" ];
615 interfaces.eth2.ipv4.addresses = mkOverride 0 [];
616 interfaces.bridge.ipv4.addresses = mkOverride 0 [
617 { address = "192.168.1.2"; prefixLength = 24; }
618 ];
619 interfaces.eth3.ipv6.addresses = [
620 { address = "fd00:1234:5678:4::2"; prefixLength = 64; }
621 ];
622 interfaces.gre6Tunnel.ipv6.addresses = mkOverride 0 [
623 { address = "fc00::2"; prefixLength = 64; }
624 ];
625 };
626 }
627 ];
628 testScript = { ... }:
629 ''
630 import json
631 start_all()
632
633 with subtest("Wait for networking to be configured"):
634 client1.wait_for_unit("network.target")
635 client2.wait_for_unit("network.target")
636
637 # Print diagnostic information
638 client1.succeed("ip addr >&2")
639 client2.succeed("ip addr >&2")
640
641 with subtest("Test GRE tunnel bridge over VLAN"):
642 client1.wait_until_succeeds("ping -c 1 192.168.1.2")
643
644 client2.wait_until_succeeds("ping -c 1 192.168.1.1")
645
646 client1.wait_until_succeeds("ping -c 1 fc00::2")
647
648 client2.wait_until_succeeds("ping -c 1 fc00::1")
649
650 with subtest("Test GRE tunnel TTL"):
651 links = json.loads(client1.succeed("ip -details -json link show greTunnel"))
652 assert links[0]['linkinfo']['info_data']['ttl'] == 225, "ttl not set for greTunnel"
653
654 links = json.loads(client2.succeed("ip -details -json link show gre6Tunnel"))
655 assert links[0]['linkinfo']['info_data']['ttl'] == 255, "ttl not set for gre6Tunnel"
656 '';
657 };
658 vlan = let
659 node = address: { pkgs, ... }: with pkgs.lib; {
660 #virtualisation.vlans = [ 1 ];
661 networking = {
662 useNetworkd = networkd;
663 useDHCP = false;
664 vlans.vlan = {
665 id = 1;
666 interface = "eth0";
667 };
668 interfaces.eth0.ipv4.addresses = mkOverride 0 [ ];
669 interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
670 interfaces.vlan.ipv4.addresses = mkOverride 0
671 [ { inherit address; prefixLength = 24; } ];
672 };
673 };
674 in {
675 name = "vlan";
676 nodes.client1 = node "192.168.1.1";
677 nodes.client2 = node "192.168.1.2";
678 testScript = { ... }:
679 ''
680 start_all()
681
682 with subtest("Wait for networking to be configured"):
683 client1.wait_for_unit("network.target")
684 client2.wait_for_unit("network.target")
685
686 with subtest("Test vlan is setup"):
687 client1.succeed("ip addr show dev vlan >&2")
688 client2.succeed("ip addr show dev vlan >&2")
689 '';
690 };
691 vlan-ping = let
692 baseIP = number: "10.10.10.${number}";
693 vlanIP = number: "10.1.1.${number}";
694 baseInterface = "enp1s0";
695 vlanInterface = "vlan42";
696 node = number: {pkgs, ... }: with pkgs.lib; {
697 virtualisation.interfaces.enp1s0.vlan = 1;
698 networking = {
699 #useNetworkd = networkd;
700 useDHCP = false;
701 vlans.${vlanInterface} = { id = 42; interface = baseInterface; };
702 interfaces.${baseInterface}.ipv4.addresses = mkOverride 0 [{ address = baseIP number; prefixLength = 24; }];
703 interfaces.${vlanInterface}.ipv4.addresses = mkOverride 0 [{ address = vlanIP number; prefixLength = 24; }];
704 };
705 };
706
707 serverNodeNum = "1";
708 clientNodeNum = "2";
709
710 in {
711 name = "vlan-ping";
712 nodes.server = node serverNodeNum;
713 nodes.client = node clientNodeNum;
714 testScript = { ... }:
715 ''
716 start_all()
717
718 with subtest("Wait for networking to be configured"):
719 server.wait_for_unit("network.target")
720 client.wait_for_unit("network.target")
721
722 with subtest("Test ping on base interface in setup"):
723 client.succeed("ping -I ${baseInterface} -c 1 ${baseIP serverNodeNum}")
724 server.succeed("ping -I ${baseInterface} -c 1 ${baseIP clientNodeNum}")
725
726 with subtest("Test ping on vlan subinterface in setup"):
727 client.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP serverNodeNum}")
728 server.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP clientNodeNum}")
729 '';
730 };
731 virtual = {
732 name = "Virtual";
733 nodes.machine = {
734 networking.useNetworkd = networkd;
735 networking.useDHCP = false;
736 networking.interfaces.tap0 = {
737 ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
738 ipv6.addresses = [ { address = "2001:1470:fffd:2096::"; prefixLength = 64; } ];
739 virtual = true;
740 mtu = 1342;
741 macAddress = "02:de:ad:be:ef:01";
742 };
743 networking.interfaces.tun0 = {
744 ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
745 ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
746 virtual = true;
747 mtu = 1343;
748 };
749 };
750
751 testScript = ''
752 targetList = """
753 tap0: tap persist user 0
754 tun0: tun persist user 0
755 """.strip()
756
757 with subtest("Wait for networking to come up"):
758 machine.start()
759 machine.wait_for_unit("network.target")
760
761 with subtest("Test interfaces set up"):
762 list = machine.succeed("ip tuntap list | sort").strip()
763 assert (
764 list == targetList
765 ), """
766 The list of virtual interfaces does not match the expected one:
767 Result:
768 {}
769 Expected:
770 {}
771 """.format(
772 list, targetList
773 )
774 with subtest("Test MTU and MAC Address are configured"):
775 machine.wait_until_succeeds("ip link show dev tap0 | grep 'mtu 1342'")
776 machine.wait_until_succeeds("ip link show dev tun0 | grep 'mtu 1343'")
777 assert "02:de:ad:be:ef:01" in machine.succeed("ip link show dev tap0")
778 '' # network-addresses-* only exist in scripted networking
779 + optionalString (!networkd) ''
780 with subtest("Test interfaces clean up"):
781 machine.succeed("systemctl stop network-addresses-tap0")
782 machine.sleep(10)
783 machine.succeed("systemctl stop network-addresses-tun0")
784 machine.sleep(10)
785 residue = machine.succeed("ip tuntap list")
786 assert (
787 residue == ""
788 ), "Some virtual interface has not been properly cleaned:\n{}".format(residue)
789 '';
790 };
791 privacy = {
792 name = "Privacy";
793 nodes.router = { ... }: {
794 virtualisation.interfaces.enp1s0.vlan = 1;
795 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
796 networking = {
797 useNetworkd = networkd;
798 useDHCP = false;
799 interfaces.enp1s0.ipv6.addresses = singleton {
800 address = "fd00:1234:5678:1::1";
801 prefixLength = 64;
802 };
803 };
804 services.radvd = {
805 enable = true;
806 config = ''
807 interface enp1s0 {
808 AdvSendAdvert on;
809 AdvManagedFlag on;
810 AdvOtherConfigFlag on;
811
812 prefix fd00:1234:5678:1::/64 {
813 AdvAutonomous on;
814 AdvOnLink on;
815 };
816 };
817 '';
818 };
819 };
820 nodes.client_with_privacy = { pkgs, ... }: with pkgs.lib; {
821 virtualisation.interfaces.enp1s0.vlan = 1;
822 networking = {
823 useNetworkd = networkd;
824 useDHCP = false;
825 interfaces.enp1s0 = {
826 tempAddress = "default";
827 ipv4.addresses = mkOverride 0 [ ];
828 ipv6.addresses = mkOverride 0 [ ];
829 useDHCP = true;
830 };
831 };
832 };
833 nodes.client = { pkgs, ... }: with pkgs.lib; {
834 virtualisation.interfaces.enp1s0.vlan = 1;
835 networking = {
836 useNetworkd = networkd;
837 useDHCP = false;
838 interfaces.enp1s0 = {
839 tempAddress = "enabled";
840 ipv4.addresses = mkOverride 0 [ ];
841 ipv6.addresses = mkOverride 0 [ ];
842 useDHCP = true;
843 };
844 };
845 };
846 testScript = { ... }:
847 ''
848 start_all()
849
850 client.wait_for_unit("network.target")
851 client_with_privacy.wait_for_unit("network.target")
852 router.wait_for_unit("network-online.target")
853
854 with subtest("Wait until we have an ip address"):
855 client_with_privacy.wait_until_succeeds(
856 "ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'"
857 )
858 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
859
860 with subtest("Test vlan 1"):
861 client_with_privacy.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
862 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
863
864 with subtest("Test address used is temporary"):
865 client_with_privacy.wait_until_succeeds(
866 "! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
867 )
868
869 with subtest("Test address used is EUI-64"):
870 client.wait_until_succeeds(
871 "ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
872 )
873 '';
874 };
875 routes = {
876 name = "routes";
877 nodes.machine = {
878 networking.useNetworkd = networkd;
879 networking.useDHCP = false;
880 networking.interfaces.eth0 = {
881 ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
882 ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
883 ipv6.routes = [
884 { address = "fdfd:b3f0::"; prefixLength = 48; }
885 { address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; }
886 ];
887 ipv4.routes = [
888 { address = "10.0.0.0"; prefixLength = 16; options = {
889 mtu = "1500";
890 # Explicitly set scope because iproute and systemd-networkd
891 # disagree on what the scope should be
892 # if the type is the default "unicast"
893 scope = "link";
894 }; }
895 { address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; }
896 ];
897 };
898 virtualisation.vlans = [ ];
899 };
900
901 testScript = ''
902 targetIPv4Table = [
903 "10.0.0.0/16 proto static scope link mtu 1500",
904 "192.168.1.0/24 proto kernel scope link src 192.168.1.2",
905 "192.168.2.0/24 via 192.168.1.1 proto static",
906 ]
907
908 targetIPv6Table = [
909 "2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium",
910 "2001:1470:fffd:2098::/64 via fdfd:b3f0::1 proto static metric 1024 pref medium",
911 "fdfd:b3f0::/48 proto static metric 1024 pref medium",
912 ]
913
914 machine.start()
915 machine.wait_for_unit("network.target")
916
917 with subtest("test routing tables"):
918 ipv4Table = machine.succeed("ip -4 route list dev eth0 | head -n3").strip()
919 ipv6Table = machine.succeed("ip -6 route list dev eth0 | head -n3").strip()
920 assert [
921 l.strip() for l in ipv4Table.splitlines()
922 ] == targetIPv4Table, """
923 The IPv4 routing table does not match the expected one:
924 Result:
925 {}
926 Expected:
927 {}
928 """.format(
929 ipv4Table, targetIPv4Table
930 )
931 assert [
932 l.strip() for l in ipv6Table.splitlines()
933 ] == targetIPv6Table, """
934 The IPv6 routing table does not match the expected one:
935 Result:
936 {}
937 Expected:
938 {}
939 """.format(
940 ipv6Table, targetIPv6Table
941 )
942
943 '' + optionalString (!networkd) ''
944 with subtest("test clean-up of the tables"):
945 machine.succeed("systemctl stop network-addresses-eth0")
946 ipv4Residue = machine.succeed("ip -4 route list dev eth0 | head -n-3").strip()
947 ipv6Residue = machine.succeed("ip -6 route list dev eth0 | head -n-3").strip()
948 assert (
949 ipv4Residue == ""
950 ), "The IPv4 routing table has not been properly cleaned:\n{}".format(ipv4Residue)
951 assert (
952 ipv6Residue == ""
953 ), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue)
954 '';
955 };
956 rename = if networkd then {
957 name = "RenameInterface";
958 nodes.machine = { pkgs, ... }: {
959 virtualisation.vlans = [ 1 ];
960 networking = {
961 useNetworkd = networkd;
962 useDHCP = false;
963 };
964 systemd.network.links."10-custom_name" = {
965 matchConfig.MACAddress = "52:54:00:12:01:01";
966 linkConfig.Name = "custom_name";
967 };
968 };
969 testScript = ''
970 machine.succeed("udevadm settle")
971 print(machine.succeed("ip link show dev custom_name"))
972 '';
973 } else {
974 name = "RenameInterface";
975 nodes = { };
976 testScript = "";
977 };
978 # even with disabled networkd, systemd.network.links should work
979 # (as it's handled by udev, not networkd)
980 link = {
981 name = "Link";
982 nodes.client = { pkgs, ... }: {
983 virtualisation.vlans = [ 1 ];
984 networking = {
985 useNetworkd = networkd;
986 useDHCP = false;
987 };
988 systemd.network.links."50-foo" = {
989 matchConfig = {
990 Name = "foo";
991 Driver = "dummy";
992 };
993 linkConfig.MTUBytes = "1442";
994 };
995 };
996 testScript = ''
997 print(client.succeed("ip l add name foo type dummy"))
998 print(client.succeed("stat /etc/systemd/network/50-foo.link"))
999 client.succeed("udevadm settle")
1000 assert "mtu 1442" in client.succeed("ip l show dev foo")
1001 '';
1002 };
1003 wlanInterface = let
1004 testMac = "06:00:00:00:02:00";
1005 in {
1006 name = "WlanInterface";
1007 nodes.machine = { pkgs, ... }: {
1008 boot.kernelModules = [ "mac80211_hwsim" ];
1009 networking.wlanInterfaces = {
1010 wlan0 = { device = "wlan0"; };
1011 wap0 = { device = "wlan0"; mac = testMac; };
1012 };
1013 };
1014 testScript = ''
1015 machine.start()
1016 machine.wait_for_unit("network.target")
1017 machine.wait_until_succeeds("ip address show wap0 | grep -q ${testMac}")
1018 machine.fail("ip address show wlan0 | grep -q ${testMac}")
1019 '';
1020 };
1021 naughtyInterfaceNames = let
1022 ifnames = [
1023 # flags of ip-address
1024 "home" "temporary" "optimistic"
1025 "bridge_slave" "flush"
1026 # flags of ip-route
1027 "up" "type" "nomaster" "address"
1028 # other
1029 "very_loong_name" "lowerUpper" "-"
1030 ];
1031 in {
1032 name = "naughtyInterfaceNames";
1033 nodes.machine = { pkgs, ... }: {
1034 networking.useNetworkd = networkd;
1035 networking.bridges = listToAttrs
1036 (flip map ifnames
1037 (name: { inherit name; value.interfaces = []; }));
1038 };
1039 testScript = ''
1040 machine.start()
1041 machine.wait_for_unit("network.target")
1042 for ifname in ${builtins.toJSON ifnames}:
1043 machine.wait_until_succeeds(f"ip link show dev '{ifname}' | grep -q '{ifname}'")
1044 '';
1045 };
1046 caseSensitiveRenaming = {
1047 name = "CaseSensitiveRenaming";
1048 nodes.machine = { pkgs, ... }: {
1049 virtualisation.interfaces.enCustom.vlan = 11;
1050 networking = {
1051 useNetworkd = networkd;
1052 useDHCP = false;
1053 };
1054 };
1055 testScript = ''
1056 machine.succeed("udevadm settle")
1057 print(machine.succeed("ip link show dev enCustom"))
1058 machine.wait_until_succeeds("ip link show dev enCustom | grep -q 52:54:00:12:0b:01")
1059 '';
1060 };
1061 };
1062
1063in mapAttrs (const (attrs: makeTest (attrs // {
1064 name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
1065}))) testCases