Setup local DHCP to distribute fallback DNS server

A few months ago, I had an issue with my Unraid server crashing randomly for no apparent reason. Since I host several services, including a local DNS, it became annoying to have no DNS when this server crashes.

I considered setting up a Raspberry Pi, but then the possibility of failing SD cards became a concern. A USB disk or even an NVMe HAT could be options, but they are somewhat pricey and hard to obtain. Since I didn’t want to give up on the idea of running a backup server with an NVMe, I purchased a Beelink Mini S12 during Black Friday, which came with a 256 GB NVMe for around 140€.

Currently, it is running Debian with almost all services operating in Docker via Portainer.

Portainer

blockyI have started building my own blocky image that already includes my configuration. This allows me to distribute the same configuration to multiple machines.
borgmaticThis tool performs backups of configuration files and container data to borgbase.com
esphomeI use this tool to set up, update, and maintain esphome devices. Currently, I am only using it for my pc-switch to hard reset my Unraid server from anywhere
ntp-serverI use this tool to set up a local NTP server that can be distributed via DHCP to all clients. Unfortunately, it is not possible to distribute a hostname like time.cloudflare.com
portainer-backupPortainer does not provide scheduled backups in its free version. This one fixes that
seqseq is a lightweight logging server with its own UI and support for syslog. I configured my Unraid server to send logs to this server
traefikI use traefik to make everything available via https
watchtowerwatchtower is a useful tool for scheduling automatic container image updates

But there are some more services. I was looking for a DHCP server that is written in Go or any other modern language with modern documentation that runs in Docker without me having to create images first. Turns out, there aren’t many. I found coredhcp and dora. In the end, they either didn’t work properly in Docker or had overly complicated documentation. Ultimately, I went with kea. In fact, I am using docker-kea, which basically provides everything as pre-made docker images.

Kea is everything but modern, but at least the documentation is easily understandable and it’s easy to use, unless you want to set up their frontend Stork

kea-dhcpThe main DHCP server that listens for DHCP requests via the host's interface
kea-ddnsA middleman service that kea-dhcp communicates with. Whenever a new lease is assigned, kea-dhcp communicates with kea-ddns, which then communicates with a DNS server to map the IP to $hostname.lan and vice versa for reverse DNS
kea-bind-ddnsAn instance of bind that I had to set up because blocky didn't implement dynamic DNS updates yet
kea-ctrl-agentJust a controller agent that makes the kea socket available as a REST API. I haven't really used that feature yet though

So, to come back to this post’s headline, I did all that just to distribute my fallback DNS to all clients in my local network. Why? Because my Fritz!Box only allows distributing a single DNS server via DHCP. Yep, that’s the reason for all that.

Besides learning a lot of things about how DHCP works, I now have a bit more control over my local clients from a single configuration file instead of my router’s interface. Replacing the router without losing IP reservations is now also possible.

To resolve client names, blocky has a neat split-DNS feature. Requests for a certain domain are resolved by a different DNS server. In this case, that’s the kea-bind-ddns. The same goes for blocky’s internal client resolution:

conditional:
  mapping:
    lan: tcp+udp:192.168.0.8:5353
    168.192.in-addr.arpa: tcp+udp:192.168.0.8:5353
clientLookup:
  upstream: tcp+udp:192.168.0.8:5353
  singleNameOrder:
    - 1

I’ve had this setup for about 2-3 weeks now. Everything seemed to work fine until yesterday. My Sonos devices were not available for music playback via Spotify Connect. They showed up in the Sonos app and I could play music through Apple AirPlay though.

When I contacted the Sonos support via chat, I spoke with “Christian.” He was very competent and helpful. He understood my setup and provided personalized responses without boilerplate answers. He directed me to the “internal” tools that Sonos devices offer. Each device has a web page like http://<ip>:1400/tools.htm or /status to access additional information. On the tools page, I discovered that my devices could ping local IPs but not external ones. The same issue occurred with DNS resolution - it didn’t work.

It turned out that the settings for the gateway, DNS servers, etc. were not applied to my Sonos devices but to all other devices in the DHCP pool. I reserve IPs for my Sonos devices and change their hostname to avoid them all showing up as “SonosZP”. The problem was that the settings were only configured for the pool, while reservations are outside of the pool. Moving the settings to the global level fixed the issue.