Today I would like to share a highly available DHCP server setup on FreeBSD system, but it should be similarly simple on other UNIX and Unix-like systems. I will use the most obvious choice here – the Internet Systems Consortium implementation – ISC DHCP server – available in the FreeBSD Ports and packages as well.

Since some time ISC is developing a new DHCP server – Kea – with which they intend to eventually replace the ISC DHCP in most server implementations. They also recommend that new implementers consider using Kea instead ISC DHCP and implement ISC DHCP only if Kea does not meet their needs. Kea currently does not include either client or relay for example. Maybe I will make an UPDATE to this post or a separate article some time.

Also Kea got high availability mode just a month ago so if I would be writing this article little earlier then such setup would not be possible with Kea. It also shows how young Kea implementation is thus I would stick to ISC DHCP server for now and ‘watch’ Kea development for the future.

Architecture

Below is the POOR MAN’S ASCII ARCHITECT diagram showing our ISC DHCP setup.

+-------------+ +-------------+ | {primary} | | {secondary} | | DHCPs1 | ==== HA ==== | DHCPs2 | | 10.0.10.251 | | 10.0.10.252 | +-------------+ +-------------+ \ / +------------------------------------------+ | ADDRESS POOL 10.0.10.x/24 ADDRESS POOL | +------------------------------------------+ \ / +----------------+ | {DHCP CLIENTS} | +----------------+

The setup of each DHCP server node is very simple. Its FreeBSD 11.2-RELEASE installed on a 4 GB GPT partition using UFS for the / filesystem and only 666 MB are used as shown below.

root@DHCPs1:/ # uname -v FreeBSD 11.2-RELEASE #0 r335510: Fri Jun 22 04:32:14 UTC 2018 root@releng2.nyi.freebsd.org:/usr/obj/usr/src/sys/GENERIC root@DHCPs1:/ # gpart show => 40 8388528 ada0 GPT (4.0G) 40 1024 1 freebsd-boot (512K) 1064 8386560 2 freebsd-ufs (4.0G) 8387624 944 - free - (472K) root@DHCPs1:/ # du -smc * | sort -n 0 sys 1 COPYRIGHT 1 dev 1 entropy 1 libexec 1 media 1 mnt 1 net 1 proc 1 root 1 tmp 2 bin 4 etc 7 sbin 8 var 10 rescue 12 lib 128 boot 499 usr 666 total

The 128 MB of RAM is enough for small amount of clients. There is still 32 MB free memory along with 32 MB of Inactive and Buffered memory that can be swapped out. Not to mention that each getty process takes about 2 MB ram and instead of 8 you just only need 1 of them. In other words you would be able to run it even with as low as 64 MB of RAM.

root@DHCPs1:~ # top -b -o res last pid: 15205; load averages: 0.13, 0.25, 0.29 up 0+07:39:11 20:03:48 16 processes: 2 running, 14 sleeping Mem: 1688K Active, 30M Inact, 26M Wired, 3800K Buf, 32M Free Swap: PID USERNAME THR PRI NICE SIZE RES STATE TIME WCPU COMMAND 38897 dhcpd 1 20 0 16424K 10724K select 0:00 0.00% dhcpd 30199 root 1 20 0 13160K 8036K RUN 0:00 0.00% sshd 15106 root 1 28 0 12848K 7136K select 0:00 0.00% sshd 53100 root 1 20 0 9180K 5040K select 0:02 0.00% devd 31079 root 1 20 0 7412K 3640K pause 0:00 0.00% csh 15205 root 1 20 0 7916K 3060K RUN 0:00 0.00% top 15960 root 1 20 0 6464K 2480K nanslp 0:00 0.00% cron 69084 root 1 20 0 6412K 2364K select 0:01 0.00% syslogd 28412 root 1 52 0 6408K 2124K ttyin 0:00 0.00% getty 28188 root 1 52 0 6408K 2124K ttyin 0:00 0.00% getty 28504 root 1 52 0 6408K 2124K ttyin 0:00 0.00% getty 28972 root 1 52 0 6408K 2124K ttyin 0:00 0.00% getty 29736 root 1 52 0 6408K 2124K ttyin 0:00 0.00% getty 29080 root 1 52 0 6408K 2124K ttyin 0:00 0.00% getty 30106 root 1 52 0 6408K 2124K ttyin 0:00 0.00% getty 29392 root 1 52 0 6408K 2124K ttyin 0:00 0.00% getty

The /etc/rc.conf file for DHCP nodes DHCPs1 and DHCPs2 is the same (besides hostname and address).

root@DHCPs1:/ # cat /etc/rc.conf hostname=DHCPs1 ifconfig_em0="inet 10.0.10.251/24 up" sshd_enable=YES sendmail_enable=NONE clear_tmp_enable=YES syslogd_flags="-ss" dumpdev=NO

The /etc/sysctl.conf and /boot/loader.conf files modifications are not needed.

Now you will have to install the ISC DHCP server, as the current version is 4.4.x the package will be named accordingly – isc-dhcp44-server – lets add it using the pkg(8) command.

root@DHCPs1:/ # pkg update -f -y The package management tool is not yet installed on your system. Bootstrapping pkg from pkg+http://pkg.FreeBSD.org/FreeBSD:11:amd64//quarterly, please wait... Verifying signature with trusted certificate pkg.freebsd.org.2013102301... done [nextcloud] Installing pkg-1.10.5... [nextcloud] Extracting pkg-1.10.5: 100% Updating FreeBSD repository catalogue... pkg: Repository FreeBSD load error: access repo file(/var/db/pkg/repo-FreeBSD.sqlite) failed: No such file or directory [nextcloud] Fetching meta.txz: 100% 944 B 0.9kB/s 00:01 [nextcloud] Fetching packagesite.txz: 100% 6 MiB 530.8kB/s 00:12 Processing entries: 100% FreeBSD repository update completed. 31134 packages processed. All repositories are up to date. root@DHCPs1:/ # echo ? 0 root@DHCPs1:/ #

Now lets install isc-dhcp44-server package.

root@DHCPs1:/ # pkg install isc-dhcp44-server Updating FreeBSD repository catalogue... FreeBSD repository is up to date. All repositories are up to date. Checking integrity... done (0 conflicting) The following 1 package(s) will be affected (of 0 checked): New packages to be INSTALLED: isc-dhcp44-server: 4.4.1_3 [FreeBSD] Number of packages to be installed: 1 The process will require 6 MiB more space. Proceed with this action? [y/N]: y [1/1] Installing isc-dhcp44-server-4.4.1_3... ===> Creating groups. Creating group 'dhcpd' with gid '136'. ===> Creating users Creating user 'dhcpd' with uid '136'. [1/1] Extracting isc-dhcp44-server-4.4.1_3: 100% Message from isc-dhcp44-server-4.4.1_3: **** To setup dhcpd, please edit /usr/local/etc/dhcpd.conf. **** This port installs the dhcp daemon, but doesn't invoke dhcpd by default. If you want to invoke dhcpd at startup, add these lines to /etc/rc.conf: dhcpd_enable="YES" # dhcpd enabled? dhcpd_flags="-q" # command option(s) dhcpd_conf="/usr/local/etc/dhcpd.conf" # configuration file dhcpd_ifaces="" # ethernet interface(s) dhcpd_withumask="022" # file creation mask **** If compiled with paranoia support (the default), the following rc.conf options are also supported: dhcpd_chuser_enable="YES" # runs w/o privileges? dhcpd_withuser="dhcpd" # user name to run as dhcpd_withgroup="dhcpd" # group name to run as dhcpd_chroot_enable="YES" # runs chrooted? dhcpd_devfs_enable="YES" # use devfs if available? dhcpd_rootdir="/var/db/dhcpd" # directory to run in dhcpd_includedir="" # directory with config- files to include **** WARNING: never edit the chrooted or jailed dhcpd.conf file but /usr/local/etc/dhcpd.conf instead which is always copied where needed upon startup.

Now update the pkg(8) repository data and install the isc-dhcp44-server package on DHCPs2 node.

The configuration uses single network segment 10.0.10.0/24 for the clients in the range of 10-250 values in the last octet. The parameter split 128 will split the load equally between DHCP server nodes. As this is just example, we will use 1.1.1.1 and 9.9.9.9 DNS servers and ‘ domain.com ‘ domain. For the record, the split 128 parameter is set only on the primary node – DHCPs1 in our case. As the man dhcpd.conf page suggests we will “use the same master configuration file for both servers, and have a separate file that contains the peer declaration and includes the master file.” as “This will help you to avoid configuration mismatches.”

root@DHCPs1:/ # cat /usr/local/etc/dhcpd.conf # CORE failover peer "ha-dhcp" { primary; address 10.0.10.251; port 678; peer address 10.0.10.252; peer port 678; max-response-delay 60; max-unacked-updates 10; mclt 3600; split 128; load balance max seconds 3; } include "/usr/local/etc/dhcpd.conf.SHARED";

root@DHCPs1:/ # cat /usr/local/etc/dhcpd.conf.SHARED # CLIENTS subnet 10.0.10.0 netmask 255.255.255.0 { default-lease-time 604800; max-lease-time 604800; option routers 10.0.10.254; option broadcast-address 10.0.10.255; option subnet-mask 255.255.255.0; option domain-search "domain.com"; option domain-name-servers 1.1.1.1,9.9.9.9; pool { failover peer "ha-dhcp"; range 10.0.10.10 10.0.10.250; } }

… and the secondary node.

root@DHCPs2:~ # cat /usr/local/etc/dhcpd.conf # CORE failover peer "ha-dhcp" { secondary; address 10.0.10.252; port 678; peer address 10.0.10.251; peer port 678; max-response-delay 60; max-unacked-updates 10; mclt 3600; load balance max seconds 3; } include "/usr/local/etc/dhcpd.conf.SHARED";

root@DHCPs2:/ # cat /usr/local/etc/dhcpd.conf.SHARED # CLIENTS subnet 10.0.10.0 netmask 255.255.255.0 { default-lease-time 604800; max-lease-time 604800; option routers 10.0.10.254; option broadcast-address 10.0.10.255; option subnet-mask 255.255.255.0; option domain-search "domain.com"; option domain-name-servers 1.1.1.1,9.9.9.9; pool { failover peer "ha-dhcp"; range 10.0.10.10 10.0.10.250; } }

The /usr/local/etc/dhcpd.conf.SHARED file is identical on both nodes.

Now lets start the DHCP server on both nodes.

root@DHCPs1:~ # sysrc dhcpd_enable=YES dhcpd_enable: -> YES root@DHCPs1:~ # service isc-dhcpd start Starting dhcpd. Internet Systems Consortium DHCP Server 4.4.1 Copyright 2004-2018 Internet Systems Consortium. All rights reserved. For info, please visit https://www.isc.org/software/dhcp/ Config file: /usr/local/etc/dhcpd.conf Database file: /var/db/dhcpd/dhcpd.leases PID file: /var/run/dhcpd/dhcpd.pid Wrote 122 leases to leases file. Listening on BPF/em0/08:00:27:3c:ab:c8/10.0.10.0/24 Sending on BPF/em0/08:00:27:3c:ab:c8/10.0.10.0/24 Sending on Socket/fallback/fallback-net failover peer ha-dhcp: I move from normal to startup

… and the same on secondary node.

root@DHCPs2:~ # sysrc dhcpd_enable=YES dhcpd_enable: -> YES root@DHCPs2:~ # service isc-dhcpd onestart Starting dhcpd. Internet Systems Consortium DHCP Server 4.4.1 Copyright 2004-2018 Internet Systems Consortium. All rights reserved. For info, please visit https://www.isc.org/software/dhcp/ Config file: /usr/local/etc/dhcpd.conf Database file: /var/db/dhcpd/dhcpd.leases PID file: /var/run/dhcpd/dhcpd.pid Wrote 122 leases to leases file. Listening on BPF/em0/08:00:27:de:9b:3d/10.0.10.0/24 Sending on BPF/em0/08:00:27:de:9b:3d/10.0.10.0/24 Sending on Socket/fallback/fallback-net failover peer ha-dhcp: I move from communications-interrupted to startup

Now, as the both nodes for the highly available DHCP server are started, lets try to get some DHCP lease on the DHCP client – DHCPc in our example.

root@DHCPc:~ # dhclient em0 DHCPREQUEST on em0 to 255.255.255.255 port 67 DHCPREQUEST on em0 to 255.255.255.255 port 67 DHCPACK from 10.0.10.251 bound to 10.0.10.131 -- renewal in 302119 seconds.

root@DHCPc:~ # ifconfig em0 em0: flags=8843 metric 0 mtu 1500 options=9b ether 08:00:27:d9:45:96 hwaddr 08:00:27:d9:45:96 inet 10.0.10.131 netmask 0xffffff00 broadcast 10.0.10.255 nd6 options=29 media: Ethernet autoselect (1000baseT ) status: active

We can see that the DHCP client – DHCPc – got the 10.0.10.131 address.

We can of course set permanent address for it with the host option in the /usr/local/etc/dhcpd.conf.SHARED config file as show below.

The needed ‘addon’ is shown below.

group { host DHCPc { hardware ethernet 08:00:27:d9:45:96; fixed-address 10.0.10.9; } }

It needs to be added on both nodes in the /usr/local/etc/dhcpd.conf.SHARED config file, here is how the new shared config file would look like.

root@DHCPs1:~ # cat /usr/local/etc/dhcpd.conf.SHARED # CLIENTS subnet 10.0.10.0 netmask 255.255.255.0 { default-lease-time 604800; max-lease-time 604800; option routers 10.0.10.254; option broadcast-address 10.0.10.255; option subnet-mask 255.255.255.0; option domain-search "domain.com"; option domain-name-servers 1.1.1.1,9.9.9.9; group { host DHCPc { hardware ethernet 08:00:27:d9:45:96; fixed-address 10.0.10.9; } } pool { failover peer "ha-dhcp"; range 10.0.10.10 10.0.10.250; } }

Now copy the /usr/local/etc/dhcpd.conf.SHARED file to the second node.

Lets try again to get the address from the same DHCP client.

root@DHCPc:~ # pkill dhclient root@DHCPc:~ # service netif restart root@DHCPc:~ # dhclient em0 DHCPREQUEST on em0 to 255.255.255.255 port 67 DHCPREQUEST on em0 to 255.255.255.255 port 67 DHCPACK from 10.0.10.252 bound to 10.0.10.131 -- renewal in 1665 seconds. DHCPREQUEST on em0 to 255.255.255.255 port 67 DHCPREQUEST on em0 to 255.255.255.255 port 67 DHCPNAK from 10.0.10.252 DHCPDISCOVER on em0 to 255.255.255.255 port 67 interval 3 DHCPOFFER from 10.0.10.251 DHCPOFFER from 10.0.10.252 DHCPOFFER already seen. DHCPREQUEST on em0 to 255.255.255.255 port 67 DHCPACK from 10.0.10.252 bound to 10.0.10.9 -- renewal in 302400 seconds.

root@DHCPc:~ # ifconfig em0 em0: flags=8843 metric 0 mtu 1500 options=9b ether 08:00:27:d9:45:96 hwaddr 08:00:27:d9:45:96 inet 10.0.10.9 netmask 0xffffff00 broadcast 10.0.10.255 nd6 options=29 media: Ethernet autoselect (1000baseT ) status: active

Now we got the permanent 10.0.10.9 address.

You can now experiment with these values in the /etc/rc.conf file:

dhcpd_flags

dhcpd_ifaces

dhcpd_withumask

dhcpd_chuser_enable

dhcpd_withuser

dhcpd_withgroup

dhcpd_chroot_enable

dhcpd_devfs_enable

dhcpd_rootdir

dhcpd_includedirnclude

… with the all other possible options from the man dhcpd.conf page 🙂

EOF