As IPv4 availability becomes more and more of a problem, IPv6 starts to become more and more of a necessity. It is incredibly important that engineers on all parts of the Internet work together to ensure that infrastructure is fully IPv6-capable. Service Providers all agree that IPv6 is incredibly important for the future of the Internet, not only because of the massive cost of IPv4, but because of the increased complexity that the life support we build for it entails.

This article will demonstrate some thoughts when implementing DHCP for IPv6, utilising ISC Kea as a Highly Available DHCP server, with IOS XR at our Access Layer.

The above diagram illustrates a simplified ISP network, where each customer belongs within a shared subnet (I decided upon a /64 for my purposes - your subnetting plan might be larger), and gets allocated a /48 via Prefix Delegation from a larger pool of addresses. The actual medium in which the customer connects to the network I've left out because it's not entirely relevant.

When planning your network, be sure that you have a large enough pool to use for Prefix Delegation such that each customer will be able to get something. It's generally recommended to delegate at least a /48, however you may decide to delegate more or less depending on what you have available.

For the purposes of HA, you will require at least two instances. I highly advise that you use two separate subnets, and ideally two separate physical hosts, in ideally two separate locations, for your Kea servers. This ensures that things will keep chugging when other things start to hit the fan.

Preparing Kea

For the purposes of this article, I will be spinning up two Virtual Machines on Vultr running CentOS 7 and Kea 1.6.1, the latest version available at the time of me writing this article. If you are using a cloud provider, ensure that you have selected any available option to enable IPv6 - this is important.

Ensure that your server has at least 2GB memory, ideally 4GB. Kea will not build with less than 2GB due to memory requirements.

The first step to installing anything is downloading it, so let's do that. Head on over to the ISC Downloads website, and copy the link to the latest .tar.gz archive. In my case, this happens to be kea-1.6.1.tar.gz. Let's download this to both of our servers, then extract it.

$ curl -O https://downloads.isc.org/isc/kea/cur/kea-1.6.1.tar.gz
$ tar xvf kea-1.6.1.tar.gz
$ cd kea-1.6.1

Next, we must install some dependencies to ensure that we can build Kea's source code.

$ yum groupinstall "Development Tools"
$ yum install epel-release
$ yum install openssl-devel log4cplus-devel.x86_64 boost boost-thread boost-devel

And finally, let's prepare the Kea build.

$ ./configure

Assuming everything installed correctly, the configuration should run smoothly and you should see something similar to this:

  Now you can type "make" to build Kea. Note that if you intend to
  run "make check", you must run "make" first as some files need to be
  generated by "make" before "make check" can be run.

Let's do exactly what it says - let's build Kea!

$ make && make install

This can sometimes take quite a while depending on what sort of specification your server has. For me, it generally takes between 15 and 30 minutes. Once the installation has finished, verify all of the modules have been installed and are the correct version.

$ keactrl version
keactrl: 1.6.1
kea-dhcp4: 1.6.1
kea-dhcp6: 1.6.1
kea-dhcp-ddns: 1.6.1
kea-ctrl-agent: 1.6.1

Next, let's ensure that we have Kea's HA hook installed. Running the command below should produce the filename we pass in as the output. If it doesn't, the HA hook is not installed.

$ ls /usr/local/lib/kea/hooks/libdhcp_ha.so
/usr/local/lib/kea/hooks/libdhcp_ha.so

Congratulations, Kea is now ready to be configured!

Configuring Kea

As the title suggests, I'll only be covering IPv6 configuration. If you wish to configure IPv4, good luck! It's pretty similar, though.

Kea is broken up into several "services". Examples of these include the keactrl, dhcp4, dhcp6 and DDNS. Each of these have configuration files which you can find at /usr/local/etc/kea/. They are JSON files, making them easy to work with both in code and in the editor of your choice (although they do have an API, so avoid automating with the configuration files directly!). The configuration file that we'll be working with is kea-dhcp6.conf. This is where all the DHCPv6 configuration is found.

By default, Kea includes quite a lot of comments in the DHCPv6 configuration file. To speed things up a little, I'll take you through some of the basic structure of the configuration, after I removed some of the comments and extras we don't need at the moment.

{
    "Dhcp6": {
        "interfaces-config": {
            "interfaces": [ "eth0/2001:19f0:7402:22e:XXXX:XXXX:XXXX:XXXX" ]
        },
        ...
    }
}

One of the first parts of the DHCPv6 configuration is the interface configuration. Here you specify which interface(s) that Kea's DHCPv6 service will be listening on. In my case, the interface is eth0, and I also elected to specify the specific address it will listen on by putting a slash after the interface name, then the IPv6 address.

{
    "Dhcp6": {
        ...
        "control-socket": {
            "socket-type": "unix",
            "socket-name": "/tmp/kea-dhcp6-ctrl.sock"
        },

        "lease-database": {
            "type": "memfile",
            "lfc-interval": 3600
        },
        ...
    }
}

The "control-socket" section tells the DHCPv6 service where to place its UNIX socket. This allows for other processes, such as the Kea Control Agent, to communicate with the process.

The "lease-database" configuration by default uses memfile, which is a file on the local filesystem used to store leases. This is just a CSV containing all the lease information. If you do so desire, you can use a MySQL, PostgreSQL, or Cassandra for storing Kea leases. I won't go into that level of detail here, and for now the memfile will serve us just fine. The "lfc-interval" option tells Kea how often to remove old lease data. You can disable this by setting it to 0, however this could lead to pretty large lease databases containing old/expired leases. This value is in seconds.

{
    "Dhcp6": {
        ...
        "expired-leases-processing": {
            "reclaim-timer-wait-time": 10,
            "flush-reclaimed-timer-wait-time": 25,
            "hold-reclaimed-time": 3600,
            "max-reclaim-leases": 100,
            "max-reclaim-time": 250,
            "unwarned-reclaim-cycles": 5
        },

        "renew-timer": 1000,
        "rebind-timer": 2000,
        "preferred-lifetime": 3000,
        "valid-lifetime": 4000,
        ...
    }
}

I expect anyone reading this is in a position where they are pretty familiar with the fundamentals of DHCPv6, so I won't go into a lot of detail about the timers. One thing I do want to mention though is that you can generally be pretty generous with these timers, since you probably have enough IPv6 space to let people sit on it for a while. The added benefit here is that you can let Kea have a bit of a break, especially in a large network with thousands of CPEs requesting new leases.

{
    "Dhcp6": {
        ...
        "option-data": [
            {
                "name": "dns-servers",
                "data": "2606:4700:4700::1111, 2606:4700:4700::1001"
            }
        ],
        ...
    }
}

Here we can define some options to be sent back with our DHCP responses. You can do it by name or by its code. If it's possible, I recommend using the name since that way your configuration becomes a lot more maintainable and easy to digest, should it ever become necessary.

{
    "Dhcp6": {
        ...
        "subnet6": [
            {
                "subnet": "2001:db8:1::/64",
                "pools": [ { "pool": "2001:db8:1::/80" } ],
                "pd-pools": [
                    {
                        "prefix": "2001:db9::",
                        "prefix-len": 36,
                        "delegated-len": 48
                    }
                ],
                "relay": {
                  "ip-addresses": [ "2001:db8::1" ]
                }
            }
        ],
        ...
    }
}

The "subnet6" part of the configuration file is an array of subnets. At the top level of each subnet, we have a property called "subnet", which is the /64 I showed in the diagram further up in the article. This provides your Access Router and CPE to communicate, and for a route to be inserted into the Access Router's routing table.

Kea doesn't assume that you want every IP to be handed out, so you must specify one or more "pools" to allocate from in the subnet. You can either use CIDR, or specify a range.

Next up is the "pd-pools". This is where we specify one or more pools to use for Prefix Delegation. The first property is the prefix to use, followed by the prefix length. This signifies the total address space to allocate from. In my case, I used a /36. After that, we have the "delegated-len", which tells Kea what block size we wish to allocate on the LAN portion of the CPE through Prefix Delegation. As I stated further up, it's recommended to delegate a /48.

We also specify the relay IP addresses. This is so that Kea can easily identify which subnets to process when a request comes in.

{
    "Dhcp6": {
        ...
        "loggers": [
            {
                "name": "kea-dhcp6",
                "output_options": [
                    {
                        "output": "/usr/local/var/log/kea-dhcp6.log"
                    }
                ],
                "severity": "INFO",
                "debuglevel": 0
            }
        ]
    }
}

Next up is the configuration for logging. This part of the configuration tells Kea where to save its log file, the severity to log (ranging from INFO to DEBUG), and the debugging level (if you chose DEBUG). INFO will log various pieces of information, notably leases as and when they are given out.

Now that we understand the basic Kea configuration, let's move onto some more specific configuration. Let's enable the most important part - Kea's HA hook! Here's the configuration you need - and don't worry, I'll walk you through it.

{
    "Dhcp6": {
        ...
        "hooks-libraries": [
            {
                "library": "/usr/local/lib/kea/hooks/libdhcp_ha.so",
                "parameters": {
                    "high-availability": [
                        {
                            "heartbeat-delay": 10000,
                            "max-ack-delay": 10000,
                            "max-response-delay": 45000,
                            "max-unacked-clients": 10,
                            "mode": "hot-standby",
                            "peers": [
                                {
                                    "auto-failover": true,
                                    "name": "kea-1.preprocess.uk",
                                    "role": "primary",
                                    "url": "http://10.0.0.1:8080/"
                                },
                                {
                                    "auto-failover": true,
                                    "name": "kea-2.preprocess.uk",
                                    "role": "standby",
                                    "url": "http://172.16.0.1:8080/"
                                }
                            ],
                            "send-lease-updates": true,
                            "sync-leases": true,
                            "sync-page-limit": 10000,
                            "sync-timeout": 60000,
                            "this-server-name": "kea-1.preprocess.uk"
                        }
                    ]
                }
            }
        ]
        ...
    }
}

Let's break this up and go through it bit-by-bit.

"hooks-libraries": [
    {
        "library": "/usr/local/lib/kea/hooks/libdhcp_ha.so",
        ...
    }
]

Here we're telling Kea that we would like to load a hook located at /usr/local/lib/kea/hooks/libdhcp_ha.so. We verified that this existed earlier after we installed Kea.

"hooks-libraries": [
    {
        ...
        "parameters": {
            "high-availability": [
                {
                    "heartbeat-delay": 10000,
                    "max-ack-delay": 10000,
                    "max-response-delay": 45000,
                    "max-unacked-clients": 10,
                    "mode": "hot-standby",
                    ...
                }
            ]
        }
    }
]

The next part tells Kea how often to send a heartbeat to the other server(s), how long to wait for a heartbeat response, and what mode to operate in. There are a few modes, however I've only ever used the hot-standby mode.

"hooks-libraries": [
    {
        ...
        "parameters": {
            "high-availability": [
                {
                    ...
                    "peers": [
                        {
                            "auto-failover": true,
                            "name": "kea-1.preprocess.uk",
                            "role": "primary",
                            "url": "http://10.0.0.1:8080/"
                        },
                        {
                            "auto-failover": true,
                            "name": "kea-2.preprocess.uk",
                            "role": "standby",
                            "url": "http://172.16.0.1:8080/"
                        }
                    ],
                    ...
                }
            ]
        }
    }
]

The next section is where we define our peers. Note that unlike routing protocol configuration, here you must define the current server. If you have two servers in your setup, you have two peers.

"auto-failover" tells Kea whether or not it should automatically failover to the second standby server in the event that heartbeats fail. The "name" property is simply a human readable name for yourself. "role" plays a significant part because it's how you let Kea know who is the boss in this DHCPv6 world.

"url" is a bit more complicated. It is essentially letting Kea's HA hook know the URL of the peer's Kea Control Agent API. This allows for it to send ha-heartbeat commands to see who is and/or isn't alive. This can use either IPv4 or IPv6, just be sure that it's reachable.

"hooks-libraries": [
    {
        ...
        "parameters": {
            "high-availability": [
                {
                    ...
                    "send-lease-updates": true,
                    "sync-leases": true,
                    "sync-page-limit": 10000,
                    "sync-timeout": 60000,
                    "this-server-name": "kea-1.preprocess.uk"
                }
            ]
        }
    }
]

This next part tells Kea what exactly it should send to its peers, and which server you're currently configuring. In my case, I am configuring the primary server and put the name in accordingly.

In addition to the HA hook, we also need the lease_cmds hook. This is what Kea uses to synchronise leases across multiple servers. You can enable this by adding just a couple more lines of configuration.

{
    "Dhcp6": {
        "hooks-libraries": [
            {
                "library": "/usr/local/lib/kea/hooks/libdhcp_lease_cmds.so",
                "parameters": { }
            },
        ]
    }
}

It is important that both of your Kea servers have the same configuration for all of the DHCPv6 service, with the exception of "this-server-name" for the HA hook.

Once you have configured both of your servers, you're almost ready to get rolling. Next we must ensure that the Kea Control Agent API is listening on the correct IP addresses, just as you configured for the HA hook.

Let's take a trip to the kea-control-agent.conf file, located at /usr/local/etc/kea/kea-control-agent.conf. We just need to make a quick stop to modify one value.

"http-host": "127.0.0.1",

You should see this by default. Let's change that on each server to reflect its IP address on the network.

"http-host": "10.0.0.1",

Great, now we should be ready to start Kea. Let's give it a shot.

$ keactrl start
INFO/keactrl: Starting /usr/local/sbin/kea-dhcp4 -c /usr/local/etc/kea/kea-dhcp4.conf
INFO/keactrl: Starting /usr/local/sbin/kea-dhcp6 -c /usr/local/etc/kea/kea-dhcp6.conf
INFO/keactrl: Starting /usr/local/sbin/kea-ctrl-agent -c /usr/local/etc/kea/kea-ctrl-agent.conf

If your Kea server didn't start as expected, just have a read of the output. It'll usually tell you which line something isn't quite right on, for example:

2019-12-04 23:14:59.266 ERROR [kea-dhcp6.dhcp6/7067] DHCP6_INIT_FAIL failed to initialize Kea server: configuration error using file '/usr/local/etc/kea/kea-dhcp6.conf': /usr/local/etc/kea/kea-dhcp6.conf:49.13: syntax error, unexpected }

This tells us that there is a syntax error in our configuration file, /usr/local/etc/kea/kea-dhcp6.conf, on line 49. Rinse and repeat until Kea starts properly.

Verify Kea HA Connectivity

The simplest way to verify that Kea's HA hook is in action is to check the logs. Let's take a look at /usr/local/var/log/kea-dhcp6.log. You should hopefully see something that somewhat resembles the following:

2019-12-04 23:25:51.709 INFO  [kea-dhcp6.ha-hooks/7442] HA_LOCAL_DHCP_ENABLE local DHCP service is enabled while the kea-1.preprocess.uk is in the HOT-STANDBY state
2019-12-04 23:25:57.716 INFO  [kea-dhcp6.commands/7442] COMMAND_RECEIVED Received command 'ha-heartbeat'
2019-12-04 23:26:07.729 INFO  [kea-dhcp6.commands/7442] COMMAND_RECEIVED Received command 'ha-heartbeat'
2019-12-04 23:26:17.743 INFO  [kea-dhcp6.commands/7442] COMMAND_RECEIVED Received command 'ha-heartbeat'
2019-12-04 23:26:27.757 INFO  [kea-dhcp6.commands/7442] COMMAND_RECEIVED Received command 'ha-heartbeat'
2019-12-04 23:26:37.770 INFO  [kea-dhcp6.commands/7442] COMMAND_RECEIVED Received command 'ha-heartbeat'
2019-12-04 23:26:47.784 INFO  [kea-dhcp6.commands/7442] COMMAND_RECEIVED Received command 'ha-heartbeat'

If you see lots of heartbeats and no errors in your logs, you can rest assured things are running smoothly. Be sure to check that you definitely don't have any errors on both servers, though. Sometimes heartbeats can reach one server but not the other due to firewall issues, causing both servers to become active.

Configuring IOS XR

The router configuration is actually pretty straight forward and takes much less effort than Kea itself.

dhcp ipv6
 profile hakea proxy
  relay option interface-id insert received
  helper-address vrf your-vrf <server-1>
  helper-address vrf your-vrf <server-2>
 !
 interface Bundle-Ether100 proxy profile hakea

We use the "proxy" option in IOS XR to enable DHCPv6 server + relay so that we can use relay option interface-id insert received in order to insert our Prefix Delegation blocks into the routing table. Replace Bundle-Ether100 with whatever interface your customers reside on.

Verifying DHCPv6 is Functional

IOS XR's proxy mode allows you to view bindings, making is extremely simple to verify that DHCPv6 is working. See the below example:

#show dhcp ipv6 proxy binding 
Wed Dec  4 23:33:51.209 UTC

Summary:
Total number of clients: 64
  DUID  : deadbeefdeaddeadbeefcafe
  MAC Address: dead.beef.cafe
  Client Link Local: fe80::dead:bff:feeef:cafe
  Sublabel: 0x0
    IA ID: 0x390ec4b
      State: BOUND
      IPv6 Prefix:  2001:db9::/48 (Bundle-Ether100)
        lifetime  : 691200 secs (1w1d)
        expiration: 646478 secs (1w0d)

Celebration!

Congratulations! You've made it to the end. I hope by now you have enough of an idea about Kea to understand the power behind it. In all honesty, the hooks are amazing. If you know a bit of programming, you can really extend what Kea does for you to great lengths.

I love working with Kea, and their support team is well worth the money if you plan on running this in production, though bear in mind that Kea is completely free and open-source.