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