DHCPv6-PD - First steps
This commit is contained in:
parent
5275adb20c
commit
f5886f370f
249
dhcpv6-pd-first-steps.org
Normal file
249
dhcpv6-pd-first-steps.org
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
#+TITLE: DHCPv6-PD - First steps
|
||||||
|
#+DATE: 2024-05-26
|
||||||
|
* 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 [[https://roy.marples.name/projects/dhcpcd][dhcpcd]]
|
||||||
|
from ports and a donated Fritz!Box 6660 Cable[fn::Thanks again
|
||||||
|
Mischa!][fn::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 [[https://www.rfc-editor.org/rfc/rfc4389][ND Proxy.]]]. Time to hack on this.
|
||||||
|
* DHCPv6-PD
|
||||||
|
[[https://www.rfc-editor.org/rfc/rfc8415][DHCPv6]] is not wildly deployed outside of enterprise
|
||||||
|
networks[fn::Because of android refusing to implement it.]. DHCP for
|
||||||
|
Prefix Delegation (DHCPv6-PD) on the other hand is *the standard* to
|
||||||
|
get IPv6 prefixes into home networks. [[https://www.rfc-editor.org/rfc/rfc8415#section-6.3][RFC 8415]] has this:
|
||||||
|
#+begin_quote
|
||||||
|
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.
|
||||||
|
#+end_quote
|
||||||
|
* Transmogrifying dhcpleased(8)
|
||||||
|
This not being my first rodeo, it took me about 4 hours over a weekend
|
||||||
|
to transmogrify [[https://man.openbsd.org/dhcpleased.8][=dhcpleased(8)=]] into =dhcp6leased(8)= and have it talk
|
||||||
|
to my Fritz!Box. I have also setup [[https://www.isc.org/kea/][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. [[https://man.openbsd.org/rad.8][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 vlans[fn::Maybe one vlan for WiFi, one for IOT
|
||||||
|
and one for guest WiFi.] 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 interface[fn::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: [[https://datatracker.ietf.org/wg/snac/about/][Stub Network Auto Configuration for IPv6 (snac)]]. But they
|
||||||
|
only want to deal with flat networks.] 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 [[https://www.rfc-editor.org/rfc/rfc8415#section-6.6][RFC has this]]:
|
||||||
|
|
||||||
|
#+begin_quote
|
||||||
|
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.
|
||||||
|
#+end_quote
|
||||||
|
|
||||||
|
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 [[https://en.wikipedia.org/wiki/Bin_packing_problem][Bin packing
|
||||||
|
problem]] which is... annoying[fn::Otherwise known as NP-hard.].
|
||||||
|
|
||||||
|
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 GitHub[fn::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.], 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 [[https://man.freebsd.org/cgi/man.cgi?query=dhcpcd.conf&apropos=0&sektion=0&manpath=FreeBSD+14.0-RELEASE+and+Ports&arch=default&format=html][dhcpcd.conf man page:]]
|
||||||
|
#+begin_example
|
||||||
|
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.
|
||||||
|
#+end_example
|
||||||
|
I kinda do not know what all of this means.
|
||||||
|
|
||||||
|
After much experimentation I ended up with this working-ish
|
||||||
|
configuration:
|
||||||
|
#+begin_example
|
||||||
|
ia_pd 2/::/59 vether0/0/60 vether1/1/64
|
||||||
|
#+end_example
|
||||||
|
which put this in =daemon.log=:
|
||||||
|
#+begin_example
|
||||||
|
vio1: delegated prefix 2001:db8:3::/56
|
||||||
|
vether0: adding address 2001:db8:3::1/60
|
||||||
|
vether1: adding address 2001:db8:3:1::1/64
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
A closer look shows that the two prefixes overlap though:
|
||||||
|
#+begin_src python
|
||||||
|
>>> import ipaddress
|
||||||
|
>>> a = ipaddress.ip_network('2001:db8:3::/60')
|
||||||
|
>>> b = ipaddress.ip_network('2001:db8:3:1::/64')
|
||||||
|
>>> a.overlaps(b)
|
||||||
|
True
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
This configuration produces non-overlapping prefix assignments:
|
||||||
|
#+begin_example
|
||||||
|
ia_pd 2/::/59 vether0/0/60 vether1/16/64
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
vio1: delegated prefix 2001:db8:3::/56
|
||||||
|
vether0: adding address 2001:db8:3::1/60
|
||||||
|
vether1: adding address 2001:db8:3:10::1/64
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
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 17th[fn::We are starting to
|
||||||
|
count at 0.] (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 avoids[fn::You could say it punts
|
||||||
|
them to the administrator.] 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:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
request prefix delegation on vio0 for {
|
||||||
|
vether0/60,
|
||||||
|
reserve/60,
|
||||||
|
vether1/64,
|
||||||
|
vether2/64,
|
||||||
|
vether3/60
|
||||||
|
}
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
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=:
|
||||||
|
|
||||||
|
#+begin_example
|
||||||
|
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
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
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 [[https://datatracker.ietf.org/wg/6man/about/][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.
|
Loading…
Reference in New Issue
Block a user