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 interfacesrdisc6 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 asystemctl 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