1{
2 system ? builtins.currentSystem,
3 config ? { },
4 pkgs ? import ../.. { inherit system config; },
5 # bool: whether to use networkd in the tests
6 networkd,
7}:
8
9with import ../../lib/testing-python.nix { inherit system pkgs; };
10
11let
12 lib = pkgs.lib;
13 router = import ./router.nix { inherit networkd; };
14 clientConfig =
15 extraConfig:
16 lib.recursiveUpdate {
17 networking.useDHCP = false;
18 networking.useNetworkd = networkd;
19 } extraConfig;
20 testCases = {
21 loopback = {
22 name = "Loopback";
23 nodes.client = clientConfig { };
24 testScript = ''
25 start_all()
26 client.wait_for_unit("network.target")
27 loopback_addresses = client.succeed("ip addr show lo")
28 assert "inet 127.0.0.1/8" in loopback_addresses
29 assert "inet6 ::1/128" in loopback_addresses
30 '';
31 };
32 static = {
33 name = "Static";
34 nodes.router = router;
35 nodes.client = clientConfig {
36 virtualisation.interfaces.enp1s0.vlan = 1;
37 virtualisation.interfaces.enp2s0.vlan = 2;
38 networking = {
39 defaultGateway = {
40 address = "192.168.1.1";
41 interface = "enp1s0";
42 source = "192.168.1.3";
43 };
44 defaultGateway6 = {
45 address = "fd00:1234:5678:1::1";
46 interface = "enp1s0";
47 source = "fd00:1234:5678:1::3";
48 };
49 interfaces.enp1s0.ipv6.addresses = [
50 {
51 address = "fd00:1234:5678:1::2";
52 prefixLength = 64;
53 }
54 {
55 address = "fd00:1234:5678:1::3";
56 prefixLength = 128;
57 }
58 ];
59 interfaces.enp1s0.ipv4.addresses = [
60 {
61 address = "192.168.1.2";
62 prefixLength = 24;
63 }
64 {
65 address = "192.168.1.3";
66 prefixLength = 32;
67 }
68 {
69 address = "192.168.1.10";
70 prefixLength = 32;
71 }
72 ];
73 interfaces.enp2s0.ipv4.addresses = [
74 {
75 address = "192.168.2.2";
76 prefixLength = 24;
77 }
78 ];
79 };
80 };
81 testScript = ''
82 start_all()
83
84 client.wait_for_unit("network.target")
85 router.systemctl("start network-online.target")
86 router.wait_for_unit("network-online.target")
87
88 with subtest("Make sure DHCP server is not started"):
89 client.fail("systemctl status kea-dhcp4-server.service")
90 client.fail("systemctl status kea-dhcp6-server.service")
91
92 with subtest("Test vlan 1"):
93 client.wait_until_succeeds("ping -c 1 192.168.1.1")
94 router.wait_until_succeeds("ping -c 1 192.168.1.2")
95 router.wait_until_succeeds("ping -c 1 192.168.1.3")
96 router.wait_until_succeeds("ping -c 1 192.168.1.10")
97
98 with subtest("Test vlan 2"):
99 client.wait_until_succeeds("ping -c 1 192.168.2.1")
100 router.wait_until_succeeds("ping -c 1 192.168.2.2")
101
102 with subtest("Test default gateway"):
103 client.wait_until_succeeds("ping -c 1 192.168.3.1")
104 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
105
106 with subtest("Test default addresses"):
107 client.succeed("ip -4 route show default | grep -q 'src 192.168.1.3'")
108 client.succeed("ip -6 route show default | grep -q 'src fd00:1234:5678:1::3'")
109 '';
110 };
111 routeType = {
112 name = "RouteType";
113 nodes.client = clientConfig {
114 networking = {
115 interfaces.eth1.ipv4.routes = [
116 {
117 address = "192.168.1.127";
118 prefixLength = 32;
119 type = "local";
120 }
121 ];
122 };
123 };
124 testScript = ''
125 start_all()
126 client.wait_for_unit("network.target")
127 client.succeed("ip -4 route list table local | grep 'local 192.168.1.127'")
128 '';
129 };
130 dhcpDefault = {
131 name = "useDHCP-by-default";
132 nodes.router = router;
133 nodes.client = {
134 # Disable test driver default config
135 networking.interfaces = lib.mkForce {
136 # Make sure DHCP defaults correctly even when some unrelated config
137 # is set on the interface (nothing, in this case).
138 enp1s0 = { };
139 };
140 networking.useNetworkd = networkd;
141 virtualisation.interfaces.enp1s0.vlan = 1;
142 };
143 testScript = ''
144 start_all()
145 client.wait_for_unit("multi-user.target")
146 client.wait_until_succeeds("ip addr show dev enp1s0 | grep '192.168.1'")
147 router.succeed("ping -c 1 192.168.1.1")
148 client.succeed("ping -c 1 192.168.1.2")
149 '';
150 };
151 dhcpSimple = {
152 name = "SimpleDHCP";
153 nodes.router = router;
154 nodes.client = clientConfig {
155 virtualisation.interfaces.enp1s0.vlan = 1;
156 virtualisation.interfaces.enp2s0.vlan = 2;
157 networking = {
158 interfaces.enp1s0.useDHCP = true;
159 interfaces.enp2s0.useDHCP = true;
160 };
161 };
162 testScript = ''
163 router.start()
164 router.systemctl("start network-online.target")
165 router.wait_for_unit("network-online.target")
166
167 client.start()
168 client.wait_for_unit("network.target")
169
170 with subtest("Wait until we have an ip address on each interface"):
171 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
172 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
173 client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q '192.168.2'")
174 client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q 'fd00:1234:5678:2:'")
175
176 with subtest("Wait until we have received the nameservers"):
177 if "${builtins.toJSON networkd}" == "true":
178 client.wait_until_succeeds("resolvectl status enp2s0 | grep -q 2001:db8::1")
179 client.wait_until_succeeds("resolvectl status enp2s0 | grep -q 192.168.2.1")
180 else:
181 client.wait_until_succeeds("resolvconf -l | grep -q 2001:db8::1")
182 client.wait_until_succeeds("resolvconf -l | grep -q 192.168.2.1")
183
184 with subtest("Test vlan 1"):
185 client.wait_until_succeeds("ping -c 1 192.168.1.1")
186 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
187 router.wait_until_succeeds("ping -c 1 192.168.1.2")
188 router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
189
190 with subtest("Test vlan 2"):
191 client.wait_until_succeeds("ping -c 1 192.168.2.1")
192 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
193 router.wait_until_succeeds("ping -c 1 192.168.2.2")
194 router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
195 '';
196 };
197 dhcpHostname = {
198 name = "hostnameDHCP";
199 nodes.router = router;
200 nodes.client = clientConfig {
201 # use the name given by the DHCP server
202 system.name = "client";
203 networking.hostName = lib.mkForce "";
204 security.polkit.enable = true;
205 virtualisation.interfaces.enp1s0.vlan = 1;
206 networking.interfaces.enp1s0.useDHCP = true;
207 };
208 testScript = ''
209 router.start()
210 router.systemctl("start network-online.target")
211 router.wait_for_unit("network-online.target")
212
213 client.start()
214 client.wait_for_unit("network.target")
215
216 with subtest("Wait until we have received the hostname"):
217 client.wait_until_succeeds("hostname | grep -q 'client1'")
218 '';
219 };
220 dhcpOneIf = {
221 name = "OneInterfaceDHCP";
222 nodes.router = router;
223 nodes.client = clientConfig {
224 virtualisation.interfaces.enp1s0.vlan = 1;
225 virtualisation.interfaces.enp2s0.vlan = 2;
226 networking = {
227 interfaces.enp1s0 = {
228 mtu = 1343;
229 useDHCP = true;
230 };
231 };
232 };
233 testScript = ''
234 start_all()
235
236 with subtest("Wait for networking to come up"):
237 client.wait_for_unit("network.target")
238 router.wait_for_unit("network.target")
239
240 with subtest("Wait until we have an ip address on each interface"):
241 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
242
243 with subtest("ensure MTU is set"):
244 assert "mtu 1343" in client.succeed("ip link show dev enp1s0")
245
246 with subtest("Test vlan 1"):
247 client.wait_until_succeeds("ping -c 1 192.168.1.1")
248 router.wait_until_succeeds("ping -c 1 192.168.1.2")
249
250 with subtest("Test vlan 2"):
251 client.wait_until_succeeds("ping -c 1 192.168.2.1")
252 client.fail("ping -c 1 192.168.2.2")
253
254 router.wait_until_succeeds("ping -c 1 192.168.2.1")
255 router.fail("ping -c 1 192.168.2.2")
256 '';
257 };
258 bond =
259 let
260 node =
261 address:
262 clientConfig {
263 virtualisation.interfaces.enp1s0.vlan = 1;
264 virtualisation.interfaces.enp2s0.vlan = 2;
265 networking = {
266 bonds.bond0 = {
267 interfaces = [
268 "enp1s0"
269 "enp2s0"
270 ];
271 driverOptions.mode = "802.3ad";
272 };
273 interfaces.bond0.ipv4.addresses = lib.mkOverride 0 [
274 {
275 inherit address;
276 prefixLength = 30;
277 }
278 ];
279 };
280
281 # virtio-net reports its speed and duplex as "unknown" by default,
282 # which confuses the 802.3ad logic. However, you can just tell it
283 # to pretend to have any link speed with ethtool, so do that.
284 systemd.services.fake-link-settings = {
285 path = [ pkgs.ethtool ];
286 script = ''
287 ethtool -s enp1s0 speed 1000 duplex full
288 ethtool -s enp2s0 speed 1000 duplex full
289 '';
290 wantedBy = [ "network-pre.target" ];
291 };
292 };
293 in
294 {
295 name = "Bond";
296 nodes.client1 = node "192.168.1.1";
297 nodes.client2 = node "192.168.1.2";
298 testScript = ''
299 start_all()
300
301 with subtest("Wait for networking to come up"):
302 client1.wait_for_unit("network.target")
303 client2.wait_for_unit("network.target")
304
305 with subtest("Test bonding"):
306 client1.wait_until_succeeds("ping -c2 -w2 192.168.1.1")
307 client1.wait_until_succeeds("ping -c2 -w2 192.168.1.2")
308
309 client2.wait_until_succeeds("ping -c2 -w2 192.168.1.1")
310 client2.wait_until_succeeds("ping -c2 -w2 192.168.1.2")
311
312 with subtest("Verify bonding mode"):
313 for client in client1, client2:
314 client.succeed('grep -q "Bonding Mode: IEEE 802.3ad Dynamic link aggregation" /proc/net/bonding/bond0')
315 '';
316 };
317 bridge =
318 let
319 node =
320 { address, vlan }:
321 { pkgs, ... }:
322 {
323 virtualisation.interfaces.enp1s0.vlan = vlan;
324 networking = {
325 useNetworkd = networkd;
326 useDHCP = false;
327 interfaces.enp1s0.ipv4.addresses = [
328 {
329 inherit address;
330 prefixLength = 24;
331 }
332 ];
333 };
334 };
335 in
336 {
337 name = "Bridge";
338 nodes.client1 = node {
339 address = "192.168.1.2";
340 vlan = 1;
341 };
342 nodes.client2 = node {
343 address = "192.168.1.3";
344 vlan = 2;
345 };
346 nodes.router = {
347 virtualisation.interfaces.enp1s0.vlan = 1;
348 virtualisation.interfaces.enp2s0.vlan = 2;
349 networking = {
350 useNetworkd = networkd;
351 useDHCP = false;
352 bridges.bridge.interfaces = [
353 "enp1s0"
354 "enp2s0"
355 ];
356 interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [ ];
357 interfaces.eth2.ipv4.addresses = lib.mkOverride 0 [ ];
358 interfaces.bridge.ipv4.addresses = lib.mkOverride 0 [
359 {
360 address = "192.168.1.1";
361 prefixLength = 24;
362 }
363 ];
364 };
365 };
366 testScript = ''
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 =
391 { pkgs, ... }:
392 {
393 environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
394 virtualisation.interfaces.enp1s0.vlan = 1;
395 networking = {
396 useNetworkd = networkd;
397 useDHCP = false;
398 firewall.logReversePathDrops = true; # to debug firewall rules
399 # reverse path filtering rules for the macvlan interface seem
400 # to be incorrect, causing the test to fail. Disable temporarily.
401 firewall.checkReversePath = false;
402 macvlans.macvlan.interface = "enp1s0";
403 interfaces.enp1s0.useDHCP = true;
404 interfaces.macvlan.useDHCP = true;
405 };
406 };
407 testScript = ''
408 start_all()
409
410 with subtest("Wait for networking to come up"):
411 client.wait_for_unit("network.target")
412 router.wait_for_unit("network.target")
413
414 with subtest("Wait until we have an ip address on each interface"):
415 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
416 client.wait_until_succeeds("ip addr show dev macvlan | grep -q '192.168.1'")
417
418 with subtest("Print lots of diagnostic information"):
419 router.log("**********************************************")
420 router.succeed("ip addr >&2")
421 router.succeed("ip route >&2")
422 router.execute("iptables-save >&2")
423 client.log("==============================================")
424 client.succeed("ip addr >&2")
425 client.succeed("ip route >&2")
426 client.execute("iptables-save >&2")
427 client.log("##############################################")
428
429 with subtest("Test macvlan creates routable ips"):
430 client.wait_until_succeeds("ping -c 1 192.168.1.1")
431 client.wait_until_succeeds("ping -c 1 192.168.1.2")
432 client.wait_until_succeeds("ping -c 1 192.168.1.3")
433
434 router.wait_until_succeeds("ping -c 1 192.168.1.1")
435 router.wait_until_succeeds("ping -c 1 192.168.1.2")
436 router.wait_until_succeeds("ping -c 1 192.168.1.3")
437 '';
438 };
439 fou = {
440 name = "foo-over-udp";
441 nodes.machine = clientConfig {
442 virtualisation.interfaces.enp1s0.vlan = 1;
443 networking = {
444 interfaces.enp1s0.ipv4.addresses = [
445 {
446 address = "192.168.1.1";
447 prefixLength = 24;
448 }
449 ];
450 fooOverUDP = {
451 fou1 = {
452 port = 9001;
453 };
454 fou2 = {
455 port = 9002;
456 protocol = 41;
457 };
458 fou3 = lib.mkIf (!networkd) {
459 port = 9003;
460 local.address = "192.168.1.1";
461 };
462 fou4 = lib.mkIf (!networkd) {
463 port = 9004;
464 local = {
465 address = "192.168.1.1";
466 dev = "enp1s0";
467 };
468 };
469 };
470 };
471 systemd.services = {
472 fou3-fou-encap.after = lib.optional (!networkd) "network-addresses-enp1s0.service";
473 };
474 };
475 testScript = ''
476 import json
477
478 machine.wait_for_unit("network.target")
479 fous = json.loads(machine.succeed("ip -json fou show"))
480 assert {"port": 9001, "gue": None, "family": "inet"} in fous, "fou1 exists"
481 assert {"port": 9002, "ipproto": 41, "family": "inet"} in fous, "fou2 exists"
482 ''
483 + lib.optionalString (!networkd) ''
484 assert {
485 "port": 9003,
486 "gue": None,
487 "family": "inet",
488 "local": "192.168.1.1",
489 } in fous, "fou3 exists"
490 assert {
491 "port": 9004,
492 "gue": None,
493 "family": "inet",
494 "local": "192.168.1.1",
495 "dev": "enp1s0",
496 } in fous, "fou4 exists"
497 '';
498 };
499 sit-6in4 =
500 let
501 node =
502 {
503 address4,
504 remote,
505 address6,
506 }:
507 {
508 virtualisation.interfaces.enp1s0.vlan = 1;
509 networking = {
510 useNetworkd = networkd;
511 useDHCP = false;
512 sits.sit = {
513 inherit remote;
514 local = address4;
515 dev = "enp1s0";
516 };
517 nftables.enable = true;
518 firewall.extraInputRules = "meta l4proto 41 accept";
519 interfaces.enp1s0.ipv4.addresses = lib.mkOverride 0 [
520 {
521 address = address4;
522 prefixLength = 24;
523 }
524 ];
525 interfaces.sit.ipv6.addresses = lib.mkOverride 0 [
526 {
527 address = address6;
528 prefixLength = 64;
529 }
530 ];
531 };
532 };
533 in
534 {
535 name = "Sit-6in4";
536 nodes.client1 = node {
537 address4 = "192.168.1.1";
538 remote = "192.168.1.2";
539 address6 = "fc00::1";
540 };
541 nodes.client2 = node {
542 address4 = "192.168.1.2";
543 remote = "192.168.1.1";
544 address6 = "fc00::2";
545 };
546 testScript = ''
547 start_all()
548
549 with subtest("Wait for networking to be configured"):
550 client1.wait_for_unit("network.target")
551 client2.wait_for_unit("network.target")
552
553 # Print diagnostic information
554 client1.succeed("ip addr >&2")
555 client2.succeed("ip addr >&2")
556
557 with subtest("Test ipv6"):
558 client1.wait_until_succeeds("ping -c 1 fc00::1")
559 client1.wait_until_succeeds("ping -c 1 fc00::2")
560
561 client2.wait_until_succeeds("ping -c 1 fc00::1")
562 client2.wait_until_succeeds("ping -c 1 fc00::2")
563 '';
564 };
565 sit-fou =
566 let
567 node =
568 {
569 address4,
570 remote,
571 address6,
572 }:
573 { pkgs, ... }:
574 {
575 virtualisation.interfaces.enp1s0.vlan = 1;
576 networking = {
577 useNetworkd = networkd;
578 useDHCP = false;
579 sits.sit = {
580 inherit remote;
581 local = address4;
582 dev = "enp1s0";
583 };
584 interfaces.enp1s0.ipv4.addresses = lib.mkOverride 0 [
585 {
586 address = address4;
587 prefixLength = 24;
588 }
589 ];
590 interfaces.sit.ipv6.addresses = lib.mkOverride 0 [
591 {
592 address = address6;
593 prefixLength = 64;
594 }
595 ];
596 };
597 };
598 in
599 {
600 name = "Sit-fou";
601 # note on firewalling: the two nodes are explicitly asymmetric.
602 # client1 sends SIT packets in UDP, but accepts only proto-41 incoming.
603 # client2 does the reverse, sending in proto-41 and accepting only UDP incoming.
604 # that way we'll notice when either SIT itself or FOU breaks.
605 nodes.client1 =
606 args@{ pkgs, ... }:
607 lib.mkMerge [
608 (node {
609 address4 = "192.168.1.1";
610 remote = "192.168.1.2";
611 address6 = "fc00::1";
612 } args)
613 {
614 networking = {
615 nftables.enable = true;
616 firewall.extraInputRules = "meta l4proto 41 accept";
617 sits.sit.encapsulation = {
618 type = "fou";
619 port = 9001;
620 };
621 };
622 }
623 ];
624 nodes.client2 =
625 args@{ pkgs, ... }:
626 lib.mkMerge [
627 (node {
628 address4 = "192.168.1.2";
629 remote = "192.168.1.1";
630 address6 = "fc00::2";
631 } args)
632 {
633 networking = {
634 firewall.allowedUDPPorts = [ 9001 ];
635 fooOverUDP.fou1 = {
636 port = 9001;
637 protocol = 41;
638 };
639 };
640 }
641 ];
642 testScript = ''
643 start_all()
644
645 with subtest("Wait for networking to be configured"):
646 client1.wait_for_unit("network.target")
647 client2.wait_for_unit("network.target")
648
649 # Print diagnostic information
650 client1.succeed("ip addr >&2")
651 client2.succeed("ip addr >&2")
652
653 with subtest("Test ipv6"):
654 client1.wait_until_succeeds("ping -c 1 fc00::1")
655 client1.wait_until_succeeds("ping -c 1 fc00::2")
656
657 client2.wait_until_succeeds("ping -c 1 fc00::1")
658 client2.wait_until_succeeds("ping -c 1 fc00::2")
659 '';
660 };
661 ipip-4in6 =
662 let
663 node =
664 {
665 address4,
666 remote,
667 address6,
668 }:
669 {
670 virtualisation.interfaces.enp1s0.vlan = 1;
671 networking = {
672 useNetworkd = networkd;
673 useDHCP = false;
674 ipips."4in6" = {
675 inherit remote;
676 local = address6;
677 dev = "enp1s0";
678 encapsulation.type = "4in6";
679 };
680 firewall.enable = false;
681 nftables.enable = true;
682 firewall.extraInputRules = "meta l4proto ipip accept";
683 interfaces.enp1s0.ipv6.addresses = lib.mkOverride 0 [
684 {
685 address = address6;
686 prefixLength = 64;
687 }
688 ];
689 interfaces."4in6".ipv4.addresses = lib.mkOverride 0 [
690 {
691 address = address4;
692 prefixLength = 24;
693 }
694 ];
695 };
696 };
697 in
698 {
699 name = "ipip-4in6";
700 nodes.client1 = node {
701 address6 = "fc00::1";
702 address4 = "192.168.1.1";
703 remote = "fc00::2";
704 };
705 nodes.client2 = node {
706 address6 = "fc00::2";
707 address4 = "192.168.1.2";
708 remote = "fc00::1";
709 };
710 testScript = ''
711 start_all()
712
713 with subtest("Wait for networking to be configured"):
714 client1.wait_for_unit("network.target")
715 client2.wait_for_unit("network.target")
716
717 # Print diagnostic information
718 client1.succeed("ip addr >&2")
719 client2.succeed("ip addr >&2")
720
721 with subtest("Test ipv6"):
722 client1.wait_until_succeeds("ping -c 1 192.168.1.1")
723 client1.wait_until_succeeds("ping -c 1 192.168.1.2")
724
725 client2.wait_until_succeeds("ping -c 1 192.168.1.1")
726 client2.wait_until_succeeds("ping -c 1 192.168.1.2")
727 '';
728 };
729 ipip =
730 let
731 node =
732 {
733 local,
734 remote,
735 address,
736 }:
737 {
738 virtualisation.interfaces.enp1s0.vlan = 1;
739 networking = {
740 useNetworkd = networkd;
741 useDHCP = false;
742 ipips.ipip = {
743 inherit local remote;
744 dev = "enp1s0";
745 encapsulation.type = "ipip";
746 };
747 nftables.enable = true;
748 firewall.extraInputRules = "meta l4proto 4 accept";
749 interfaces.enp1s0.ipv4.addresses = lib.mkOverride 0 [
750 {
751 address = local;
752 prefixLength = 24;
753 }
754 ];
755 interfaces.ipip.ipv4.addresses = lib.mkOverride 0 [
756 {
757 inherit address;
758 prefixLength = 24;
759 }
760 ];
761 };
762 };
763 in
764 {
765 name = "ipip";
766 nodes.client1 = node {
767 local = "192.168.1.1";
768 remote = "192.168.1.2";
769 address = "192.168.10.1";
770 };
771 nodes.client2 = node {
772 local = "192.168.1.2";
773 remote = "192.168.1.1";
774 address = "192.168.10.2";
775 };
776 testScript = ''
777 start_all()
778
779 with subtest("Wait for networking to be configured"):
780 client1.wait_for_unit("network.target")
781 client2.wait_for_unit("network.target")
782
783 # Print diagnostic information
784 client1.succeed("ip addr >&2")
785 client2.succeed("ip addr >&2")
786
787 with subtest("Test IPIP tunnel"):
788 client1.wait_until_succeeds("ping -c 1 192.168.10.1")
789 client1.wait_until_succeeds("ping -c 1 192.168.10.2")
790
791 client2.wait_until_succeeds("ping -c 1 192.168.10.1")
792 client2.wait_until_succeeds("ping -c 1 192.168.10.2")
793 '';
794 };
795 gre =
796 let
797 node =
798 { ... }:
799 {
800 networking = {
801 useNetworkd = networkd;
802 useDHCP = false;
803 firewall.extraCommands = "ip6tables -A nixos-fw -p gre -j nixos-fw-accept";
804 };
805 };
806 in
807 {
808 name = "GRE";
809 nodes.client1 =
810 args@{ pkgs, ... }:
811 lib.mkMerge [
812 (node args)
813 {
814 virtualisation.vlans = [
815 1
816 2
817 4
818 ];
819 networking = {
820 greTunnels = {
821 greTunnel = {
822 local = "192.168.2.1";
823 remote = "192.168.2.2";
824 dev = "eth2";
825 ttl = 225;
826 type = "tap";
827 };
828 gre6Tunnel = {
829 local = "fd00:1234:5678:4::1";
830 remote = "fd00:1234:5678:4::2";
831 dev = "eth3";
832 ttl = 255;
833 type = "tun6";
834 };
835 };
836 bridges.bridge.interfaces = [
837 "greTunnel"
838 "eth1"
839 ];
840 interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [ ];
841 interfaces.eth1.ipv6.addresses = lib.mkOverride 0 [ ];
842 interfaces.bridge.ipv4.addresses = lib.mkOverride 0 [
843 {
844 address = "192.168.1.1";
845 prefixLength = 24;
846 }
847 ];
848 interfaces.eth3.ipv6.addresses = [
849 {
850 address = "fd00:1234:5678:4::1";
851 prefixLength = 64;
852 }
853 ];
854 interfaces.gre6Tunnel.ipv6.addresses = lib.mkOverride 0 [
855 {
856 address = "fc00::1";
857 prefixLength = 64;
858 }
859 ];
860 };
861 }
862 ];
863 nodes.client2 =
864 args@{ pkgs, ... }:
865 lib.mkMerge [
866 (node args)
867 {
868 virtualisation.vlans = [
869 2
870 3
871 4
872 ];
873 networking = {
874 greTunnels = {
875 greTunnel = {
876 local = "192.168.2.2";
877 remote = "192.168.2.1";
878 dev = "eth1";
879 ttl = 225;
880 type = "tap";
881 };
882 gre6Tunnel = {
883 local = "fd00:1234:5678:4::2";
884 remote = "fd00:1234:5678:4::1";
885 dev = "eth3";
886 ttl = 255;
887 type = "tun6";
888 };
889 };
890 bridges.bridge.interfaces = [
891 "greTunnel"
892 "eth2"
893 ];
894 interfaces.eth2.ipv4.addresses = lib.mkOverride 0 [ ];
895 interfaces.eth2.ipv6.addresses = lib.mkOverride 0 [ ];
896 interfaces.bridge.ipv4.addresses = lib.mkOverride 0 [
897 {
898 address = "192.168.1.2";
899 prefixLength = 24;
900 }
901 ];
902 interfaces.eth3.ipv6.addresses = [
903 {
904 address = "fd00:1234:5678:4::2";
905 prefixLength = 64;
906 }
907 ];
908 interfaces.gre6Tunnel.ipv6.addresses = lib.mkOverride 0 [
909 {
910 address = "fc00::2";
911 prefixLength = 64;
912 }
913 ];
914 };
915 }
916 ];
917 testScript = ''
918 import json
919 start_all()
920
921 with subtest("Wait for networking to be configured"):
922 client1.wait_for_unit("network.target")
923 client2.wait_for_unit("network.target")
924
925 # Print diagnostic information
926 client1.succeed("ip addr >&2")
927 client2.succeed("ip addr >&2")
928
929 with subtest("Test GRE tunnel bridge over VLAN"):
930 client1.wait_until_succeeds("ping -c 1 192.168.1.2")
931
932 client2.wait_until_succeeds("ping -c 1 192.168.1.1")
933
934 client1.wait_until_succeeds("ping -c 1 fc00::2")
935
936 client2.wait_until_succeeds("ping -c 1 fc00::1")
937
938 with subtest("Test GRE tunnel TTL"):
939 links = json.loads(client1.succeed("ip -details -json link show greTunnel"))
940 assert links[0]['linkinfo']['info_data']['ttl'] == 225, "ttl not set for greTunnel"
941
942 links = json.loads(client2.succeed("ip -details -json link show gre6Tunnel"))
943 assert links[0]['linkinfo']['info_data']['ttl'] == 255, "ttl not set for gre6Tunnel"
944 '';
945 };
946 vlan =
947 let
948 node = address: {
949 networking = {
950 useNetworkd = networkd;
951 useDHCP = false;
952 vlans.vlan = {
953 id = 1;
954 interface = "eth0";
955 };
956 interfaces.eth0.ipv4.addresses = lib.mkOverride 0 [ ];
957 interfaces.eth1.ipv4.addresses = lib.mkOverride 0 [ ];
958 interfaces.vlan.ipv4.addresses = lib.mkOverride 0 [
959 {
960 inherit address;
961 prefixLength = 24;
962 }
963 ];
964 };
965 };
966 in
967 {
968 name = "vlan";
969 nodes.client1 = node "192.168.1.1";
970 nodes.client2 = node "192.168.1.2";
971 testScript = ''
972 start_all()
973
974 with subtest("Wait for networking to be configured"):
975 client1.wait_for_unit("network.target")
976 client2.wait_for_unit("network.target")
977
978 with subtest("Test vlan is setup"):
979 client1.succeed("ip addr show dev vlan >&2")
980 client2.succeed("ip addr show dev vlan >&2")
981 '';
982 };
983 vlan-ping =
984 let
985 baseIP = number: "10.10.10.${number}";
986 vlanIP = number: "10.1.1.${number}";
987 baseInterface = "enp1s0";
988 vlanInterface = "vlan42";
989 node = number: {
990 virtualisation.interfaces.enp1s0.vlan = 1;
991 networking = {
992 #useNetworkd = networkd;
993 useDHCP = false;
994 vlans.${vlanInterface} = {
995 id = 42;
996 interface = baseInterface;
997 };
998 interfaces.${baseInterface}.ipv4.addresses = lib.mkOverride 0 [
999 {
1000 address = baseIP number;
1001 prefixLength = 24;
1002 }
1003 ];
1004 interfaces.${vlanInterface}.ipv4.addresses = lib.mkOverride 0 [
1005 {
1006 address = vlanIP number;
1007 prefixLength = 24;
1008 }
1009 ];
1010 };
1011 };
1012
1013 serverNodeNum = "1";
1014 clientNodeNum = "2";
1015
1016 in
1017 {
1018 name = "vlan-ping";
1019 nodes.server = node serverNodeNum;
1020 nodes.client = node clientNodeNum;
1021 testScript = ''
1022 start_all()
1023
1024 with subtest("Wait for networking to be configured"):
1025 server.wait_for_unit("network.target")
1026 client.wait_for_unit("network.target")
1027
1028 with subtest("Test ping on base interface in setup"):
1029 client.succeed("ping -I ${baseInterface} -c 1 ${baseIP serverNodeNum}")
1030 server.succeed("ping -I ${baseInterface} -c 1 ${baseIP clientNodeNum}")
1031
1032 with subtest("Test ping on vlan subinterface in setup"):
1033 client.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP serverNodeNum}")
1034 server.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP clientNodeNum}")
1035 '';
1036 };
1037 virtual = {
1038 name = "Virtual";
1039 nodes.machine = {
1040 networking.useNetworkd = networkd;
1041 networking.useDHCP = false;
1042 networking.interfaces.tap0 = {
1043 ipv4.addresses = [
1044 {
1045 address = "192.168.1.1";
1046 prefixLength = 24;
1047 }
1048 ];
1049 ipv6.addresses = [
1050 {
1051 address = "2001:1470:fffd:2096::";
1052 prefixLength = 64;
1053 }
1054 ];
1055 virtual = true;
1056 mtu = 1342;
1057 macAddress = "02:de:ad:be:ef:01";
1058 };
1059 networking.interfaces.tun0 = {
1060 ipv4.addresses = [
1061 {
1062 address = "192.168.1.2";
1063 prefixLength = 24;
1064 }
1065 ];
1066 ipv6.addresses = [
1067 {
1068 address = "2001:1470:fffd:2097::";
1069 prefixLength = 64;
1070 }
1071 ];
1072 virtual = true;
1073 mtu = 1343;
1074 };
1075 };
1076
1077 testScript = ''
1078 targetList = """
1079 tap0: tap persist user 0
1080 tun0: tun persist user 0
1081 """.strip()
1082
1083 with subtest("Wait for networking to come up"):
1084 machine.start()
1085 machine.wait_for_unit("network.target")
1086
1087 with subtest("Test interfaces set up"):
1088 list = machine.succeed("ip tuntap list | sort").strip()
1089 assert (
1090 list == targetList
1091 ), """
1092 The list of virtual interfaces does not match the expected one:
1093 Result:
1094 {}
1095 Expected:
1096 {}
1097 """.format(
1098 list, targetList
1099 )
1100 with subtest("Test MTU and MAC Address are configured"):
1101 machine.wait_until_succeeds("ip link show dev tap0 | grep 'mtu 1342'")
1102 machine.wait_until_succeeds("ip link show dev tun0 | grep 'mtu 1343'")
1103 assert "02:de:ad:be:ef:01" in machine.succeed("ip link show dev tap0")
1104 '' # network-addresses-* only exist in scripted networking
1105 + lib.optionalString (!networkd) ''
1106 with subtest("Test interfaces' addresses clean up"):
1107 machine.succeed("systemctl stop network-addresses-tap0")
1108 machine.sleep(10)
1109 machine.succeed("systemctl stop network-addresses-tun0")
1110 machine.sleep(10)
1111 residue = machine.succeed("ip tuntap list | sort").strip()
1112 assert (
1113 residue == targetList
1114 ), "Some virtual interface has been removed:\n{}".format(residue)
1115 assert "192.168.1.1" not in machine.succeed("ip address show dev tap0"), "tap0 interface address has not been removed"
1116 assert "192.168.1.2" not in machine.succeed("ip address show dev tun0"), "tun0 interface address has not been removed"
1117
1118 with subtest("Test interfaces clean up"):
1119 machine.succeed("systemctl stop tap0-netdev")
1120 machine.sleep(10)
1121 machine.succeed("systemctl stop tun0-netdev")
1122 machine.sleep(10)
1123 residue = machine.succeed("ip tuntap list")
1124 assert (
1125 residue == ""
1126 ), "Some virtual interface has not been properly cleaned:\n{}".format(residue)
1127 '';
1128 };
1129 privacy = {
1130 name = "Privacy";
1131 nodes.router = {
1132 virtualisation.interfaces.enp1s0.vlan = 1;
1133 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
1134 networking = {
1135 useNetworkd = networkd;
1136 useDHCP = false;
1137 interfaces.enp1s0.ipv6.addresses = lib.singleton {
1138 address = "fd00:1234:5678:1::1";
1139 prefixLength = 64;
1140 };
1141 };
1142 services.radvd = {
1143 enable = true;
1144 config = ''
1145 interface enp1s0 {
1146 AdvSendAdvert on;
1147 AdvManagedFlag on;
1148 AdvOtherConfigFlag on;
1149
1150 prefix fd00:1234:5678:1::/64 {
1151 AdvAutonomous on;
1152 AdvOnLink on;
1153 };
1154 };
1155 '';
1156 };
1157 };
1158 nodes.client_with_privacy = {
1159 virtualisation.interfaces.enp1s0.vlan = 1;
1160 networking = {
1161 useNetworkd = networkd;
1162 useDHCP = false;
1163 interfaces.enp1s0 = {
1164 tempAddress = "default";
1165 ipv4.addresses = lib.mkOverride 0 [ ];
1166 ipv6.addresses = lib.mkOverride 0 [ ];
1167 useDHCP = true;
1168 };
1169 };
1170 };
1171 nodes.client = {
1172 virtualisation.interfaces.enp1s0.vlan = 1;
1173 networking = {
1174 useNetworkd = networkd;
1175 useDHCP = false;
1176 interfaces.enp1s0 = {
1177 tempAddress = "enabled";
1178 ipv4.addresses = lib.mkOverride 0 [ ];
1179 ipv6.addresses = lib.mkOverride 0 [ ];
1180 useDHCP = true;
1181 };
1182 };
1183 };
1184 testScript = ''
1185 start_all()
1186
1187 client.wait_for_unit("network.target")
1188 client_with_privacy.wait_for_unit("network.target")
1189 router.systemctl("start network-online.target")
1190 router.wait_for_unit("network-online.target")
1191
1192 with subtest("Wait until we have an ip address"):
1193 client_with_privacy.wait_until_succeeds(
1194 "ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'"
1195 )
1196 client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
1197
1198 with subtest("Test vlan 1"):
1199 client_with_privacy.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
1200 client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
1201
1202 with subtest("Test address used is temporary"):
1203 client_with_privacy.wait_until_succeeds(
1204 "! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
1205 )
1206
1207 with subtest("Test address used is EUI-64"):
1208 client.wait_until_succeeds(
1209 "ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
1210 )
1211 '';
1212 };
1213 routes = {
1214 name = "routes";
1215 nodes.machine = {
1216 networking.useNetworkd = networkd;
1217 networking.useDHCP = false;
1218 networking.interfaces.eth0 = {
1219 ipv4.addresses = [
1220 {
1221 address = "192.168.1.2";
1222 prefixLength = 24;
1223 }
1224 ];
1225 ipv6.addresses = [
1226 {
1227 address = "2001:1470:fffd:2097::";
1228 prefixLength = 64;
1229 }
1230 ];
1231 ipv6.routes = [
1232 {
1233 address = "fdfd:b3f0::";
1234 prefixLength = 48;
1235 }
1236 {
1237 address = "2001:1470:fffd:2098::";
1238 prefixLength = 64;
1239 via = "fdfd:b3f0::1";
1240 }
1241 ];
1242 ipv4.routes = [
1243 {
1244 address = "10.0.0.0";
1245 prefixLength = 16;
1246 options = {
1247 mtu = "1500";
1248 # Explicitly set scope because iproute and systemd-networkd
1249 # disagree on what the scope should be
1250 # if the type is the default "unicast"
1251 scope = "link";
1252 };
1253 }
1254 {
1255 address = "192.168.2.0";
1256 prefixLength = 24;
1257 via = "192.168.1.1";
1258 }
1259 ];
1260 };
1261 virtualisation.vlans = [ ];
1262 };
1263
1264 testScript = ''
1265 targetIPv4Table = [
1266 "10.0.0.0/16 proto static scope link mtu 1500",
1267 "192.168.1.0/24 proto kernel scope link src 192.168.1.2",
1268 "192.168.2.0/24 via 192.168.1.1 proto static",
1269 ]
1270
1271 targetIPv6Table = [
1272 "2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium",
1273 "2001:1470:fffd:2098::/64 via fdfd:b3f0::1 proto static metric 1024 pref medium",
1274 "fdfd:b3f0::/48 proto static metric 1024 pref medium",
1275 ]
1276
1277 machine.start()
1278 machine.wait_for_unit("network.target")
1279
1280 with subtest("test routing tables"):
1281 ipv4Table = machine.succeed("ip -4 route list dev eth0 | head -n3").strip()
1282 ipv6Table = machine.succeed("ip -6 route list dev eth0 | head -n3").strip()
1283 assert [
1284 l.strip() for l in ipv4Table.splitlines()
1285 ] == targetIPv4Table, """
1286 The IPv4 routing table does not match the expected one:
1287 Result:
1288 {}
1289 Expected:
1290 {}
1291 """.format(
1292 ipv4Table, targetIPv4Table
1293 )
1294 assert [
1295 l.strip() for l in ipv6Table.splitlines()
1296 ] == targetIPv6Table, """
1297 The IPv6 routing table does not match the expected one:
1298 Result:
1299 {}
1300 Expected:
1301 {}
1302 """.format(
1303 ipv6Table, targetIPv6Table
1304 )
1305
1306 ''
1307 + lib.optionalString (!networkd) ''
1308 with subtest("test clean-up of the tables"):
1309 machine.succeed("systemctl stop network-addresses-eth0")
1310 ipv4Residue = machine.succeed("ip -4 route list dev eth0 | head -n-3").strip()
1311 ipv6Residue = machine.succeed("ip -6 route list dev eth0 | head -n-3").strip()
1312 assert (
1313 ipv4Residue == ""
1314 ), "The IPv4 routing table has not been properly cleaned:\n{}".format(ipv4Residue)
1315 assert (
1316 ipv6Residue == ""
1317 ), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue)
1318 '';
1319 };
1320 rename =
1321 if networkd then
1322 {
1323 name = "RenameInterface";
1324 nodes.machine = {
1325 virtualisation.vlans = [ 1 ];
1326 networking = {
1327 useNetworkd = networkd;
1328 useDHCP = false;
1329 };
1330 systemd.network.links."10-custom_name" = {
1331 matchConfig.MACAddress = "52:54:00:12:01:01";
1332 linkConfig.Name = "custom_name";
1333 };
1334 };
1335 testScript = ''
1336 machine.succeed("udevadm settle")
1337 print(machine.succeed("ip link show dev custom_name"))
1338 '';
1339 }
1340 else
1341 {
1342 name = "RenameInterface";
1343 nodes = { };
1344 testScript = "";
1345 };
1346 # even with disabled networkd, systemd.network.links should work
1347 # (as it's handled by udev, not networkd)
1348 link = {
1349 name = "Link";
1350 nodes.client = {
1351 virtualisation.vlans = [ 1 ];
1352 networking = {
1353 useNetworkd = networkd;
1354 useDHCP = false;
1355 };
1356 systemd.network.links."50-foo" = {
1357 matchConfig = {
1358 Name = "foo";
1359 Driver = "dummy";
1360 };
1361 linkConfig.MTUBytes = "1442";
1362 };
1363 };
1364 testScript = ''
1365 print(client.succeed("ip l add name foo type dummy"))
1366 print(client.succeed("stat /etc/systemd/network/50-foo.link"))
1367 client.succeed("udevadm settle")
1368 assert "mtu 1442" in client.succeed("ip l show dev foo")
1369 '';
1370 };
1371 wlanInterface =
1372 let
1373 testMac = "06:00:00:00:02:00";
1374 in
1375 {
1376 name = "WlanInterface";
1377 nodes.machine = {
1378 boot.kernelModules = [ "mac80211_hwsim" ];
1379 networking.wlanInterfaces = {
1380 wlan0 = {
1381 device = "wlan0";
1382 };
1383 wap0 = {
1384 device = "wlan0";
1385 mac = testMac;
1386 };
1387 };
1388 };
1389 testScript = ''
1390 machine.start()
1391 machine.wait_for_unit("network.target")
1392 machine.wait_until_succeeds("ip address show wap0 | grep -q ${testMac}")
1393 machine.fail("ip address show wlan0 | grep -q ${testMac}")
1394 '';
1395 };
1396 naughtyInterfaceNames =
1397 let
1398 ifnames = [
1399 # flags of ip-address
1400 "home"
1401 "temporary"
1402 "optimistic"
1403 "bridge_slave"
1404 "flush"
1405 # flags of ip-route
1406 "up"
1407 "type"
1408 "nomaster"
1409 "address"
1410 # other
1411 "very_loong_name"
1412 "lowerUpper"
1413 "-"
1414 ];
1415 in
1416 {
1417 name = "naughtyInterfaceNames";
1418 nodes.machine = {
1419 networking.useNetworkd = networkd;
1420 networking.bridges = lib.listToAttrs (
1421 lib.flip builtins.map ifnames (name: {
1422 inherit name;
1423 value.interfaces = [ ];
1424 })
1425 );
1426 };
1427 testScript = ''
1428 machine.start()
1429 machine.wait_for_unit("network.target")
1430 for ifname in ${builtins.toJSON ifnames}:
1431 machine.wait_until_succeeds(f"ip link show dev '{ifname}' | grep -q '{ifname}'")
1432 '';
1433 };
1434 caseSensitiveRenaming = {
1435 name = "CaseSensitiveRenaming";
1436 nodes.machine = {
1437 virtualisation.interfaces.enCustom.vlan = 11;
1438 networking = {
1439 useNetworkd = networkd;
1440 useDHCP = false;
1441 };
1442 };
1443 testScript = ''
1444 machine.succeed("udevadm settle")
1445 print(machine.succeed("ip link show dev enCustom"))
1446 machine.wait_until_succeeds("ip link show dev enCustom | grep -q 52:54:00:12:0b:01")
1447 '';
1448 };
1449 };
1450
1451in
1452lib.mapAttrs (lib.const (
1453 attrs:
1454 makeTest (
1455 attrs
1456 // {
1457 name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
1458 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
1459 }
1460 )
1461)) testCases