os-kea-ubnd-ddns v0.98 — Kea DHCP → Unbound automatic DNS registration

Started by tkreagan, Today at 04:56:44 PM

Previous topic - Next topic
Hi all,

os-kea-ubnd-ddns is an OPNsense plugin that keeps Kea DHCP and Unbound in sync, automatically and close to real-time.
GitHub: https://github.com/tkreagan/os-kea-ubnd-ddns
Latest release (v0.98 .pkg): https://github.com/tkreagan/os-kea-ubnd-ddns/releases/latest
It lives under Services → Kea Unbound DDNS once installed.

Background
I've been running OPNsense with Kea DHCP since the ISC DHCP sunset and hit the same problem a lot of people have: Kea has no built-in equivalent to the automatic DNS registration that ISC DHCP used to provide. I know static reservation mapping was added to Unbound in the standard plugin, but it doesn't handle dynamic leases and the other workarounds felt fragile. So I wrote a plugin to fix it properly.
I know some people have used dnsmasq — it's a fine combined DHCP+DNS server — but it works by being both things at once, and that's exactly why it can't help here. Kea and Unbound are separate services with no shared state, and dnsmasq can't bridge two services it doesn't own.

How it works
The best mechanism Kea provides is kea-dhcp-ddns (D2), which sends RFC 2136 DNS UPDATE packets when leases change. Standard Unbound doesn't speak RFC 2136. This plugin closes that gap: it's a stub listener on 127.0.0.1:53535 that receives D2's DNS UPDATE packets and translates them immediately into unbound-control local_data and local_data_remove calls. A, AAAA, and PTR records are handled automatically, and DNS updates happen as soon as the lease is registered.
The resident daemon also watches the Kea and Unbound pidfiles. If either service restarts and flushes its state, the daemon automatically runs a full reconcile from Kea — no manual intervention needed.

Beyond the live path, the plugin has scheduled cron jobs for syncing and cleaning anything that drifted between live updates and the next cron tick (due to how DHCP and Kea work, some things can't easily be handled in real time). If you want closer to real-time performance, there is a secondary log watcher daemon that tails the Kea log for release events and SERVFAILs, triggering targeted DNS cleanup without waiting for the next cron run.


Hostname collision handling
When two devices claim the same hostname, you choose the behavior: last wins (default — the new lease wins), allow (both IPs coexist, useful for dual-stack), first wins (first registrant holds the name), or none (evict all records for the name until the next sync resolves who owns it — prefer a brief gap over a wrong answer). OPNsense Host Overrides and "Register DHCP Static Mappings" entries are never touched by any path.


Magic hostnames
This is the feature I'm most pleased with. On most networks you'll eventually get collisions that aren't easily managed — accidents, IoT devices with hard-coded names, devices with MAC address randomization (iOS, Android, modern Windows).
When Magic hostnames is enabled, every device involved in a collision gets a stable, per-MAC parallel FQDN alongside whatever the collision policy does to the bare name:

laptop.home.lan          → 192.168.1.42  (last_wins: the current registrant)
laptop-mAABBCC.home.lan  → 192.168.1.42  (magic FQDN for that MAC)
laptop-mDDEEFF.home.lan  → 192.168.1.75  (the displaced device still has a name)
The magic FQDNs are written regardless of collision policy and are never displaced by subsequent collisions. You always have a stable name to reach any device in a collision group, even when the bare hostname is bouncing between registrants.

There's also an optional LAA tag that inserts -laa- into the suffix for MACs with the locally-administered bit set (the kind iOS and Android randomize), so you can see at a glance that the suffix encodes a throwaway MAC that may change.


Is it ready to use?
Yes. v0.98 runs on my main OPNsense box and has since shortly after the Kea migration. The feature set is complete — all four collision policies, magic hostnames, the log watcher, a Lease Audit tab that shows exactly what's registered and what the plugin thinks about each record, and a Kea Config Check tab that validates your subnet DDNS settings and can configure them for you automatically.
The 0.98 version label is honest: I've tested this extensively and the plugin is designed to be safe — it only ever writes to Unbound's runtime in-memory zone via unbound-control and never touches Unbound's config files or Host Overrides. I'm treating the next phase of broader testing and the community plugin review process as the gate to 1.0.


What's next
I'm still actively developing this - working through a few things on the edge and I have a couple more ideas I want to develop - so I am happy to take feedback and suggestions.  Most importantly, installation is currently manual (download the .pkg from the release page). My goal is to get 1.0 submitted as an OPNsense community plugin once 26.7 releases and things settle — 26.7.1 or so, when there's bandwidth to review it. The plugin is fairly self-contained and shouldn't be impacted by the 26.7 changes, but I'd rather not put in a PR during a release crunch.

Happy to answer questions — would love to hear how it works on other setups.