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_DIR" ] || _die "DYNDNS_BIND_ZONE_DIR env-var undefined" 5
[ -n "$DYNDNS_BIND_ZONE_FILE" ] || _die "DYNDNS_BIND_ZONE_FILE 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 _log "Leave the function '_vars_source_check()'..." 1
return 0 return 0
@@ -265,12 +278,15 @@ _verify_runtime_init() {
|| command -v "$DYNDNS_DB_PROGRAM" >/dev/null 2>&1 \ || command -v "$DYNDNS_DB_PROGRAM" >/dev/null 2>&1 \
|| _die "Database client '$DYNDNS_DB_PROGRAM' not available. $help_note" 5 || _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 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 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 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 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 _log "Leave the function '_verify_runtime_init()'..." 1
return 0 return 0
} #=> _verify_runtime_init() } #=> _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_FILE_SIGNED "$DYNDNS_BIND_ZONE_DIR/$DYNDNS_BIND_ZONE.zone.signed"
set_var DYNDNS_BIND_ZONE_TTL 300 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 # Create a per-run temp directory name. The directory itself may be created
# lazily later by dyndns_mktemp(). # lazily later by dyndns_mktemp().
if [ -z "$DYNDNS_TEMP_DIR_session" ]; then if [ -z "$DYNDNS_TEMP_DIR_session" ]; then
@@ -1297,26 +1323,11 @@ _zone_updates_apply() {
return 1 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 'debug\n'
printf 'server %s\n' "$DYNDNS_BIND_SERVER" printf 'server %s\n' "$DYNDNS_BIND_SERVER"
printf 'zone %s.\n' "$DYNDNS_BIND_ZONE" 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" ' awk -F '\t' -v zone_file="$DYNDNS_BIND_ZONE_FILE" '
$1 == "del" { $1 == "del" {
del_owner[++del_count] = $2 del_owner[++del_count] = $2
@@ -1375,9 +1386,20 @@ _zone_updates_apply() {
cp "$nsupdate_file" "$DYNDNS_LOG_UPDATE_FILE" 2>/dev/null || true 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" 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 _log "Leave the function '_zone_updates_apply()'..." 1
return 1 return 1
fi fi
@@ -1397,20 +1419,22 @@ _zone_updates_apply() {
rm -f "$nsupdate_file" rm -f "$nsupdate_file"
_log "Zone updates were applied successfully." 2 _log "Zone updates were applied successfully." 2
if command -v rndc >/dev/null 2>&1; then # Synchronize dynamic zone journal changes into the master zone file.
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 # BIND keeps successful RFC 2136 updates in the zone journal until a zone dump
if rndc -s "$DYNDNS_BIND_SERVER" -k "$DYNDNS_BIND_ZONE_KEY" thaw "$DYNDNS_BIND_ZONE" >/dev/null 2>&1; then # occurs. sync -clean writes the active journal state to the master zone file
_log "The zone '$DYNDNS_BIND_ZONE' was reloaded." 2 # and removes the journal after a successful synchronization.
else #
_log "Couldn't reload the zone '$DYNDNS_BIND_ZONE'." 4 # A synchronization failure is intentionally non-fatal: nsupdate has already
_log "Leave the function '_zone_updates_apply()'..." 1 # succeeded and named keeps the authoritative runtime state in the journal.
return 1 if [ "$DYNDNS_RNDC_SYNC_CLEAN" = "1" ]; then
fi 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 else
_log "Couldn't freeze the zone '$DYNDNS_BIND_ZONE'." 4 _log "Zone update succeeded, but rndc sync -clean failed for '$DYNDNS_BIND_ZONE'." 3
_log "Leave the function '_zone_updates_apply()'..." 1
return 1
fi fi
fi fi
+38 -2
View File
@@ -81,7 +81,12 @@
# DNS / BIND # 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" #set_var DYNDNS_BIND_SERVER "127.0.0.1"
# Service name used when reloading/reconfiguring the name server # Service name used when reloading/reconfiguring the name server
@@ -90,7 +95,10 @@
# DNS zone handled by dynDNS # DNS zone handled by dynDNS
#set_var DYNDNS_BIND_ZONE "example24.com" #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" #set_var DYNDNS_BIND_ZONE_KEY "keyfile"
# Directory containing the primary zone files # Directory containing the primary zone files
@@ -105,6 +113,34 @@
# Default TTL for generated/managed records # Default TTL for generated/managed records
#set_var DYNDNS_BIND_ZONE_TTL 300 #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 # MEMBER DEFAULTS
# ------------------------------------------------------------------ # ------------------------------------------------------------------