DHCPv6-PD - First steps

Prologue

The single most requested feature missing in OpenBSD base directed at me is DHCPv6-PD. Recently I got a working setup at home using dhcpcd from ports and a donated Fritz!Box 6660 Cable1, 2. Time to hack on this.

DHCPv6-PD

DHCPv6 is not wildly deployed outside of enterprise networks3. DHCP for Prefix Delegation (DHCPv6-PD) on the other hand is the standard to get IPv6 prefixes into home networks. RFC 8415 has this:

It is appropriate for situations in which the delegating router (1) does not have knowledge about the topology of the networks to which the requesting router is attached and (2) does not require other information aside from the identity of the requesting router to choose a prefix for delegation. This mechanism is appropriate for use by an ISP to delegate a prefix to a subscriber, where the delegated prefix would possibly be subnetted and assigned to the links within the subscriber's network.

Transmogrifying dhcpleased(8)

This not being my first rodeo, it took me about 4 hours over a weekend to transmogrify dhcpleased(8) into dhcp6leased(8) and have it talk to my Fritz!Box. I have also setup ISC's Kea DHCP server for easier development and to not risk my production network at home. Of course it is not yet able to configure the system, but it can request a prefix delegation from the server and parse the response. This is enough to play with the protocol and work on the grammar for the configuration file.

Describing network topology

dhcp6leased(8) will not just request an IPv6 prefix delegation but also use the delegated prefix to assign prefixes to downstream network interfaces. rad(8) can then be used to send router advertisements for clients to get IPv6 connectivity on different subnets in the home network.

The typical use case is probably to have a few networks connected to the OpenBSD router using vlans4 and assign /64 prefixes to each one of them.

A more advanced use case would be to assign prefixes of different lengths to the vlan interfaces. For example I have a whole (virtual) network lab hanging off of an OpenBSD router which is not a single flat network. I need to assign a /60 to that interface5 to have enough space to subnet further.

Now, DHCPv6-PD allows us to request multiple prefixes. We could just punt the problem of splitting a bigger prefix into smaller prefixes to the DHCPv6 server. However, the RFC has this:

In principle, DHCP allows a client to request new prefixes to be delegated by sending additional IA_​PD options (see Section 21.21). However, a typical operator usually prefers to delegate a single, larger prefix. In most deployments, it is recommended that the client request a larger prefix in its initial transmissions rather than request additional prefixes later on.

And indeed, the Fritz!Box only gives us one prefix. We can hand the prefix back and request a larger one, but it will only honour a single IA_PD option in a solicit message.

This means we have to split up the prefix ourselves. This is perfectly simple if we are only dealing with /64 networks. Just count the networks, round up to the nearest power of two and calculate the required prefix size from that.

This gets more complicated if the prefix lengths for our sub-networks are non-uniform, like in the more advanced use case.

I went a bit on a tangent and tried to solve this for the general case. That means arbitrary subnet sizes and an optimal packing in the delegated prefix. I think that would come down to the Bin packing problem which is… annoying6.

I then noticed that we want a stable assignment, meaning when we add or remove an interface we do not want to renumber all the existing and remaining interfaces. Which would happen if try to come up with an optimal solution because prefix assignments would most likely shift around every time we change something.

dhcpcd's solution

At this point I was somewhat stuck and I had a look at how dhcpcd deals with this. While I was already using dhcpcd in my network, I had not yet setup the more advanced use case with a /60 and multiple /64. I was pretty sure that dhcpcd can handle this, but I did not yet know how.

Disclaimer: What follows are my notes on how I got it to work. It is likely that I am doing things wrong and misunderstand some parts. Unfortunately I no longer have access to GitHub7, so I cannot open an issue with the project to ask for help with this. I am very sorry.

Here is the relevant part from the dhcpcd.conf man page:

ia_pd [iaid [/ prefix / prefix_len] [interface [/ sla_id [/ prefix_len
        [/ suffix]]]]]
        Request a  DHCPv6 Delegated Prefix for iaid.  This option must
        be used in an interface block.  Unless a sla_id of  0  is  as-
        signed with the same resultant prefix length as the delegation,
        a  reject  route  is installed for the Delegated Prefix to stop
        unallocated addresses being resolved upstream.  If no interface
        is given then we will assign a prefix to every other  interface
        with a sla_id equivalent to the interface index assigned by the
        OS.   Otherwise addresses are only assigned for each interface
        and sla_id.  To avoid delegating to any interface, use - as the
        invalid interface name. Each  assigned address  will  have  a
        suffix, defaulting  to 1.  If the suffix is 0 then a SLAAC ad-
        dress is assigned.  You cannot assign a prefix to the  request-
        ing  interface  unless  the DHCPv6 server supports the RFC 6603
        Prefix Exclude Option.  dhcpcd has to be running  for  all  the
        interfaces  it is delegating to.  A default prefix_len of 64 is
        assumed, unless the maximum sla_id does not fit.  In this  case
        prefix_len  is  increased to the highest multiple of 8 that can
        accommodate the sla_id. sla_id is an  integer  which  must  be
        unique  inside  the  iaid and is added to the prefix which must
        fit inside prefix_len less the length of the delegated  prefix.
        You  can  specify  multiple interface / sla_id / prefix_len per
        ia_pd, space separated. IPv6RS should be  disabled  globally
        when requesting a Prefix Delegation.

I kinda do not know what all of this means.

After much experimentation I ended up with this working-ish configuration:

ia_pd 2/::/59 vether0/0/60 vether1/1/64

which put this in daemon.log:

vio1: delegated prefix 2001:db8:3::/56
vether0: adding address 2001:db8:3::1/60
vether1: adding address 2001:db8:3:1::1/64

A closer look shows that the two prefixes overlap though:

>>> import ipaddress
>>> a = ipaddress.ip_network('2001:db8:3::/60')
>>> b = ipaddress.ip_network('2001:db8:3:1::/64')
>>> a.overlaps(b)
True

This configuration produces non-overlapping prefix assignments:

ia_pd 2/::/59 vether0/0/60 vether1/16/64
vio1: delegated prefix 2001:db8:3::/56
vether0: adding address 2001:db8:3::1/60
vether1: adding address 2001:db8:3:10::1/64

Taking this apart, token by token:

ia_pd
This is just the keyword to request a prefix delegation.
2/::/59
2 is a unique request ID needed by the DHCPv6 protocol. :: is the unspecified prefix and 59 is the requested prefix length. Since the DHCPv6 server does not have an address pool for /59 it hands out a prefix for the next larger prefix for which it does have a pool, /56 in this case.
vether0/0/60
This assigns the 1st (index 0) /60 prefix to vether0.
vether1/16/64
This assigns the 17th8 (index 16) /64 prefix to vether1.

What I misunderstood when I used vether1/1/64 was that sla_id (the 1 in the middle) does not mean use the next free /64 but use the 2nd /64 in the delegated prefix.

I find this confusing because the way I think about subnetting is that the different prefixes do not stand alone. 2001:db8:3:10::/64 is not the 17th /64 prefix in 2001:db8:3::/56 but the first /64 in the 2nd /60. It's a hierarchy.

Next steps

dhcpcd puts a lot of work on the administrator to get the subnet assignments just right. It neatly avoids9 the problems I had identified. The assignments are stable and the algorithm is not massively expensive.

This got me unstuck and I have an idea how dhcp6leased(8) should be configured.

  1. It should work out automatically the size of the prefix it requests. I was under the impression that dhcpcd would also do that, but it did not work. Probably my mistake somewhere.
  2. Assignments are listed in order and dhcpleased(8) will work out the boundaries.

I am not sure about the exact syntax, but as an example, consider this:

request prefix delegation on vio0 for {
    vether0/60
    reserve/60
    vether1/64
    vether2/64
    vether3/60
}

It would request a /58 which fits 4 /60. We assign the first /60 to vether0, keep the next /60 in reserve in case we want to add interfaces between vether0 and vether1 in the future without triggering a renumber. We then pick the first /64 out of the third /60 and assign it to vether1. We still have space in the third /60 to assign a /64 to vether2. We pick the fourth and last /60 and assign it to vether3:

vether0 2001:db8:3::/60
reserve 2001:db8:3:10::/60
vether1 2001:db8:3:20::/64
vether2 2001:db8:3:21::/64
vether3 2001:db8:3:30::/60

I think I have code that can do this and it is not overly complicated. It can currently only handle the upper 64 bits of an IPv6 address because it does math on uint64_t. I will try to extend it to the lower half so that we can assign something like /96 to a link, even if that means that half the IPv6 Maintenance (6man) IETF working group will hunt me down.

Epilogue

This strikes a slightly better balance between work that needs to be done by the administrator and help the tool provides compared to what dhcpcd implements. But I would not have come up with this without prior work by dhcpcd, kudos to Roy.

Coming up with an addressing plan is still hard work, so I will implement a feature in dhcp6leased to have it output the addressing plan it worked out as a configuration check before going to work. Because renumbering is hard.

Footnotes:

1

Thanks again Mischa & Ibsen!

2

The CPE from my ISP was just too broken to work with, let alone develop against. It only ever hands out a single prefix and would need a factory reset afterwards. It also does not route the delegated prefix but depends on ND Proxy.

3

Because of android refusing to implement it.

4

Maybe one vlan for WiFi, one for IOT and one for guest WiFi.

5

The IETF never managed to fully standardize this. I hear this is where homenet failed. A less ambitious working group is working in this problem space now: Stub Network Auto Configuration for IPv6 (snac). But they only want to deal with flat networks.

6

Otherwise known as NP-hard.

7

An alternative reading is: I refuse to use it because they decided I am a suplier, which I am not. And they locked me out of my account.

8

We are starting to count at 0.

9

You could say it punts them to the administrator.

Published: 2024-05-26