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