Category Archives: Linux

DHCPv6 configuration for isc-dhcp-server

My network is running IPv6 with SLAAC and the JunOS version on my firewall does not support RDNSS, so I needed a full-featured DHCP server to hand out DNS information.

I already have a Raspberry Pi running isc-dhcp-server for my IPv4 network so I thought it would be simple to add IPv6 support. Unfortunately it was not – the ISC DHCP server does not support dual-stack natively so you need to run two independent services in parallel.

The official documentation for this is very limited and outdated, so it took a lot of blogs and forum posts to get it working, so perhaps this post will help others in their quest for DHCPv6 support 🙂

I am assuming that you have already installed and configured the DHCP server for IPv4. If not, you can do so by entering the following command.

pi@pi ~ $ sudo apt-get install isc-dhcp-server

After installation, you can start the service manually, or reboot.

pi@pi ~ $ sudo service isc-dhcp-server start
[ ok ] Starting ISC DHCP server: dhcpd.

This post focuses on the DHCPv6 part. For the actual configuration for standard DHCP, there are plenty of good guides available online.

Configuring a static IPv6 address

In case you haven’t done so, be sure to configure your device with a static IPv6 address.

pi@pi ~ $ cat /etc/network/interfaces

# iface eth0 inet dhcp

# Loopback interface

auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static

# Static IPv4

address 10.6.0.36
gateway 10.6.0.1
network 10.6.0.0
netmask 255.255.255.0
broadcast 10.6.0.255

# Static IPv6

iface eth0 inet6 static
address 2a02:1234:420a:100b::10
netmask 64
gateway 2a02:1234:420a:100b::1

Restart the network interface or reboot the device to apply it.

INIT file – /etc/init.d/isc-dhcp-server6

First step is to copy the working init script, which is used by the v4 server, to a new file.

pi@pi ~ $ sudo cp /etc/init.d/isc-dhcp-server /etc/init.d/isc-dhcp-server6

This will create a new file isc-dhcp-server6 – use nano or vi to edit it.

Some of the values in the ### INIT INFO ### section need to modified to represent the new dhcpv6 service. I’ve also modified the value for the default config file, DHCPD_DEFAULT, to be /etc/default/isc-dhcp-server6 – more on that file later.

The script below shows only the first part of the script so don’t copy-paste.

#!/bin/sh
#
#

### BEGIN INIT INFO
# Provides:          isc-dhcp-server6
# Required-Start:    $remote_fs $network $syslog
# Required-Stop:     $remote_fs $network $syslog
# Should-Start:      $local_fs slapd $named
# Should-Stop:       $local_fs slapd
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: DHCPv6 server
# Description:       Dynamic Host Configuration Protocol Server for IPv6
### END INIT INFO

PATH=/sbin:/bin:/usr/sbin:/usr/bin

test -f /usr/sbin/dhcpd || exit 0

DHCPD_DEFAULT="${DHCPD_DEFAULT:-/etc/default/isc-dhcp-server6}"

# It is not safe to start if we don't have a default configuration...
if [ ! -f "$DHCPD_DEFAULT" ]; then
        echo "$DHCPD_DEFAULT does not exist! - Aborting..."
        if [ "$DHCPD_DEFAULT" = "/etc/default/isc-dhcp-server" ]; then
                echo "Run 'dpkg-reconfigure isc-dhcp-server' to fix the problem."
        fi
        exit 0
fi

. /lib/lsb/init-functions

# Read init script configuration
[ -f "$DHCPD_DEFAULT" ] && . "$DHCPD_DEFAULT"

NAME=dhcpd
DESC="ISC DHCPv6 server"
# fallback to default config file
DHCPD_CONF=${DHCPD_CONF:-/etc/dhcp/dhcpd6.conf}
# try to read pid file name from config file, with fallback to /var/run/dhcpd.pid
if [ -z "$DHCPD_PID" ]; then
        DHCPD_PID=$(sed -n -e 's/^[ \t]*pid-file-name[ \t]*"(.*)"[ \t]*;.*$/\1/p' < "$DHCPD_CONF" 2>/dev/null | head -n 1)
fi
DHCPD_PID="${DHCPD_PID:-/var/run/dhcpd.pid}"


... rest of script omitted for brevity ...

Defaults file – /etc/default/isc-dhcp-server6

This file stores the values used by the INIT script, so it’s absolutely essential that they are correct, or the service will refuse to start or will not work as expected.

Again, you can copy the existing file and modify those parameters.

sudo cp /etc/default/isc-dhcp-server /etc/default/isc-dhcp-server6

The configuration file for this example has four different values.

  • DHCPD_CONF – this value identifies the configuration file which will hold our DHCPv6 attributes.
  • DHCPD_PID – when the service is started, the process ID for the service will be written to this file
  • OPTIONS – here we need to specify the server to run in v6 mode by using the -6 knob
  • INTERFACES – the interface(s) on which the DHCP server will be listening, in my case eth0
# Defaults for isc-dhcp-server6 initscript
# sourced by /etc/init.d/isc-dhcp-server6
# installed at /etc/default/isc-dhcp-server6 by the maintainer scripts

#
# This is a POSIX shell fragment
#

# Path to dhcpd's config file (default: /etc/dhcp/dhcpd.conf).
DHCPD_CONF=/etc/dhcp/dhcpd6.conf

# Path to dhcpd's PID file (default: /var/run/dhcpd.pid).
DHCPD_PID=/var/run/dhcpd6.pid

# Additional options to start dhcpd with.
#       Don't use options -cf or -pf here; use DHCPD_CONF/ DHCPD_PID instead
OPTIONS="-6"

# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
#       Separate multiple interfaces with spaces, e.g. "eth0 eth1".
INTERFACES="eth0"

Config file – /etc/dhcp/dhcpd6.conf

This file, just like in the IPv4 version, contains the global DHCP options and the subnet statements, reservations, etc. I am using it in conjunction with SLAAC, so DHCPv6 is only used to hand out the DNS info.

Note – For IPv6 subnets, you need to use the subnet6 statement.

# The ddns-updates-style parameter controls whether or not the server will
# attempt to do a DNS update when a lease is confirmed.
ddns-update-style none;

# Option definitions common to all supported networks...
default-lease-time 600;
max-lease-time 7200;

# This DHCP server is the official DHCP server for the local network
authoritative;

# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
log-facility local7;

# Subnet declaration
subnet6 2a02:1234:420a:112d::/64  {
        option dhcp6.name-servers 2a02:1234:420a:100b::10, 2001:4860:4860::8888;
        option dhcp6.domain-search "netprobe.local";
}

subnet6 2a02:1234:420a:100b::/64 {
        option dhcp6.name-servers 2a02:1234:420a:100b::10, 2001:4860:4860::8888;
        option dhcp6.domain-search "netprobe.local";
}

The DHCP6 leases file

It’s possible the service will not start because this file does not exist:

Dec  5 21:37:00 pi dhcpd: Can't open lease database /var/lib/dhcp/dhcpd6.leases: No such file or directory --
Dec  5 21:37:00 pi dhcpd:   check for failed database rewrite attempt!

Just create an empty file and it should be OK.

pi@pi / $ sudo touch /var/lib/dhcp/dhcpd6.leases

Starting the Service and verifying it

If the configuration files are correct, we can now manually start the service.

pi@pi /etc/default $ sudo service isc-dhcp-server6 start
[ ok ] Starting ISC DHCPv6 server: dhcpd.

If you are getting an error, you will usually find a clue in the /var/log/syslog files…

Verify that the second service is now running:

pi@pi /etc/default $ ps aux | grep dhcp
root      2091  0.0  1.2   6824  5368 ?        Ss   20:52   0:00 /usr/sbin/dhcpd -q -cf /etc/dhcp/dhcpd.conf -pf /var/run/dhcpd.pid eth0
root      2751  0.0  0.6   5484  2868 ?        Ss   21:18   0:00 /usr/sbin/dhcpd -q -6 -cf /etc/dhcp/dhcpd6.conf -pf /var/run/dhcpd6.pid eth0
pi        2772  0.0  0.4   3572  1944 pts/0    S+   21:19   0:00 grep --color=auto dhcp

To validate the process ID as written at service start time.

pi@pi /etc/default $ cat /var/run/dhcpd6.pid
2751

To verify that the service is listening on UDP port 547:

pi@pi /etc/default $ netstat -an | grep 547
udp6       0      0 :::547                  :::*

If you have your DHCP relays set up correctly, you can also verify communication with tcpdump. Here we can see a client is requesting information from DHCP.

pi@pi /etc/default $ sudo tcpdump -i eth0 port 547
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
21:21:36.935479 IP6 2a02:1234:420a:112d::1.dhcpv6-server > 2a02:1234:420a:100b::10.dhcpv6-server: dhcp6 relay-fwd
21:21:36.942854 IP6 2a02:1234:420a:100b::10.dhcpv6-server > 2a02:1234:420a:112d::1.dhcpv6-server: dhcp6 relay-reply
21:21:36.951406 IP6 2a02:1234:420a:112d::1.dhcpv6-server > 2a02:1234:420a:100b::10.dhcpv6-server: dhcp6 relay-fwd
21:21:36.955437 IP6 2a02:1234:420a:100b::10.dhcpv6-server > 2a02:1234:420a:112d::1.dhcpv6-server: dhcp6 relay-reply
21:22:16.092006 IP6 2a02:1234:420a:112d::1.dhcpv6-server > 2a02:1234:420a:100b::10.dhcpv6-server: dhcp6 relay-fwd
21:22:16.094353 IP6 2a02:1234:420a:100b::10.dhcpv6-server > 2a02:1234:420a:112d::1.dhcpv6-server: dhcp6 relay-reply
21:22:16.169193 IP6 2a02:1234:420a:112d::1.dhcpv6-server > 2a02:1234:420a:100b::10.dhcpv6-server: dhcp6 relay-fwd
21:22:16.171748 IP6 2a02:1234:420a:100b::10.dhcpv6-server > 2a02:1234:420a:112d::1.dhcpv6-server: dhcp6 relay-reply

Auto-starting the service at boot

To add the new service to the initialization scripts, enter the following command.

pi@pi ~ $ sudo update-rc.d isc-dhcp-server6 defaults
update-rc.d: using dependency based boot sequencing
pi@pi ~ $

After a reboot, you should again have two running instances of the dhcpd service – one with the -6 option.

pi@pi ~ $ ps aux | grep dhcp
root      2100  0.0  1.1   6824  5120 ?        Ss   21:30   0:00 /usr/sbin/dhcpd -q -cf /etc/dhcp/dhcpd.conf -pf /var/run/dhcpd.pid eth0
root      2135  0.0  0.6   5484  3048 ?        Ss   21:30   0:00 /usr/sbin/dhcpd -q -6 -cf /etc/dhcp/dhcpd6.conf -pf /var/run/dhcpd6.pid eth0
pi        2356  0.0  0.4   3568  1836 pts/0    S+   21:32   0:00 grep --color=auto dhcp

If everything went well, your clients should now be picking up the DNS servers (or other DHCPv6 information) from your new DHCPv6 server.

If this guide does not work for you, please let me know in the comments!