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