Redundant DHCP and DNS Resolver using OpenBSD

       888 words, 5 minutes

One of my OpenBSD server provides DHCP and DNS resolving for my home LAN. But it sometimes has to go into maintenance mode. And if an IoT or phone requires an IP address or an FQDN at the precise moment, I hear screaming throughout the whole house.

So I decided to have fully redundant network services using two OpenBSD servers.

The installation is really simple:

Unbound DNS validating resolver

OpenBSD 7.7 ships with unbound 1.22.0.

Its configuration is really simple given that a /var/unbound/etc/unbound.conf file already exists.

Both servers share the exact same configuration. I only changed a few things from the default configuration.

--- unbound.conf.orig   Sun Apr 13 16:07:40 2025
+++ unbound.conf        Wed Jul 16 18:09:40 2025
@@ -3,4 +3,5 @@
 server:
        interface: 127.0.0.1
+       interface: 192.0.2.666
        #interface: 127.0.0.1@5353      # listen on alternative port
        interface: ::1
@@ -12,6 +13,10 @@
        #outgoing-interface: 2001:db8::53
 
+       num-threads: 2
+
        access-control: 0.0.0.0/0 refuse
        access-control: 127.0.0.0/8 allow
+       access-control: 192.0.2.0/24 allow
        access-control: ::0/0 refuse
        access-control: ::1 allow
@@ -20,4 +25,14 @@
        hide-version: yes
 
+       log-queries: no
+       log-replies: yes
+       log-tag-queryreply: yes
+       log-destaddr: yes
+       log-local-actions: yes
+       log-servfail: yes
+
+       prefetch: yes
+       prefetch-key: yes
+
        # Perform DNSSEC validation.
        #
@@ -25,4 +40,6 @@
        val-log-level: 2
 
+       qname-minimisation: yes
+
        # Synthesize NXDOMAINs from DNSSEC NSEC chains.
        # https://tools.ietf.org/html/rfc8198
@@ -38,4 +55,6 @@
        #local-data-ptr: "192.0.2.51 mycomputer.local"
 
+       include: "/var/unbound/etc/home.arpa.conf"
+
        # Use TCP for "forward-zone" requests. Useful if you are making
        # DNS requests over an SSH port forwarding.

What this diff means is:

Once both servers are configured properly, enabling the service is as simple as:

# rcctl enable unbound
# rcctl start unbound

To make sure the root trust anchor for DNSSEC validation is always up-to-date, I added an entry in the crontab:

# update the root trust anchor for DNSSEC validation
1-30 0-3 * * * -ns /usr/sbin/unbound-anchor -v

When an update is required on the configuration or the local zone file, I edit the file on one server, reload it, rsync the configuration to the second server and reload it.

Having two DNS resolvers on the same LAN is no problem; since they have the same local zone content. So the LAN clients can query any one of those, any time.

Dynamic Host Configuration Protocol (DHCP) daemon

Should you not want to start from scratch after reading dhcpd(8), dhcpd.conf(5) and dhcpd.leases(5), there is a configuration example in /etc/examples/dhcpd.conf that can be copied to /etc/dhcpd.conf.

Not much has to be modified to have a working DHCPd configuration:

--- /etc/examples/dhcpd.conf    Sun Apr 13 16:07:40 2025
+++ /etc/dhcpd.conf     Thu Sep 11 01:43:26 2025
@@ -11,21 +11,115 @@
 # Addresses:           192.168.1.32 - 192.168.1.127
 #
-option  domain-name "my.domain";
-option  domain-name-servers 192.168.1.3, 192.168.1.5;
+option  domain-name "home.arpa";
+option  domain-name-servers 192.0.2.666, 192.0.2.999;

-subnet 192.168.1.0 netmask 255.255.255.0 {
-       option routers 192.168.1.1;
+subnet 192.0.2.0 netmask 255.255.255.0 {
+       option domain-search "home.arpa";
+       option routers 192.0.2.1;

-       range 192.168.1.32 192.168.1.127;
+       range 192.0.2.100 192.0.2.200;

-       host static-client {
-               hardware ethernet 22:33:44:55:66:77;
-               fixed-address 192.168.1.200;
+       host smart-stove-top {
+               hardware ethernet fe:ed:ba:be:ca:fe;
+               fixed-address 192.0.2.404;
        }
}

What this diff implies is:

DHCP leases synchronization

Having two DHCP servers on the same LAN can lead to allocating the same IP to two LAN clients - as soon as both DHCP servers have the exact same configuration. To prevent such issue, both servers have to be kept aware of the allocation the other one already did. Hopefully, dhcpd(8) has options for this.

I want to use an Active/Active configuration. So I need to have my DHCP servers both broadcasting their IP allocations and also listen to others allocations. Those behaviour are achieved using the -Y and -y arguments. Read dhcpd(8) for more details.

# rcctl enable dhcpd
# rcctl set dhcpd flags -y vio0 -Y vio0
# rcctl start dhcpd

Both DHCP servers are configured the same way and share the same configuration file. When a modification is required, it is copied from one server to the other; and services are reloaded.

Depending on your topology, you also may be interested by the fact that dhcpd(8) can interact with pf(4). Just read the man page for more details.

Until then, have a nice IP allocation and DNS resolving day!