feat(dyndns): add optional rndc journal synchronization

Add configurable post-update journal synchronization via rndc sync -clean.

Separate RFC 2136 update settings from local RNDC control settings.
Run synchronization only after a successful nsupdate request and only when
DYNDNS_RNDC_SYNC_CLEAN is enabled.

Treat RNDC synchronization failures as warnings because the DNS update has
already been accepted and named retains the authoritative state in its journal.
This commit is contained in:
2026-09-05 13:55:34 +02:00
parent 24d23012c5
commit 4e375b130c
2 changed files with 93 additions and 33 deletions
+55 -31
View File
@@ -241,6 +241,19 @@ _vars_source_check() {
[ -n "$DYNDNS_BIND_ZONE_DIR" ] || _die "DYNDNS_BIND_ZONE_DIR env-var undefined" 5
[ -n "$DYNDNS_BIND_ZONE_FILE" ] || _die "DYNDNS_BIND_ZONE_FILE env-var undefined" 5
case "$DYNDNS_RNDC_SYNC_CLEAN" in
0|1)
;;
*)
_die "DYNDNS_RNDC_SYNC_CLEAN must be either 0 or 1" 5
;;
esac
if [ "$DYNDNS_RNDC_SYNC_CLEAN" = "1" ]; then
[ -n "$DYNDNS_RNDC_SERVER" ] || _die "DYNDNS_RNDC_SERVER env-var undefined" 5
[ -n "$DYNDNS_RNDC_KEY" ] || _die "DYNDNS_RNDC_KEY env-var undefined" 5
fi
_log "Leave the function '_vars_source_check()'..." 1
return 0
@@ -265,12 +278,15 @@ _verify_runtime_init() {
|| command -v "$DYNDNS_DB_PROGRAM" >/dev/null 2>&1 \
|| _die "Database client '$DYNDNS_DB_PROGRAM' not available. $help_note" 5
command -v rndc >/dev/null 2>&1 || _die "rndc not available. $help_note" 5
command -v sed >/dev/null 2>&1 || _die "sed not available. $help_note" 5
command -v grep >/dev/null 2>&1 || _die "grep not available. $help_note" 5
command -v awk >/dev/null 2>&1 || _die "awk not available. $help_note" 5
command -v nsupdate >/dev/null 2>&1 || _die "nsupdate not available. $help_note" 5
if [ "$DYNDNS_RNDC_SYNC_CLEAN" = "1" ]; then
command -v rndc >/dev/null 2>&1 || _die "rndc not available. $help_note" 5
fi
_log "Leave the function '_verify_runtime_init()'..." 1
return 0
} #=> _verify_runtime_init()
@@ -371,6 +387,16 @@ _vars_setup() {
set_var DYNDNS_BIND_ZONE_FILE_SIGNED "$DYNDNS_BIND_ZONE_DIR/$DYNDNS_BIND_ZONE.zone.signed"
set_var DYNDNS_BIND_ZONE_TTL 300
# RNDC post-update zone synchronization defaults
#
# The RNDC control interface is separate from the authoritative DNS
# listener used by nsupdate. Keep the default disabled so existing
# installations do not gain a new administrative post-update action
# unless it is explicitly enabled in their vars file.
set_var DYNDNS_RNDC_SERVER "127.0.0.1"
set_var DYNDNS_RNDC_KEY ""
set_var DYNDNS_RNDC_SYNC_CLEAN 0
# Create a per-run temp directory name. The directory itself may be created
# lazily later by dyndns_mktemp().
if [ -z "$DYNDNS_TEMP_DIR_session" ]; then
@@ -1297,26 +1323,11 @@ _zone_updates_apply() {
return 1
}
_log "Check the zone '$DYNDNS_BIND_ZONE' for DNSSEC capabilities..." 1
if [ -f "$DYNDNS_BIND_ZONE_FILE_SIGNED" ]; then
_log "The zone '$DYNDNS_BIND_ZONE' uses DNSSEC..." 1
zone_uses_dnssec=1
else
_log "The zone '$DYNDNS_BIND_ZONE' does not use DNSSEC..." 1
zone_uses_dnssec=0
fi
{
printf 'debug\n'
printf 'server %s\n' "$DYNDNS_BIND_SERVER"
printf 'zone %s.\n' "$DYNDNS_BIND_ZONE"
if [ "$zone_uses_dnssec" -eq 1 ]; then
printf '; Move %s from secure to insecure temporarily\n' "$DYNDNS_BIND_ZONE_FILE"
printf 'update delete %s. DNSKEY\n' "$DYNDNS_BIND_ZONE"
printf 'send\n'
fi
awk -F '\t' -v zone_file="$DYNDNS_BIND_ZONE_FILE" '
$1 == "del" {
del_owner[++del_count] = $2
@@ -1375,9 +1386,20 @@ _zone_updates_apply() {
cp "$nsupdate_file" "$DYNDNS_LOG_UPDATE_FILE" 2>/dev/null || true
if ! nsupdate -k "$DYNDNS_BIND_ZONE_KEY" "$nsupdate_file" >/dev/null 2>&1; then
nsupdate_output=$(nsupdate -d -k "$DYNDNS_BIND_ZONE_KEY" "$nsupdate_file" 2>&1)
nsupdate_rc=$?
if [ "$nsupdate_rc" -ne 0 ]; then
{
printf '%s\n' "nsupdate output (exit code: $nsupdate_rc):"
printf '%s\n' "$nsupdate_output"
} >> "$DYNDNS_LOG_FILE"
cp "$nsupdate_file" \
"$DYNDNS_LOG_UPDATE_FILE.failed" 2>/dev/null || true
rm -f "$nsupdate_file"
_log "Zone updates could not be applied." 4
_log "Zone updates could not be applied (nsupdate exit code: $nsupdate_rc)." 4
_log "Leave the function '_zone_updates_apply()'..." 1
return 1
fi
@@ -1397,20 +1419,22 @@ _zone_updates_apply() {
rm -f "$nsupdate_file"
_log "Zone updates were applied successfully." 2
if command -v rndc >/dev/null 2>&1; then
if rndc -s "$DYNDNS_BIND_SERVER" -k "$DYNDNS_BIND_ZONE_KEY" freeze "$DYNDNS_BIND_ZONE" >/dev/null 2>&1; then
_log "The zone '$DYNDNS_BIND_ZONE' was frozen." 2
if rndc -s "$DYNDNS_BIND_SERVER" -k "$DYNDNS_BIND_ZONE_KEY" thaw "$DYNDNS_BIND_ZONE" >/dev/null 2>&1; then
_log "The zone '$DYNDNS_BIND_ZONE' was reloaded." 2
else
_log "Couldn't reload the zone '$DYNDNS_BIND_ZONE'." 4
_log "Leave the function '_zone_updates_apply()'..." 1
return 1
fi
# Synchronize dynamic zone journal changes into the master zone file.
#
# BIND keeps successful RFC 2136 updates in the zone journal until a zone dump
# occurs. sync -clean writes the active journal state to the master zone file
# and removes the journal after a successful synchronization.
#
# A synchronization failure is intentionally non-fatal: nsupdate has already
# succeeded and named keeps the authoritative runtime state in the journal.
if [ "$DYNDNS_RNDC_SYNC_CLEAN" = "1" ]; then
if rndc \
-s "$DYNDNS_RNDC_SERVER" \
-k "$DYNDNS_RNDC_KEY" \
sync -clean "$DYNDNS_BIND_ZONE" >/dev/null 2>&1; then
_log "The zone '$DYNDNS_BIND_ZONE' was synchronized and journals were cleaned." 2
else
_log "Couldn't freeze the zone '$DYNDNS_BIND_ZONE'." 4
_log "Leave the function '_zone_updates_apply()'..." 1
return 1
_log "Zone update succeeded, but rndc sync -clean failed for '$DYNDNS_BIND_ZONE'." 3
fi
fi
+38 -2
View File
@@ -81,7 +81,12 @@
# DNS / BIND
# ------------------------------------------------------------------
# Authoritative DNS server used for update operations
# Authoritative DNS server used for RFC 2136 dynamic update operations.
#
# This address is used by nsupdate on DNS port 53. It must point to the
# authoritative BIND listener and matching view serving DYNDNS_BIND_ZONE.
#
# Do not use a local recursive-only listener or a non-authoritative view.
#set_var DYNDNS_BIND_SERVER "127.0.0.1"
# Service name used when reloading/reconfiguring the name server
@@ -90,7 +95,10 @@
# DNS zone handled by dynDNS
#set_var DYNDNS_BIND_ZONE "example24.com"
# TSIG key file or key identifier used for nsupdate access
# TSIG key file or key identifier used for nsupdate access.
#
# This key should be restricted in BIND with update-policy and must not be
# confused with the RNDC control key configured below.
#set_var DYNDNS_BIND_ZONE_KEY "keyfile"
# Directory containing the primary zone files
@@ -105,6 +113,34 @@
# Default TTL for generated/managed records
#set_var DYNDNS_BIND_ZONE_TTL 300
# ------------------------------------------------------------------
# RNDC POST-UPDATE ZONE SYNCHRONIZATION
# ------------------------------------------------------------------
# Local BIND control endpoint used for optional post-update synchronization.
#
# rndc connects to named's control interface, normally TCP port 953.
# This is independent from DYNDNS_BIND_SERVER, which is used by nsupdate
# against DNS port 53.
#set_var DYNDNS_RNDC_SERVER "127.0.0.1"
# RNDC control key authorized by the named controls {} configuration.
#
# This key is intentionally separate from DYNDNS_BIND_ZONE_KEY. The latter is
# used by nsupdate for RFC 2136 DNS updates, while this key authorizes BIND
# administrative operations such as "rndc sync -clean".
#set_var DYNDNS_RNDC_KEY "/etc/named/rndc.key"
# Synchronize dynamic zone journals after a successful nsupdate request.
#
# 0 = disabled; named handles zone dumps using its normal maintenance schedule.
# 1 = execute "rndc sync -clean <zone>" after a successful DNS update.
#
# A failed synchronization must be logged as a warning only. The dynamic update
# remains successful because named retains the authoritative state in its
# journal until the next successful zone dump.
#set_var DYNDNS_RNDC_SYNC_CLEAN 0
# ------------------------------------------------------------------
# MEMBER DEFAULTS
# ------------------------------------------------------------------