- Python 62.6%
- Volt 24.7%
- Shell 8.4%
- PHP 4%
- Makefile 0.3%
- is_lease_valid(): filter dynamic leases to state=0 and expire > now. Expired/declined leases no longer produce DNS records. - Override unmanaged BIND records: when a valid lease resolves to a (zone, name, type) that already has a manually-created record, update the value and take ownership instead of skipping. Same-value records are adopted silently. - get_bind_records: unmanaged changed from set to dict so element access is available for in-place override without a second XML scan. - Fix final_managed_uuids: was incorrectly built from the stale-record remainder of managed (records about to be deleted) instead of the kept set. Introduce kept_uuids tracked during apply loop. Correct formula: final = kept_uuids | added_uuids. |
||
|---|---|---|
| src | ||
| build_plugin.sh | ||
| Makefile | ||
| pkg-descr | ||
| README.md | ||
os-bind-leases
OPNsense plugin that syncs Kea DHCPv4/DHCPv6 leases and static reservations into BIND DNS zones managed by the os-bind plugin.
Features
- Reads dynamic leases and static reservations from Kea (DHCPv4 + DHCPv6)
- Constructs FQDNs using domain from lease → DHCP global config → subnet → system domain
- Creates/updates A, AAAA, and PTR records in matching BIND zones
- Tracks managed records via
[kea-sync]description tag — never touches records it didn't create - Triggered on OPNsense startup and optionally via Kea
run_scripthook on lease events - WebUI under Services → Kea BIND Sync
Requirements
- OPNsense 23.x or later
os-bindplugin installed and configured with at least one zoneos-keaplugin installed with kea-ctrl-agent running (default:http://127.0.0.1:8000)
Installation
Option A — Build package (recommended)
git clone ssh://git@forgejo.zint.de:2222/l0rn/os-bind-leases.git
cd os-bind-leases
sh build_plugin.sh
Copy and install on OPNsense:
scp os-kea-bind-sync-1.0.0.pkg root@<opnsense-ip>:/tmp/
ssh root@<opnsense-ip> pkg add /tmp/os-kea-bind-sync-1.0.0.pkg
The post-install script will automatically apply patches to os-kea and restart configd.
Option B — Manual (development/testing)
Copy the plugin files directly onto your OPNsense box:
# On your workstation, from the repo root:
scp -r src/etc/ root@<opnsense-ip>:/
scp -r src/usr/ root@<opnsense-ip>:/usr/
scp -r src/opnsense/mvc/ root@<opnsense-ip>:/usr/local/opnsense/mvc/
scp -r src/opnsense/scripts/ root@<opnsense-ip>:/usr/local/opnsense/scripts/
scp -r src/opnsense/service/ root@<opnsense-ip>:/usr/local/opnsense/service/
Then on OPNsense:
# Make scripts executable
chmod +x /usr/local/opnsense/scripts/OPNsense/KeaBindSync/*.py
chmod +x /usr/local/opnsense/scripts/OPNsense/KeaBindSync/hook.sh
chmod +x /etc/rc.syshook.d/start/99-keabindsync
chmod +x /etc/rc.syshook.d/update/50-keabindsync-repair
chmod +x /etc/rc.syshook.d/early/50-keabindsync-repair
# Create the status directory
mkdir -p /var/db/keabindsync
# Apply patches to os-kea PHP/XML files
python3 /usr/local/opnsense/scripts/OPNsense/KeaBindSync/patch.py apply
# Reload configd so it picks up the new actions
pluginctl -s configd restart
# Clear Volt template cache and reload the web UI
rm -rf /var/cache/opnsense/volt/*
pluginctl -s webgui restart
Kea Hook Setup
The plugin injects the Kea run_script hook by patching the os-kea PHP/XML source files directly. This means the hook is included every time Kea regenerates its configuration — no manual re-patching needed after config changes.
Enable via Kea DHCP Settings
After installing the plugin, go to Services → Kea DHCP → Settings and enable the "Sync Leases to BIND (via os-bind-leases)" checkbox. Save and apply. The hook will be included in the next Kea config generation.
Manual patch management
From Services → Kea BIND Sync → Sync Status you can also:
- Apply Patches — re-apply patches to os-kea files (e.g. after a manual os-kea reinstall)
- Remove Patches — restore original os-kea files (restores
.bak.keabindsyncbackups)
Or from the CLI:
python3 /usr/local/opnsense/scripts/OPNsense/KeaBindSync/patch.py apply
python3 /usr/local/opnsense/scripts/OPNsense/KeaBindSync/patch.py remove
How the hook persists
The patcher (patch.py) modifies three files inside the os-kea plugin:
| File | Change |
|---|---|
generalSettings4.xml / generalSettings6.xml |
Adds a "Sync Leases to BIND" checkbox to the Kea settings form |
KeaDhcpv4.xml / KeaDhcpv6.xml |
Adds registerBindSync BooleanField to the model |
KeaDhcpv4.php / KeaDhcpv6.php |
Injects hook entry into config generation, conditional on the checkbox |
Because the hook is injected at PHP config-generation time, it survives every Kea config apply — OPNsense regenerates the JSON config from PHP, and the hook is always included when the checkbox is checked.
After a firmware update that replaces os-kea files, patches are automatically re-applied by /etc/rc.syshook.d/update/50-keabindsync-repair and /etc/rc.syshook.d/early/50-keabindsync-repair.
Backups of the original os-kea files are kept with a .bak.keabindsync suffix and are restored when patches are removed.
Verify hook is active
Check the generated Kea config:
grep -r "run_script" /usr/local/etc/kea/
Configuration
Settings are in Services → Kea BIND Sync → Settings:
| Setting | Default | Description |
|---|---|---|
| Enabled | Yes | Enable/disable the plugin |
| Kea API URL | http://127.0.0.1:8000 |
kea-ctrl-agent endpoint |
| Sync PTR records | Yes | Create PTR records in reverse zones if they exist |
| Default TTL | 300 |
TTL (seconds) for created records |
How it works
- Queries Kea for all active leases (
lease4-get-all,lease6-get-all) and static reservations (reservation-get-allper subnet, or falls back to config-embeddedreservationsifhost_cmdshook isn't loaded) - Builds an FQDN for each host using this priority:
- Hostname already contains
.→ use as-is domain-nameoption-data on the global DHCP configdomain-nameoption-data on the subnet- System domain from OPNsense config
- Hostname already contains
- Finds the best matching BIND zone by longest suffix match
- Creates/updates A, AAAA, and PTR records — all tagged
[kea-sync]in the description - Deletes any
[kea-sync]records that no longer have a matching lease - Never modifies records without the
[kea-sync]tag - Writes config.xml and calls
configctl bind reconfigure
Logs: /var/log/keabindsync.log
Status: /var/db/keabindsync/status.json
Lock: /var/run/keabindsync.lock (prevents concurrent syncs)
Troubleshooting
Sync does nothing / no records created
- Check that BIND zones are enabled in the os-bind plugin
- Verify Kea leases have hostnames set (
lease4-get-allvia curl) - Check
/var/log/keabindsync.log
Hook not firing after Kea restart
- Make sure the "Sync Leases to BIND" checkbox is enabled in Services → Kea DHCP → Settings
- Re-apply patches:
python3 /usr/local/opnsense/scripts/OPNsense/KeaBindSync/patch.py apply
reservation-get-all errors in log
- The
host_cmdshook is not loaded in Kea — plugin falls back to config-embedded reservations automatically
Permission denied writing config.xml
- Scripts must run as root. configd runs as root by default; verify with
ps aux | grep configd
Patches lost after os-kea firmware update
- Re-apply via the WebUI (Apply Patches button) or CLI:
patch.py apply - The rc.syshook.d/update hook should handle this automatically on next update