Using DHCPv6-PD on Ubuntu 22.04 Jaunty with systemd-networkd to route multiple prefixes

  • By nygren
  • Sat 12 August 2023

This provides an overview of how you can use the systemd-networkd service in Ubuntu 22.04 to obtain an IPv6 DHCPv6-PD prefix (eg, a /56) from an upstream ISP and subdivide it across local subnets, providing a /64 per subnet.

Note that this post assumes you already have extensive experience with Linux and IPv6. Most people are much better off using a distribution such as OpenWRT that handles all of this complexity for you. If you want to know why IPv6 matters, check out Akamai's blog post on "10 Years Since World IPv6 Launch".

Overview

The systemd-networkd service in Ubuntu 22.04 has enough support for DHCPv6-PD to obtain a prefix from an upstream ISP and subdivide it across local subnets, providing a /64 per subnet. For example, Comcast provides a /60 which has enough space for 16 subnets.

This includes substantial improvements since Ubuntu 20.04 (which I wrote about previously although the syntax for some of the configuration has changed. This is an updated version and assumes Ubuntu 22.04 Jaunty (or equivalent).

Because netplan doesn't have enough features to configure DHCPv6-PD yet (see https://bugs.launchpad.net/netplan/+bug/1771886), some of the configuration needs to live in systemd-networkd overrides.

Configuring the Interfaces

I am assuming starting with a netplan configuration like the following:

network:

  ethernets:

    eno1:    # ISP1
      dhcp4: true
      dhcp6: true
      accept-ra: true
      ipv6-privacy: false
      nameservers:
        search: [ example.org ]
      dhcp4-overrides:
        use-dns: false
        use-hostname: false
        use-ntp: false
      dhcp6-overrides:
        use-dns: false
        use-hostname: false
        use-ntp: false

    eno2:    # LAN
      dhcp4: false
      optional: true

  version: 2

  vlans:

    en-main:
      id: 6
      link: eno2
      dhcp4: no
      addresses:
        - 10.1.0.0/24
      optional: true

    en-guest:
      id: 7
      link: eno2
      dhcp4: no
      addresses:
        - 10.2.0.0/24
      optional: true

This has interfaces:

  • eno1 = WAN
  • eno2 = Primary LAN
  • en-main = Main VLAN
  • en-guest = Guest VLAN

While netplan and systemd-networkd somehow collaborate to put this netplan config into files such as /run/systemd/network/10-netplan-eno1.network, there is no way to get the rules needed for enabling DHCPv6-PD into those dynamically generated files in /run.

Thankfully, you can put overrides in that systemd-networkd will read. See the systemd.network(5) man page for more details on all of the options available for these files.

To configure a DHCPv6-PD client on the WAN interface (eno1 in my case), create /etc/systemd/network/10-netplan-eno1.network.d/override.conf :

[Match]
Name=eno1

[DHCPv6]
PrefixDelegationHint=::/56

You can specify a smaller or larger value for PrefixDelegationHint if needed (eg, /60 for Comcast Xfinity Residential).

For each of the LAN interfaces, create files such as /etc/systemd/network/10-netplan-en-guest.network.d/override.conf and /etc/systemd/network/10-netplan-en-main.network.d/override.conf with the following. Note that the filename prefixes must match those in /run/systemd/network/ (ie, don't change the "10-").

[Match]
Name=en-main

[Network]
IPv6PrefixDelegation=dhcpv6
IPv6DuplicateAddressDetection=1
LinkLocalAddressing=ipv6

[DHCPv6PrefixDelegation]
## Must be unique per subnet
SubnetId=6

The "SubnetId" here must be unique per subnet and is used to construct the address. The allowed values depend on what size prefix was delegated by DHCPv6-PD. If you have a /60, this can be a unique hex value in the range 0-f. If you have a /56, this can be a unique hex vale in the range of 0-ff.

For example, if you have 2001:db8:abcd:3400::/56 and have SubnetId of "7" then the subnet will be assigned the prefix 2001:db8:abcd:3407::/56.

It is critical to have this here as otherwise these will be assigned in different orders each time systemd-networkd restarts. It doesn't keep track of what prefixes it has advertised to links, so hosts on various links/subnets could end up with stale prefix advertisements for the wrong subnets and broken connectivity.

As a variation on the above, the following also: * Assigns a ULA prefix on the subnet which remains up even if you lack external network connectivity. Make sure to replace the "XX:XXXX:XXXX" part of "fdXX:XXXX:XXXX:YYYY::/64" with a sufficiently random value, and then the "YYYY" may wish to be the same as the SubnetId as above (but does not need to be). * Has the router also assign itself an address on the subnet with a token of "::7777", so it would assign itself 2001:db8:abcd:3407::7777.

[Match]
Name=en-main

[Network]
## use both DHCPv6 and static ULA prefixes ("yes")
IPv6PrefixDelegation=yes
IPv6DuplicateAddressDetection=1
LinkLocalAddressing=ipv6

[DHCPv6PrefixDelegation]
## Must be unique per subnet
SubnetId=6
Token=::7777
Assign=true

[IPv6Prefix]
Prefix=fdXX:XXXX:XXXX:YYYY::/64

Note the filename prefixes in /etc/systemd/network/ need to match those in /run/systemd/network/.

For more details on the options and their meanings, see the man page systemd.network(5).

Loading Changes

You can either reboot, or you can reapply netplan and reload systemd-networkd configuration:

netplan apply
networkctl reload

Sometimes it can help to also restart restart systemd-networkd with:

service systemd-networkd restart

Caveats and notes

Some additional notes:

  • The IPv6 address on the WAN link (eg, eno1) comes from an individual RA ND or DHCPv6 assignment, not a DHCPv6-PD prefix.

Testing and Debugging

There's plenty more information than existed in older versions, especially by running networkctl status. This will show the various assigned addresses as well as some recent log entries. For example, you can see which prefix was obtained via DHCPv6-PD and what was assigned to various links:

eno1: DHCPv6 address 2001:db8:501:b9:1111:2222:3333:4444/128 (valid for 3d 40min 1s, preferred for 3d 40min 1s)
eno1: DHCP6: received PD Prefix 2001:db8:1234:950::/60
en-main: DHCPv6-PD address 2001:db8:1234:956::7777/64 (valid for 3d 40min 1s, preferred for 3d 40min 1s)
en-guest: DHCPv6-PD address 2001:db8:1234:957::7777/64 (valid for 3d 40min 1s, preferred for 3d 40min 1s)

Here the DHCPv6-PD prefix of 2001:db8:1234:950::/60 was assigned, and then 2001:db8:1234:956::/64 was assigned out to the link with SubnetId "6".

Some things that may be helpful:

  • ip -6 route list will list the prefixes assigned to the interfaces
  • rdisc6 en-guest will do router discovery on that interface (eg, en-guest in this case) and will show the assigned prefix
  • Doing a tcpdump -s 1500 -vvv -i eno1 port 546 or port 547 and then a systemctl restart systemd-networkd will show the DHCPv6-PD lease including prefix length received from the WAN ISP
  • Doing journalctl -xeu systemd-networkd will give some logging information from systemd-networkd but I have yet to figure out how to get the most relevant parts logged.
  • Doing radvdump will show the RAs showing up on an interface.

More network logging

Doing systemctl edit systemd-networkd to add in:

[Service]
Environment=SYSTEMD_LOG_LEVEL=debug

And restart systemd-networkd service and looking at logs:

systemctl restart systemd-networkd
journalctl -b -u systemd-networkd

Multi-Homing and clearing bad prefixes

I've also started to play more with Primary/Backup failover between two links. Using the approach from RFC8475 - Using Conditional Router Advertisements for Enterprise Multihoming seems to work, although I additionally configure NAT66 for cases where traffic tries to use the wrong source address for the wrong link.

One thing that is useful is to use the ra6 tool from ipv6tools to set the preferred lifetime down to "0" to deprecate a prefix on a link. For example, this sets the preferred lifetime to "0" for an old prefix "2001:db8:5050:553::/64" which had been on a link with interface "en-guest", sending it to the all-hosts multicast (ff02::1):

ra6 -v -i en-guest -e -P 2001:db8:5050:553::/64#AL#7200#0 --lifetime 300 -r 0 -x 0 -c 0 -p -1 -d ff02::1

It is possible not all of these arguments are strictly needed.

You may need to send this periodically (for the valid lifetime that had been set previously) as hosts may be offline or may be rate-limiting RAs.

This can be useful for misconfigurations (eg, if changing SubnetId), cleaning up from botched ISP renumberings, and for primary/backup failovers.

Future Work

It would be nice if systemd-networkd kept track of IPv6 prefixes it had assigned to links so that it could deprecate those if they were no longer in-use (eg, when an ISP does a renumbering), and to also support Conditional RAs for primary/backup failovers between multiple uplinks.

It would also be nice if all of this could be configured via netplan rather than with overrides.

I also plan to setup an IPv6-only subnet using NAT64+DNS64 and will have a follow-on blog post on the details there.

Updates

  • 2023-08-26: removed "ForceDHCPv6PDOtherInformation" which has been deprecated

tags: LinuxIPv6