This post is an attempt to explain what are the challenges and magic needed for a regular DIY IPv6 router at home. Most ISPs will require you to use DHCPv6 Prefix Delegation and this poses some challenges. I will use my own ISP Ziggo and systemd-networkd as the tool here as an example.
My ISP at home is Ziggo in The Netherlands (part of VodafoneZiggo / Liberty Global). The situation on their IPv6 roll-out could be qualified as messy at best, in my opinion. Many other ISPs follow a similar IPv6 deployment strategy here, though. My situation is as follows:
- Full Dual-Stack (IPv4 + IPv6) connection.
The post should also apply for DS-Lite connections, which I used to have.
After having spent some serious effort back in 2019 convincing the customer service to remove the IPv4-only flag on my account, I was able to get on the IPv6-internet too. 🌍🤓
- Using the ISP-provided modem/router (ConnectBox, Compal variant).
From now on, I'll call this a CPE (customer-premises equipment).
- My own router (a Linux server) connected on a LAN-port of the CPE.
While the connection may be Dual-Stack, Ziggo does not offer a configuration to get Dual-Stack to your own router, sadly. The CPE can be configured in a bridge setup to get the public IPv4 address on your own router, Ziggo will then disable IPv6. 🤷 The sole option for native IPv6 on your own router is to wire it up behind the CPE with double-NAT (stacked router), like this:
INTERNET . . ┌───────┐ ┌─────────────┐ . │ ZIGGO │ eth0│ │ . │ ╞LAN1───────────────────╡ my │ .. │ CPE ╞LAN2 │ Linux │ ......│ ╞LAN3 │ server │ COAX│ ╞LAN4 eth1│ & │ │ │ ┌───────╡ router │ │ "Con- │ │ │ │ │ nect- │ │ └─────────────┘ │ Box" │ │ └───────┘ │ │ │ │ │ HOME NETWORK ─────────────────┴───────────────── . . . . . . ┌─────┐ . . │ │ . . │ │ . . └─────┘ ┌──────┐ . │ │ ┌──────┐ │ │ │ │ │ │ │ │ └──────┘ │ │ └──────┘
There's literally zero documentation, specification or support on the level of IPv6 service provided by Ziggo at the time of writing. Every question asked to support regarding IPv6 is simply directed to their 'online community' (discussion forum). 🙄 Most content in this post is based on observations by other customers and 'common' industry standards.
It appears Ziggo provisions a /56 IPv6 prefix to your CPE (undocumented), of which the first /64 is used for devices on the home LAN. This first /64 prefix is configured ready out-of-the-box for devices and uses stateless address autoconfiguration (SLAAC). The rest of the address space can be delegated to your router using DHCPv6 Prefix Delegation. Unfortunately, this requirement by Ziggo results in dynamic (global) prefixes in your home network and complicates things, hence, this blog post.
Actually, all major service providers in The Netherlands use DHCPv6-PD on residential connections. So, the majority of the post also applies to connections with ISPs like KPN or even premium operators like XS4ALL and Freedom Internet.
What needs to happen, basically, is the negotiation between your own router and the CPE to get your prefix delegated. This is a DHCPv6 client sending a request with an option set for prefix delegation (IA_PD; Identity Association for Prefix Delegation, number 25). Typically, the CPE just relays the DHCP request to the ISP server, and it replies with a prefix together with a lifetime, and then it relays it back to you. It's kind of similar to traditional DHCPv4 in terms of assigning you some address for a certain amount of time.
Note that DHCPv6 can be used in both a stateless and stateful way in IPv6 deployments and these should not be confused. Older devices that perform stateless address autoconfiguration without support for extensions to configure DNS servers for example, will then use stateless DHCPv6 to obtain this "other information". In order to use DHCPv6-Prefix Delegation (that includes a lease time for the prefix(es) delegated), it does make sense that a stateful request – a solicit message in DHCP-speak – is required.
Another important thing to get right is the local routing of any delegated prefix, in both directions. As this IPv6-deployment does not include any routing protocols, things tend to be implicit and easily omitted. After completing the delegation 'dance' the ingress routing part is done automatically; packets with the delegated prefix as destination should reach the host having requested it (based on its link-local address). So that part should be handled as magic by the CPE. You may need to disable the IPv6 firewall of the CPE in its web configuration, though, before you will observe the traffic on your router's interface.
Be sure to understand what are the consequenses of having the IPv6 firewall on your CPE disabled. All devices connected to it will be directly exposed to the internet, as well as devices connected to your own router if you don't run a firewall there.
The second part about routing is to have a "return route" back to your CPE. Coming from an IPv4-world of things where DHCPv4 would hand you a default route, I fell for this caveat big time: DHCPv6-PD does not cover handing the requester a return route (gateway) for the prefix(es) delegated. It appears common to just (re-)use the default route obtained for the first /64 global prefix via SLAAC. Setting up SLAAC next to the prefix delegation seems redundant, though. Technically it could have been sufficient to not configure any global address for your router on the CPE-LAN (in that single /64). But oh well, there's no harm in it, and it does the job: providing me a default route that works.
What's most tricky, is the step after having a (new) prefix assigned: it needs to be configured on all your 'downstream' interfaces. That means it has to 'follow' whatever comes from the service provider and start advertising a /64 from it, for each downstream interface it is operating as a router for. It has to make sure the lifetimes advertised have to match the lease time, but also, on a prefix change, it has to send unsolicited Router Advertisements to invalidate the prefix advertised earlier. How is one going to deal with that? 🤔
To do all this people share all kinds of complex setups with scripts parsing the response of the DHCPv6 client to assign the address to the downstream interfaces. One of these examples is this on the Debian wiki page IPv6PrefixDelegation. Yuck. Then you may have to do some kung-fu with radvd to have it pick up any new global prefix and correct lifetime (and yes this had an error now fixed on master branch still unreleased at the time of writing, sigh!).
Commercially available routers for (smaller) enterprises have all the convenience functionality built-in. It comes down to configuration of a dynamic pool of prefixes you can refer to in the configuration of 'downstream' interfaces. However, I'm just using a Linux PC as router here – a more elegant way to handle this in software should exist, right?
In order to get things running smooth and ready for dynamic changes on my router, I chose systemd-networkd to operate this. Why systemd-networkd? It has all the functionality built-in for such a situation. With only a single tool to configure...:
- systemd-networkd can request prefixes to be delegated using DHCPv6-PD. ☑
- systemd-networkd will maintain a local pool of prefixes it was delegated. 💾
- systemd-networkd can assign prefixes (/64) from this pool to networks it manages dynamically. 🤓
- systemd-networkd will also 'announce' the current up-to-date routable prefix assigned via Router Advertisements to hosts in a 'downstream' network. 😍
Just perfect! 😎
.network file for the connection to the CPE, comments inline:
[Match] Name=eth0 [Network] Description=Link to Ziggo CPE LAN port # We only need a link-local address for IPv6 (required to run IPv6), # but not for IPv4 (using DHCP). LinkLocalAddressing=ipv6 # SLAAC IPv6 for obtaining the default route. # This is needed, because the DHCPv6 response does not include an # address with a gateway for the prefixes. We're supposed to use # the SLAAC-announced default route one as the 'return route'. IPv6AcceptRA=yes # Boolean true enables both DHCPv4 and DHCPv6 client. See also the # IPv6AcceptRA.DHCPv6Client setting. DHCP=yes [DHCPv4] # Hostname sent in the DHCPv4 solicit (request). I like to set this to # some bogus name. 😅 Hostname=myhostname # Ignore the hostname to set in the reply. # I don't want anything to control my server's hostname! 🤨 UseHostname=no # I handle all this myself, not using Ziggo's DNS/NTP servers # mentioned in the reply. # The only thing I need for IPv4 to work is an address and a gateway. UseDNS=no UseNTP=no UseSIP=no UseRoutes=no UseGateway=yes [IPv6AcceptRA] # Similar for IPv6 via Router Advertisements; I'll handle DNS myself, # please. UseDNS=no # Force starting the DHCPv6 client even if the Router Advertisement # indicates it's not required. DHCPv6Client=yes [DHCPv6] # The Ziggo CPE does not advertise DHCPv6 stateful mode availability # in the Router Advertisements. Without this override, the DHCPv6 # solicit (request) won't result in a reply with a prefix (IA_PD). ForceDHCPv6PDOtherInformation=yes # Similar as for DHCPv4, I dislike systemd-networkd to use any other # information in the reply. UseHostname=no UseDNS=no UseNTP=no
.network file for my client network (leaving the IPv4 DHCPv4 server out of scope here, I may move to
systemd-networkd for that as well):
[Match] Name=eth1 [Network] Description=Link to the LAN for my clients LinkLocalAddressing=ipv6 IPv6AcceptRA=no # Announce a prefix here and act as a router. IPv6SendRA=yes # Use a DHCPv6-PD delegated prefix (DHCPv6PrefixDelegation.SubnetId) # from the pool and assigns one /64 to this network. DHCPv6PrefixDelegation=yes [Address] # Simple static IPv4 configuration. Address=10.3.2.1/24 [IPv6SendRA] # Currently my DHCPv4 server configures a DNS server already. EmitDNS=no EmitDomains=no [DHCPv6PrefixDelegation] # This assigns the second prefix from the pool. SubnetId=0x1
With this configuration in place, restart systemd-networkd to activate it:
root@myrouter:~# systemctl restart systemd-networkd
Now, let's have a look at the logs:
root@myrouter:~# journalctl -u systemd-networkd.service [...] systemd-networkd: eth0: DHCP6: received PD Prefix 2001:1c00:1234:ff50::/60 systemd-networkd: eth1: DHCPv6-PD address 2001:1c00:1234:ff51:36e6:d7ff:fe1b:48fe/64 (valid for 16h, preferred for 8h)
The actual addresses have been obfuscated.
It appears that the CPE or the ISP DHCP server has decided to delegate a /60 to me (for 16 /64-subnets). A more visual view on what I've got:
$ sipcalc 2001:1c00:1234:ff50::/60 [...] Network range - 2001:1c00:1234:ff50:0000:0000:0000:0000 - 2001:1c00:1234:ff5f:ffff:ffff:ffff:ffff
Then systemd-networkd successfully assigned the second subnet (ID 1,
ff51) to my eth1 interface and the hosts in this
network picked up global addresses from this prefix! 🥳
root@myrouter:~# ip addr show eth0 1: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether [...] inet 192.168.178.115/24 metric 1024 brd 192.168.178.255 scope global dynamic eth0 valid_lft 2509sec preferred_lft 2509sec inet6 2001:1c00:1234:ff00:629d:30c1:4adc:57e6/64 scope global dynamic mngtmpaddr noprefixroute valid_lft 45277sec preferred_lft 16477sec inet6 fe80::629d:30c1:4adc:57e6/64 scope link valid_lft forever preferred_lft forever root@myrouter:~# ip addr show eth1 2: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether [...] inet 10.50.0.1/24 brd 10.50.0.255 scope global eth1 valid_lft forever preferred_lft forever inet6 2001:1c00:1234:ff51:629d:30c1:4adc:57e6/64 scope global dynamic mngtmpaddr valid_lft 45882sec preferred_lft 17082sec inet6 fe80::629d:30c1:4adc:57e6/64 scope link valid_lft forever preferred_lft forever root@myrouter:~# ip -6 route ::1 dev lo proto kernel metric 256 pref medium 2001:1c00:1234:ff00::/64 dev eth0 proto ra metric 1024 expires 44796sec pref medium 2001:1c00:1234:ff00::/64 via fe80::7ffb:b602:2423:a1a6 dev eth0 proto ra metric 1024 expires 57527sec pref medium 2001:1c00:1234:ff51::/64 dev eth1 proto kernel metric 256 expires 45401sec pref medium 2001:1c00:1234:ff51::/64 dev eth1 proto dhcp metric 1024 pref medium unreachable 2001:1c00:1234:ff50::/60 dev lo proto dhcp metric 1024 pref medium fe80::/64 dev eth1 proto kernel metric 256 pref medium fe80::/64 dev eth0 proto kernel metric 256 pref medium default via fe80::7ffb:b602:2423:a1a6 dev eth0 proto ra metric 1024 expires 1727sec mtu 1500 pref high
I'm now enjoying full Dual Stack future-proof modern internet! 🥳
Just a /60 is a smaller space (longer prefix) than I expected, but okay. 👍 Quite possibly, Ziggo does this on purpose to leave room for more routers receiving different /60 prefixes delegated from the /56 pool available per subscription. It would be a bit of a challenge in case you need more than 16 subnets, though. Others have reported different prefix sizes with Ziggo – told you; it's messy. Either way, just a /60 for your own router is very greedy given the IPv6 standards and the recommendation by RIPE to hand everyone a /48.
I've been quite happy to just use a single tool – systemd-networkd – for this complex job. Also, I like the fact of having a declarative way to configure my network. With just a few files and a service restart it is able to bring the network up in the desired state and keep it that way.
To me it seems DHCPv6-PD is more of a convenience to the ISP's IPv6 deployment than it is for end-users to configure on their own routers. Ideally, I would have liked to be able to choose an ISP that statically routes the whole prefix to my connection over plain Ethernet and a link-local address, or SLAAC. None of them seem to do so here. If any of you have more thoughts about why, let me know, I'd like to learn about their reasoning.
While my setup with Ziggo should be similar to many other configurations with DHCPv6-PD involved (aside differently sized prefixes), there's one complicating factor for many other: PPPoE. Many residential connections on DSL and FTTH in The Netherlands are connected via access brokers using PPP.
The bad news here: systemd-networkd does not support PPP/PPPoE natively, yet (see RFE #481). 😢