1{ system ? builtins.currentSystem
2# bool: whether to use networkd in the tests
3, networkd }:
4
5with import ../lib/testing.nix { inherit system; };
6with pkgs.lib;
7
8let
9 router = { config, pkgs, ... }:
10 with pkgs.lib;
11 let
12 vlanIfs = range 1 (length config.virtualisation.vlans);
13 in {
14 virtualisation.vlans = [ 1 2 3 ];
15 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
16 networking = {
17 useDHCP = false;
18 useNetworkd = networkd;
19 firewall.allowPing = true;
20 firewall.checkReversePath = true;
21 firewall.allowedUDPPorts = [ 547 ];
22 interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
23 nameValuePair "eth${toString n}" {
24 ipv4.addresses = [ { address = "192.168.${toString n}.1"; prefixLength = 24; } ];
25 ipv6.addresses = [ { address = "fd00:1234:5678:${toString n}::1"; prefixLength = 64; } ];
26 })));
27 };
28 services.dhcpd4 = {
29 enable = true;
30 interfaces = map (n: "eth${toString n}") vlanIfs;
31 extraConfig = ''
32 authoritative;
33 '' + flip concatMapStrings vlanIfs (n: ''
34 subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
35 option routers 192.168.${toString n}.1;
36 # XXX: technically it's _not guaranteed_ that IP addresses will be
37 # issued from the first item in range onwards! We assume that in
38 # our tests however.
39 range 192.168.${toString n}.2 192.168.${toString n}.254;
40 }
41 '');
42 };
43 services.radvd = {
44 enable = true;
45 config = flip concatMapStrings vlanIfs (n: ''
46 interface eth${toString n} {
47 AdvSendAdvert on;
48 AdvManagedFlag on;
49 AdvOtherConfigFlag on;
50
51 prefix fd00:1234:5678:${toString n}::/64 {
52 AdvAutonomous off;
53 };
54 };
55 '');
56 };
57 services.dhcpd6 = {
58 enable = true;
59 interfaces = map (n: "eth${toString n}") vlanIfs;
60 extraConfig = ''
61 authoritative;
62 '' + flip concatMapStrings vlanIfs (n: ''
63 subnet6 fd00:1234:5678:${toString n}::/64 {
64 range6 fd00:1234:5678:${toString n}::2 fd00:1234:5678:${toString n}::2;
65 }
66 '');
67 };
68 };
69
70 testCases = {
71 loopback = {
72 name = "Loopback";
73 machine.networking.useNetworkd = networkd;
74 testScript = ''
75 startAll;
76 $machine->waitForUnit("network.target");
77 $machine->succeed("ip addr show lo | grep -q 'inet 127.0.0.1/8 '");
78 $machine->succeed("ip addr show lo | grep -q 'inet6 ::1/128 '");
79 '';
80 };
81 static = {
82 name = "Static";
83 nodes.router = router;
84 nodes.client = { config, pkgs, ... }: with pkgs.lib; {
85 virtualisation.vlans = [ 1 2 ];
86 networking = {
87 useNetworkd = networkd;
88 firewall.allowPing = true;
89 useDHCP = false;
90 defaultGateway = "192.168.1.1";
91 interfaces.eth1.ipv4.addresses = mkOverride 0 [
92 { address = "192.168.1.2"; prefixLength = 24; }
93 { address = "192.168.1.3"; prefixLength = 32; }
94 { address = "192.168.1.10"; prefixLength = 32; }
95 ];
96 interfaces.eth2.ipv4.addresses = mkOverride 0 [
97 { address = "192.168.2.2"; prefixLength = 24; }
98 ];
99 };
100 };
101 testScript = { nodes, ... }:
102 ''
103 startAll;
104
105 $client->waitForUnit("network.target");
106 $router->waitForUnit("network-online.target");
107
108 # Make sure dhcpcd is not started
109 $client->fail("systemctl status dhcpcd.service");
110
111 # Test vlan 1
112 $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
113 $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
114 $client->waitUntilSucceeds("ping -c 1 192.168.1.3");
115 $client->waitUntilSucceeds("ping -c 1 192.168.1.10");
116
117 $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
118 $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
119 $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
120 $router->waitUntilSucceeds("ping -c 1 192.168.1.10");
121
122 # Test vlan 2
123 $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
124 $client->waitUntilSucceeds("ping -c 1 192.168.2.2");
125
126 $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
127 $router->waitUntilSucceeds("ping -c 1 192.168.2.2");
128
129 # Test default gateway
130 $router->waitUntilSucceeds("ping -c 1 192.168.3.1");
131 $client->waitUntilSucceeds("ping -c 1 192.168.3.1");
132 '';
133 };
134 dhcpSimple = {
135 name = "SimpleDHCP";
136 nodes.router = router;
137 nodes.client = { config, pkgs, ... }: with pkgs.lib; {
138 virtualisation.vlans = [ 1 2 ];
139 networking = {
140 useNetworkd = networkd;
141 firewall.allowPing = true;
142 useDHCP = true;
143 interfaces.eth1 = {
144 ipv4.addresses = mkOverride 0 [ ];
145 ipv6.addresses = mkOverride 0 [ ];
146 };
147 interfaces.eth2 = {
148 ipv4.addresses = mkOverride 0 [ ];
149 ipv6.addresses = mkOverride 0 [ ];
150 };
151 };
152 };
153 testScript = { nodes, ... }:
154 ''
155 startAll;
156
157 $client->waitForUnit("network.target");
158 $router->waitForUnit("network-online.target");
159
160 # Wait until we have an ip address on each interface
161 $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
162 $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
163 $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q '192.168.2'");
164 $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'");
165
166 # Test vlan 1
167 $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
168 $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
169 $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
170 $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::2");
171
172 $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
173 $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
174 $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
175 $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::2");
176
177 # Test vlan 2
178 $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
179 $client->waitUntilSucceeds("ping -c 1 192.168.2.2");
180 $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::1");
181 $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::2");
182
183 $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
184 $router->waitUntilSucceeds("ping -c 1 192.168.2.2");
185 $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::1");
186 $router->waitUntilSucceeds("ping -c 1 fd00:1234:5678:2::2");
187 '';
188 };
189 dhcpOneIf = {
190 name = "OneInterfaceDHCP";
191 nodes.router = router;
192 nodes.client = { config, pkgs, ... }: with pkgs.lib; {
193 virtualisation.vlans = [ 1 2 ];
194 networking = {
195 useNetworkd = networkd;
196 firewall.allowPing = true;
197 useDHCP = false;
198 interfaces.eth1 = {
199 ipv4.addresses = mkOverride 0 [ ];
200 useDHCP = true;
201 };
202 interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
203 };
204 };
205 testScript = { nodes, ... }:
206 ''
207 startAll;
208
209 # Wait for networking to come up
210 $client->waitForUnit("network.target");
211 $router->waitForUnit("network.target");
212
213 # Wait until we have an ip address on each interface
214 $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
215
216 # Test vlan 1
217 $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
218 $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
219
220 $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
221 $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
222
223 # Test vlan 2
224 $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
225 $client->fail("ping -c 1 192.168.2.2");
226
227 $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
228 $router->fail("ping -c 1 192.168.2.2");
229 '';
230 };
231 bond = let
232 node = address: { config, pkgs, ... }: with pkgs.lib; {
233 virtualisation.vlans = [ 1 2 ];
234 networking = {
235 useNetworkd = networkd;
236 firewall.allowPing = true;
237 useDHCP = false;
238 bonds.bond = {
239 interfaces = [ "eth1" "eth2" ];
240 driverOptions.mode = "balance-rr";
241 };
242 interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
243 interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
244 interfaces.bond.ipv4.addresses = mkOverride 0
245 [ { inherit address; prefixLength = 30; } ];
246 };
247 };
248 in {
249 name = "Bond";
250 nodes.client1 = node "192.168.1.1";
251 nodes.client2 = node "192.168.1.2";
252 testScript = { nodes, ... }:
253 ''
254 startAll;
255
256 # Wait for networking to come up
257 $client1->waitForUnit("network.target");
258 $client2->waitForUnit("network.target");
259
260 # Test bonding
261 $client1->waitUntilSucceeds("ping -c 2 192.168.1.1");
262 $client1->waitUntilSucceeds("ping -c 2 192.168.1.2");
263
264 $client2->waitUntilSucceeds("ping -c 2 192.168.1.1");
265 $client2->waitUntilSucceeds("ping -c 2 192.168.1.2");
266 '';
267 };
268 bridge = let
269 node = { address, vlan }: { config, pkgs, ... }: with pkgs.lib; {
270 virtualisation.vlans = [ vlan ];
271 networking = {
272 useNetworkd = networkd;
273 firewall.allowPing = true;
274 useDHCP = false;
275 interfaces.eth1.ipv4.addresses = mkOverride 0
276 [ { inherit address; prefixLength = 24; } ];
277 };
278 };
279 in {
280 name = "Bridge";
281 nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
282 nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
283 nodes.router = { config, pkgs, ... }: with pkgs.lib; {
284 virtualisation.vlans = [ 1 2 ];
285 networking = {
286 useNetworkd = networkd;
287 firewall.allowPing = true;
288 useDHCP = false;
289 bridges.bridge.interfaces = [ "eth1" "eth2" ];
290 interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
291 interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
292 interfaces.bridge.ipv4.addresses = mkOverride 0
293 [ { address = "192.168.1.1"; prefixLength = 24; } ];
294 };
295 };
296 testScript = { nodes, ... }:
297 ''
298 startAll;
299
300 # Wait for networking to come up
301 $client1->waitForUnit("network.target");
302 $client2->waitForUnit("network.target");
303 $router->waitForUnit("network.target");
304
305 # Test bridging
306 $client1->waitUntilSucceeds("ping -c 1 192.168.1.1");
307 $client1->waitUntilSucceeds("ping -c 1 192.168.1.2");
308 $client1->waitUntilSucceeds("ping -c 1 192.168.1.3");
309
310 $client2->waitUntilSucceeds("ping -c 1 192.168.1.1");
311 $client2->waitUntilSucceeds("ping -c 1 192.168.1.2");
312 $client2->waitUntilSucceeds("ping -c 1 192.168.1.3");
313
314 $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
315 $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
316 $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
317 '';
318 };
319 macvlan = {
320 name = "MACVLAN";
321 nodes.router = router;
322 nodes.client = { config, pkgs, ... }: with pkgs.lib; {
323 virtualisation.vlans = [ 1 ];
324 networking = {
325 useNetworkd = networkd;
326 firewall.allowPing = true;
327 useDHCP = true;
328 macvlans.macvlan.interface = "eth1";
329 interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
330 };
331 };
332 testScript = { nodes, ... }:
333 ''
334 startAll;
335
336 # Wait for networking to come up
337 $client->waitForUnit("network.target");
338 $router->waitForUnit("network.target");
339
340 # Wait until we have an ip address on each interface
341 $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
342 $client->waitUntilSucceeds("ip addr show dev macvlan | grep -q '192.168.1'");
343
344 # Print diagnosting information
345 $router->succeed("ip addr >&2");
346 $client->succeed("ip addr >&2");
347
348 # Test macvlan creates routable ips
349 $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
350 $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
351 $client->waitUntilSucceeds("ping -c 1 192.168.1.3");
352
353 $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
354 $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
355 $router->waitUntilSucceeds("ping -c 1 192.168.1.3");
356 '';
357 };
358 sit = let
359 node = { address4, remote, address6 }: { config, pkgs, ... }: with pkgs.lib; {
360 virtualisation.vlans = [ 1 ];
361 networking = {
362 useNetworkd = networkd;
363 firewall.enable = false;
364 useDHCP = false;
365 sits.sit = {
366 inherit remote;
367 local = address4;
368 dev = "eth1";
369 };
370 interfaces.eth1.ipv4.addresses = mkOverride 0
371 [ { address = address4; prefixLength = 24; } ];
372 interfaces.sit.ipv6.addresses = mkOverride 0
373 [ { address = address6; prefixLength = 64; } ];
374 };
375 };
376 in {
377 name = "Sit";
378 nodes.client1 = node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; };
379 nodes.client2 = node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; };
380 testScript = { nodes, ... }:
381 ''
382 startAll;
383
384 # Wait for networking to be configured
385 $client1->waitForUnit("network.target");
386 $client2->waitForUnit("network.target");
387
388 # Print diagnostic information
389 $client1->succeed("ip addr >&2");
390 $client2->succeed("ip addr >&2");
391
392 # Test ipv6
393 $client1->waitUntilSucceeds("ping -c 1 fc00::1");
394 $client1->waitUntilSucceeds("ping -c 1 fc00::2");
395
396 $client2->waitUntilSucceeds("ping -c 1 fc00::1");
397 $client2->waitUntilSucceeds("ping -c 1 fc00::2");
398 '';
399 };
400 vlan = let
401 node = address: { config, pkgs, ... }: with pkgs.lib; {
402 #virtualisation.vlans = [ 1 ];
403 networking = {
404 useNetworkd = networkd;
405 firewall.allowPing = true;
406 useDHCP = false;
407 vlans.vlan = {
408 id = 1;
409 interface = "eth0";
410 };
411 interfaces.eth0.ipv4.addresses = mkOverride 0 [ ];
412 interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
413 interfaces.vlan.ipv4.addresses = mkOverride 0
414 [ { inherit address; prefixLength = 24; } ];
415 };
416 };
417 in {
418 name = "vlan";
419 nodes.client1 = node "192.168.1.1";
420 nodes.client2 = node "192.168.1.2";
421 testScript = { nodes, ... }:
422 ''
423 startAll;
424
425 # Wait for networking to be configured
426 $client1->waitForUnit("network.target");
427 $client2->waitForUnit("network.target");
428
429 # Test vlan is setup
430 $client1->succeed("ip addr show dev vlan >&2");
431 $client2->succeed("ip addr show dev vlan >&2");
432 '';
433 };
434 virtual = {
435 name = "Virtual";
436 machine = {
437 networking.interfaces."tap0" = {
438 ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
439 ipv6.addresses = [ { address = "2001:1470:fffd:2096::"; prefixLength = 64; } ];
440 virtual = true;
441 };
442 networking.interfaces."tun0" = {
443 ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
444 ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
445 virtual = true;
446 };
447 };
448
449 testScript = ''
450 my $targetList = <<'END';
451 tap0: tap UNKNOWN_FLAGS:800 user 0
452 tun0: tun UNKNOWN_FLAGS:800 user 0
453 END
454
455 # Wait for networking to come up
456 $machine->start;
457 $machine->waitForUnit("network.target");
458
459 # Test interfaces set up
460 my $list = $machine->succeed("ip tuntap list | sort");
461 "$list" eq "$targetList" or die(
462 "The list of virtual interfaces does not match the expected one:\n",
463 "Result:\n", "$list\n",
464 "Expected:\n", "$targetList\n"
465 );
466
467 # Test interfaces clean up
468 $machine->succeed("systemctl stop network-addresses-tap0");
469 $machine->succeed("systemctl stop network-addresses-tun0");
470 my $residue = $machine->succeed("ip tuntap list");
471 $residue eq "" or die(
472 "Some virtual interface has not been properly cleaned:\n",
473 "$residue\n"
474 );
475 '';
476 };
477 privacy = {
478 name = "Privacy";
479 nodes.router = { config, pkgs, ... }: {
480 virtualisation.vlans = [ 1 ];
481 boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
482 networking = {
483 useNetworkd = networkd;
484 interfaces.eth1.ipv6.addresses = singleton {
485 address = "fd00:1234:5678:1::1";
486 prefixLength = 64;
487 };
488 };
489 services.radvd = {
490 enable = true;
491 config = ''
492 interface eth1 {
493 AdvSendAdvert on;
494 AdvManagedFlag on;
495 AdvOtherConfigFlag on;
496
497 prefix fd00:1234:5678:1::/64 {
498 AdvAutonomous on;
499 AdvOnLink on;
500 };
501 };
502 '';
503 };
504 };
505 nodes.client = { config, pkgs, ... }: with pkgs.lib; {
506 virtualisation.vlans = [ 1 ];
507 networking = {
508 useNetworkd = networkd;
509 useDHCP = true;
510 interfaces.eth1 = {
511 preferTempAddress = true;
512 ipv4.addresses = mkOverride 0 [ ];
513 ipv6.addresses = mkOverride 0 [ ];
514 };
515 };
516 };
517 testScript = { nodes, ... }:
518 ''
519 startAll;
520
521 $client->waitForUnit("network.target");
522 $router->waitForUnit("network-online.target");
523
524 # Wait until we have an ip address
525 $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
526
527 # Test vlan 1
528 $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
529
530 # Test address used is temporary
531 $client->waitUntilSucceeds("! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'");
532 '';
533 };
534 routes = {
535 name = "routes";
536 machine = {
537 networking.useDHCP = false;
538 networking.interfaces."eth0" = {
539 ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
540 ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
541 ipv6.routes = [
542 { address = "fdfd:b3f0::"; prefixLength = 48; }
543 { address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; }
544 ];
545 ipv4.routes = [
546 { address = "10.0.0.0"; prefixLength = 16; options = { mtu = "1500"; }; }
547 { address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; }
548 ];
549 };
550 virtualisation.vlans = [ ];
551 };
552
553 testScript = ''
554 my $targetIPv4Table = <<'END';
555 10.0.0.0/16 scope link mtu 1500
556 192.168.1.0/24 proto kernel scope link src 192.168.1.2
557 192.168.2.0/24 via 192.168.1.1
558 END
559
560 my $targetIPv6Table = <<'END';
561 2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium
562 2001:1470:fffd:2098::/64 via fdfd:b3f0::1 metric 1024 pref medium
563 fdfd:b3f0::/48 metric 1024 pref medium
564 END
565
566 $machine->start;
567 $machine->waitForUnit("network.target");
568
569 # test routing tables
570 my $ipv4Table = $machine->succeed("ip -4 route list dev eth0 | head -n3");
571 my $ipv6Table = $machine->succeed("ip -6 route list dev eth0 | head -n3");
572 "$ipv4Table" eq "$targetIPv4Table" or die(
573 "The IPv4 routing table does not match the expected one:\n",
574 "Result:\n", "$ipv4Table\n",
575 "Expected:\n", "$targetIPv4Table\n"
576 );
577 "$ipv6Table" eq "$targetIPv6Table" or die(
578 "The IPv6 routing table does not match the expected one:\n",
579 "Result:\n", "$ipv6Table\n",
580 "Expected:\n", "$targetIPv6Table\n"
581 );
582
583 # test clean-up of the tables
584 $machine->succeed("systemctl stop network-addresses-eth0");
585 my $ipv4Residue = $machine->succeed("ip -4 route list dev eth0 | head -n-3");
586 my $ipv6Residue = $machine->succeed("ip -6 route list dev eth0 | head -n-3");
587 $ipv4Residue eq "" or die(
588 "The IPv4 routing table has not been properly cleaned:\n",
589 "$ipv4Residue\n"
590 );
591 $ipv6Residue eq "" or die(
592 "The IPv6 routing table has not been properly cleaned:\n",
593 "$ipv6Residue\n"
594 );
595 '';
596 };
597 };
598
599in mapAttrs (const (attrs: makeTest (attrs // {
600 name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
601 meta = with pkgs.stdenv.lib.maintainers; {
602 maintainers = [ wkennington ];
603 };
604}))) testCases