Thursday, May 9, 2013

ISC DHCPD programming fun: (attempting to) install routes following DHCP-PD operation

For testing, I need to run both the DHCPv6 server and the router on the single box, with the CPEs running behind it doing client DHCP-PD requests.

As such box I am using a Linksys E3200 running TomatoUSB + with optware aiccu in order to flexibly get the globally routable IPv6 chunk, and optware dhcpd to do all the DHCPv6 stuff.

Configuring DHCP-PD on the ISC DHCP was a breeze, but the tricky part is routing the traffic - DHCP-PD implies the prefix is behind a certain CPE, which implies a route... Some searching pointed to - but it seems to be a no-op with IPv6... duh. However, Wim's (@42wim) blog entry at hinted at another approach... 

The result is the below ugly hack, which might be enough for my needs (I won't change the CPEs too often and they are all OpenWRT so the initial 5 minutes delay should be fine; and I do not look at the security aspect -  looks like this is just blindly installing routes based on the info supplied by the client in the REBIND, which in any real-world deployment would be a no-no... 

Also since I derive the link-local address from the mac address that I derive in a way similar to Wim's approach - this is also very very fragile, but again - for my toy setup it is enough. 

The funniest part was the U/L bit flipping while converting the MAC address into the Link-Local IPv6 address. Luckily the ISC DHCP implements just enough logic for it to work. (Though I would have liked a decent programming language there... Maybe ISC folks could just hook Lua inside ? I suspect this might actually decrease the amount of code and with something like e.g. Lua 5.1 the bugs are very rare if at all.)

If someone has a better idea on how to implement this, feel free to write in the comment. My scenario constraints are:

1) the ISC DHCP server is whatever is supplied with tomatousb firmware, I believe 4.1 - I can not recompile or upgrade it to anything newer.

2) I do not have any cross-compiler toolchain for tomatousb

So I am pretty restricted in what can be done.

Anyway,  here goes my dhcp.conf for your enjoyment:

# BIIIG caveat:
# * does not work on initial allocation.
#   looks like we can only study the options sent by the client, 
#   not the options about to be sent by the server, so...
# Some assumptions:
# * DUID has mac address in the end (openwrt does this)
# * only one prefix in PD, and one address in NA. This is a bit buggy
default-lease-time 600;
max-lease-time 7200; 
log-facility local7; 

 # thanks @42Wim! :-)

 option dhcp6.macaddr code 193 = string;
 option dhcp6.leased-address code 194 = string;
 option dhcp6.pkt code 9998 = string;
 option dhcp6.leased-prefix code 9999 = string;
 option dhcp6.leased-prefix-len code 9997 = string;
 option dhcp6.ll-addr code 9996 = string;
 option dhcp6.leased-prefix-cidr code 9995 = string;
 option dhcp6.uli code 6011 = integer 32;
 option dhcp6.ulo code 6010 = integer 32;
 option dhcp6.macaddr = binary-to-ascii(16, 8, ":", 
                                        suffix(option dhcp6.client-id, 6));
 # extract the byte with U/L bit
 option dhcp6.uli = substring(suffix(option dhcp6.client-id, 6), 0, 1);

 # invert the U/L bit by checking the symbol in the binary string 
 # representation and adjusting the result accordingly
 if substring(suffix(binary-to-ascii(2, 8, "",
                                  config-option dhcp6.uli), 2), 0, 1) = "1" {
   # Seems there's no minus so we gotta do subtraction by addition overflow
   option dhcp6.ulo = encode-int(
                         extract-int(config-option dhcp6.uli, 8) + 254, 8);
 } else {
   option dhcp6.ulo = encode-int(
                         extract-int(config-option dhcp6.uli, 8) + 2, 8);
 option dhcp6.ll-addr = concat("fe80::", binary-to-ascii(16,16, ":", 
                                 concat(config-option dhcp6.ulo, 
                                        substring(suffix(option dhcp6.client-id, 6), 1, 2), 
                                        encode-int(255, 8),
                                        encode-int(254, 8),
                                        substring(suffix(option dhcp6.client-id, 6), 3, 3))) );
 option dhcp6.leased-prefix = binary-to-ascii(16,16, ":",
                                suffix(substring(option dhcp6.ia-pd, 12, 100), 16));
 option dhcp6.leased-prefix-len = binary-to-ascii(10,8, ".",
                                    substring(suffix(substring(option dhcp6.ia-pd, 12, 100), 17), 0, 1));
 option dhcp6.leased-prefix-cidr = concat (config-option dhcp6.leased-prefix, "/", 
                                           config-option dhcp6.leased-prefix-len);
 if substring(config-option dhcp6.leased-prefix, 0, 1) = "2" {
   log (info, concat ("Prefix ",config-option dhcp6.leased-prefix-cidr, 
                      " leased to ", config-option dhcp6.macaddr, 
                      " via ", config-option dhcp6.ll-addr));
   execute("/usr/sbin/ip", "-6", "route", "del", config-option dhcp6.leased-prefix-cidr);
   execute("/usr/sbin/ip", "-6", "route", "add", config-option dhcp6.leased-prefix-cidr, 
                                                 "via", config-option dhcp6.ll-addr, "dev", "br0");
 } else {
   log (info, "No prefix leased in this packet or not REBIND");
# pretty standard stuff.

subnet6 2001:6f8:147e::/64 {
        # Range for clients
        range6 2001:6f8:147e::1000 2001:6f8:147e::ffff;
        # Additional options
        option 2001:6f8:147e::1;
        option dhcp6.domain-search "domain.example";

        # Prefix range for delegation to sub-routers
        prefix6 2001:6f8:147e:1100:: 2001:6f8:147e:1f00::  /56;


Ernie Johnston said...

I did not know what DHCP-PD (Prefix Delegation) meant and I suspect others don't as well. Goggling it reveals: extension specified in RFC3633. … aimed at assigning complete subnets and other network and interface parameters … instead of a single address assignment, DHCPv6-PD will assign a set of IPv6 "subnets".

More at:

Andrew Yourtchenko said...

Thanks a lot for posting the link, indeed!

The idea behind using the DHCP-PD is that since the CPE does not do the NAT with IPv6 - it needs the chunk of the address space to assign to the devices on the inside - this is what DHCP-PD function is - to provide this chunk.