python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / tests / systemd-networkd-ipv6-prefix-delegation.nix
blob279b9aac8edb65ad05e7ea0e198c3d275f6a628c
1 # This test verifies that we can request and assign IPv6 prefixes from upstream
2 # (e.g. ISP) routers.
3 # The setup consits of three VMs. One for the ISP, as your residential router
4 # and the third as a client machine in the residential network.
6 # There are two VLANs in this test:
7 # - VLAN 1 is the connection between the ISP and the router
8 # - VLAN 2 is the connection between the router and the client
10 import ./make-test-python.nix ({ pkgs, lib, ... }: {
11   name = "systemd-networkd-ipv6-prefix-delegation";
12   meta = with lib.maintainers; {
13     maintainers = [ andir hexa ];
14   };
15   nodes = {
17     # The ISP's routers job is to delegate IPv6 prefixes via DHCPv6. Like with
18     # regular IPv6 auto-configuration it will also emit IPv6 router
19     # advertisements (RAs). Those RA's will not carry a prefix but in contrast
20     # just set the "Other" flag to indicate to the receiving nodes that they
21     # should attempt DHCPv6.
22     #
23     # Note: On the ISPs device we don't really care if we are using networkd in
24     # this example. That being said we can't use it (yet) as networkd doesn't
25     # implement the serving side of DHCPv6. We will use ISC Kea for that task.
26     isp = { lib, pkgs, ... }: {
27       virtualisation.vlans = [ 1 ];
28       networking = {
29         useDHCP = false;
30         firewall.enable = false;
31         interfaces.eth1 = lib.mkForce {}; # Don't use scripted networking
32       };
34       systemd.network = {
35         enable = true;
37         networks = {
38           "eth1" = {
39             matchConfig.Name = "eth1";
40             address = [
41               "2001:DB8::1/64"
42             ];
43             networkConfig.IPForward = true;
44           };
45         };
46       };
48       # Since we want to program the routes that we delegate to the "customer"
49       # into our routing table we must provide kea with the required capability.
50       systemd.services.kea-dhcp6-server.serviceConfig = {
51         AmbientCapabilities = [ "CAP_NET_ADMIN" ];
52         CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
53       };
55       services = {
56         # Configure the DHCPv6 server to hand out both IA_NA and IA_PD.
57         #
58         # We will hand out /48 prefixes from the subnet 2001:DB8:F000::/36.
59         # That gives us ~8k prefixes. That should be enough for this test.
60         #
61         # Since (usually) you will not receive a prefix with the router
62         # advertisements we also hand out /128 leases from the range
63         # 2001:DB8:0000:0000:FFFF::/112.
64         kea.dhcp6 = {
65           enable = true;
66           settings = {
67             interfaces-config.interfaces = [ "eth1" ];
68             subnet6 = [ {
69               interface = "eth1";
70               subnet = "2001:DB8:F::/36";
71               pd-pools = [ {
72                 prefix = "2001:DB8:F::";
73                 prefix-len = 36;
74                 delegated-len = 48;
75               } ];
76               pools = [ {
77                 pool = "2001:DB8:0000:0000:FFFF::-2001:DB8:0000:0000:FFFF::FFFF";
78               } ];
79             } ];
81             # This is the glue between Kea and the Kernel FIB. DHCPv6
82             # rightfully has no concept of setting up a route in your
83             # FIB. This step really depends on your setup.
84             #
85             # In a production environment your DHCPv6 server is likely
86             # not the router. You might want to consider BGP, NETCONF
87             # calls, … in those cases.
88             #
89             # In this example we use the run script hook, that lets use
90             # execute anything and passes information via the environment.
91             # https://kea.readthedocs.io/en/kea-2.2.0/arm/hooks.html#run-script-run-script-support-for-external-hook-scripts
92             hooks-libraries = [ {
93               library = "${pkgs.kea}/lib/kea/hooks/libdhcp_run_script.so";
94               parameters = {
95                 name = pkgs.writeShellScript "kea-run-hooks" ''
96                   export PATH="${lib.makeBinPath (with pkgs; [ coreutils iproute2 ])}"
98                   set -euxo pipefail
100                   leases6_committed() {
101                     for i in $(seq $LEASES6_SIZE); do
102                       idx=$((i-1))
103                       prefix_var="LEASES6_AT''${idx}_ADDRESS"
104                       plen_var="LEASES6_AT''${idx}_PREFIX_LEN"
106                       ip -6 route replace ''${!prefix_var}/''${!plen_var} via $QUERY6_REMOTE_ADDR dev $QUERY6_IFACE_NAME
107                     done
108                   }
110                   unknown_handler() {
111                     echo "Unhandled function call ''${*}"
112                     exit 123
113                   }
115                   case "$1" in
116                       "leases6_committed")
117                           leases6_committed
118                           ;;
119                       *)
120                           unknown_handler "''${@}"
121                           ;;
122                   esac
123                 '';
124                 sync = false;
125               };
126             } ];
127           };
128         };
130         # Finally we have to set up the router advertisements. While we could be
131         # using networkd or bird for this task `radvd` is probably the most
132         # venerable of them all. It was made explicitly for this purpose and
133         # the configuration is much more straightforward than what networkd
134         # requires.
135         # As outlined above we will have to set the `Managed` flag as otherwise
136         # the clients will not know if they should do DHCPv6. (Some do
137         # anyway/always)
138         radvd = {
139           enable = true;
140           config = ''
141             interface eth1 {
142               AdvSendAdvert on;
143               AdvManagedFlag on;
144               AdvOtherConfigFlag off; # we don't really have DNS or NTP or anything like that to distribute
145               prefix ::/64 {
146                 AdvOnLink on;
147                 AdvAutonomous on;
148               };
149             };
150           '';
151         };
153       };
154     };
156     # This will be our (residential) router that receives the IPv6 prefix (IA_PD)
157     # and /128 (IA_NA) allocation.
158     #
159     # Here we will actually start using networkd.
160     router = {
161       virtualisation.vlans = [ 1 2 ];
162       systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
164       boot.kernel.sysctl = {
165         # we want to forward packets from the ISP to the client and back.
166         "net.ipv6.conf.all.forwarding" = 1;
167       };
169       networking = {
170         useNetworkd = true;
171         useDHCP = false;
172         # Consider enabling this in production and generating firewall rules
173         # for fowarding/input from the configured interfaces so you do not have
174         # to manage multiple places
175         firewall.enable = false;
176       };
178       systemd.network = {
179         networks = {
180           # systemd-networkd will load the first network unit file
181           # that matches, ordered lexiographically by filename.
182           # /etc/systemd/network/{40-eth1,99-main}.network already
183           # exists. This network unit must be loaded for the test,
184           # however, hence why this network is named such.
186           # Configuration of the interface to the ISP.
187           # We must request accept RAs and request the PD prefix.
188           "01-eth1" = {
189             name = "eth1";
190             networkConfig = {
191               Description = "ISP interface";
192               IPv6AcceptRA = true;
193               #DHCP = false; # no need for legacy IP
194             };
195             linkConfig = {
196               # We care about this interface when talking about being "online".
197               # If this interface is in the `routable` state we can reach
198               # others and they should be able to reach us.
199               RequiredForOnline = "routable";
200             };
201             # This configures the DHCPv6 client part towards the ISPs DHCPv6 server.
202             dhcpV6Config = {
203               # We have to include a request for a prefix in our DHCPv6 client
204               # request packets.
205               # Otherwise the upstream DHCPv6 server wouldn't know if we want a
206               # prefix or not.  Note: On some installation it makes sense to
207               # always force that option on the DHPCv6 server since there are
208               # certain CPEs that are just not setting this field but happily
209               # accept the delegated prefix.
210               PrefixDelegationHint  = "::/48";
211             };
212             ipv6SendRAConfig = {
213               # Let networkd know that we would very much like to use DHCPv6
214               # to obtain the "managed" information. Not sure why they can't
215               # just take that from the upstream RAs.
216               Managed = true;
217             };
218           };
220           # Interface to the client. Here we should redistribute a /64 from
221           # the prefix we received from the ISP.
222           "01-eth2" = {
223             name = "eth2";
224             networkConfig = {
225               Description = "Client interface";
226               # The client shouldn't be allowed to send us RAs, that would be weird.
227               IPv6AcceptRA = false;
229               # Delegate prefixes from the DHCPv6 PD pool.
230               DHCPPrefixDelegation = true;
231               IPv6SendRA = true;
232             };
234             # In a production environment you should consider setting these as well:
235             # ipv6SendRAConfig = {
236               #EmitDNS = true;
237               #EmitDomains = true;
238               #DNS= = "fe80::1"; # or whatever "well known" IP your router will have on the inside.
239             # };
241             # This adds a "random" ULA prefix to the interface that is being
242             # advertised to the clients.
243             # Not used in this test.
244             # ipv6Prefixes = [
245             #   {
246             #     ipv6PrefixConfig = {
247             #       AddressAutoconfiguration = true;
248             #       PreferredLifetimeSec = 1800;
249             #       ValidLifetimeSec = 1800;
250             #     };
251             #   }
252             # ];
253           };
255           # finally we are going to add a static IPv6 unique local address to
256           # the "lo" interface.  This will serve as ICMPv6 echo target to
257           # verify connectivity from the client to the router.
258           "01-lo" = {
259             name = "lo";
260             addresses = [
261               { addressConfig.Address = "FD42::1/128"; }
262             ];
263           };
264         };
265       };
267       # make the network-online target a requirement, we wait for it in our test script
268       systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
269     };
271     # This is the client behind the router. We should be receving router
272     # advertisements for both the ULA and the delegated prefix.
273     # All we have to do is boot with the default (networkd) configuration.
274     client = {
275       virtualisation.vlans = [ 2 ];
276       systemd.services.systemd-networkd.environment.SYSTEMD_LOG_LEVEL = "debug";
277       networking = {
278         useNetworkd = true;
279         useDHCP = false;
280       };
282       # make the network-online target a requirement, we wait for it in our test script
283       systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
284     };
285   };
287   testScript = ''
288     # First start the router and wait for it it reach a state where we are
289     # certain networkd is up and it is able to send out RAs
290     router.start()
291     router.wait_for_unit("systemd-networkd.service")
293     # After that we can boot the client and wait for the network online target.
294     # Since we only care about IPv6 that should not involve waiting for legacy
295     # IP leases.
296     client.start()
297     client.wait_for_unit("network-online.target")
299     # the static address on the router should not be reachable
300     client.wait_until_succeeds("ping -6 -c 1 FD42::1")
302     # the global IP of the ISP router should still not be a reachable
303     router.fail("ping -6 -c 1 2001:DB8::1")
305     # Once we have internal connectivity boot up the ISP
306     isp.start()
308     # Since for the ISP "being online" should have no real meaning we just
309     # wait for the target where all the units have been started.
310     # It probably still takes a few more seconds for all the RA timers to be
311     # fired etc..
312     isp.wait_for_unit("multi-user.target")
314     # wait until the uplink interface has a good status
315     router.wait_for_unit("network-online.target")
316     router.wait_until_succeeds("ping -6 -c1 2001:DB8::1")
318     # shortly after that the client should have received it's global IPv6
319     # address and thus be able to ping the ISP
320     client.wait_until_succeeds("ping -6 -c1 2001:DB8::1")
322     # verify that we got a globally scoped address in eth1 from the
323     # documentation prefix
324     ip_output = client.succeed("ip --json -6 address show dev eth1")
326     import json
328     ip_json = json.loads(ip_output)[0]
329     assert any(
330         addr["local"].upper().startswith("2001:DB8:")
331         for addr in ip_json["addr_info"]
332         if addr["scope"] == "global"
333     )
334   '';