ddns-scripts: add API-based registered IP verification for Cloudflare proxied
authorbdk38 <redacted>
Mon, 16 Feb 2026 12:49:45 +0000 (04:49 -0800)
committerFlorian Eckert <redacted>
Mon, 16 Feb 2026 14:27:09 +0000 (15:27 +0100)
records

Problem:
When using Cloudflare with proxy enabled (orange cloud), DNS lookups
return Cloudflare's edge IP instead of the actual origin IP registered
in the dashboard. This causes ddns-scripts to incorrectly detect IP
mismatches, triggering unnecessary updates and potential rate limiting.

Solution:
Add an optional 'use_api_check' configuration option that enables
provider scripts to fetch the registered IP directly via their API,
bypassing DNS lookups.

Changes:
- dynamic_dns_functions.sh: Add API check block to get_registered_ip()
  (~25 lines). When use_api_check is enabled, sources the provider
  script with GET_REGISTERED_IP=1 flag. Falls back to DNS lookup if
  API check is disabled, unsupported, or fails.

- update_cloudflare_com_v4.sh: Add handler for GET_REGISTERED_IP mode
  (~15 lines). Reuses existing cURL setup and authentication to query
  Cloudflare API for actual record content.

- etc/config/ddns: Document use_api_check option

Behavior:
- use_api_check=0 or unset: DNS lookup (existing behavior, no changes)
- use_api_check=1 with API support: API query for registered IP
- use_api_check=1 without API support: Falls back to DNS lookup
- API failure: Gracefully falls back to DNS lookup

Testing:
- Cloudflare (proxied): Correctly retrieves origin IP via API
- Cloudflare (non-proxied): Works correctly
- No-IP: DNS lookup works (no regression)
- IPv4 and IPv6 records tested
- API failure gracefully falls back to DNS

Signed-off-by: Wayne King 244781262+bdk38@users.noreply.github.com
net/ddns-scripts/Makefile
net/ddns-scripts/files/etc/config/ddns
net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_functions.sh
net/ddns-scripts/files/usr/lib/ddns/update_cloudflare_com_v4.sh

index 020c9822963c4ce1234d7fbb9c0a3c9327738433..db405263ede2038bd8d4411db925551ed704cf36 100644 (file)
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
 
 PKG_NAME:=ddns-scripts
 PKG_VERSION:=2.8.2
-PKG_RELEASE:=91
+PKG_RELEASE:=92
 
 PKG_LICENSE:=GPL-2.0
 
index b45855f0d059818ded825b68ce7ab6614ee42770..94bda26558c1660e2cc9cd612909027b5514969f 100644 (file)
@@ -18,6 +18,7 @@ config service "myddns_ipv4"
        option interface        "wan"
        option ip_source        "network"
        option ip_network       "wan"
+       option use_api_check    "0"
 
 config service "myddns_ipv6"
        option update_url       "http://[USERNAME]:[PASSWORD]@your.provider.net/nic/update?hostname=[DOMAIN]&myip=[IP]"
@@ -29,4 +30,4 @@ config service "myddns_ipv6"
        option interface        "wan6"
        option ip_source        "network"
        option ip_network       "wan6"
-
+       option use_api_check    "0"
index 5afa5547c78778b206388f4df92e64dff26f8944..c07a859740e984f5513febfe0cffefaf4be56f36 100644 (file)
@@ -8,6 +8,9 @@
 # extended and partial rewritten
 #.2014-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
 #
+# 2026 Wayne King
+# Added use_api_check option for providers with proxied records (e.g., Cloudflare)
+#
 # function timeout
 # copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh
 # @author Anthony Thyssen  6 April 2011
@@ -985,9 +988,40 @@ get_registered_ip() {
        [ $is_glue -eq 1 -a -z "$BIND_HOST" ] && write_log 14 "Lookup of glue records is only supported using BIND host"
        write_log 7 "Detect registered/public IP"
 
+       # Ensure use_api_check defaults to 0 if not set
+       [ -z "$use_api_check" ] && use_api_check=0
+
        # set correct regular expression
        [ $use_ipv6 -eq 0 ] && __REGEX="$IPV4_REGEX" || __REGEX="$IPV6_REGEX"
 
+       # Attempt API check if enabled
+       if [ "$use_api_check" -eq 1 ]; then
+               local __SCRIPT
+               if [ -n "$update_script" ]; then
+                       __SCRIPT="$update_script"
+               elif [ "$service_name" != "custom" ] && [ -n "$service_name" ]; then
+                       local __SANITIZED
+                       __SANITIZED=$(echo "$service_name" | sed 's/[.-]/_/g')
+                       __SCRIPT="/usr/lib/ddns/update_${__SANITIZED}.sh"
+               fi
+               if [ -n "$__SCRIPT" ] && [ -f "$__SCRIPT" ]; then
+                       write_log 7 "Using provider API for registered IP check via '$__SCRIPT'"
+                       REGISTERED_IP=""
+                       GET_REGISTERED_IP=1
+                       . "$__SCRIPT"
+                       __ERR=$?
+                       unset GET_REGISTERED_IP
+                       if [ $__ERR -eq 0 ] && [ -n "$REGISTERED_IP" ]; then
+                               write_log 7 "Registered IP '$REGISTERED_IP' detected via provider API"
+                               [ -z "$IPFILE" ] || echo "$REGISTERED_IP" > "$IPFILE"
+                               eval "$1=\"$REGISTERED_IP\""
+                               return 0
+                       else
+                               write_log 4 "API check failed (error: '$__ERR') - falling back to DNS lookup"
+                       fi
+               fi
+       fi
+
        if [ -n "$BIND_HOST" ]; then
                __PROG="$BIND_HOST"
                [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -t A"  || __PROG="$__PROG -t AAAA"
index b88672c12c8895ae68e962a9f50cd19d277a5105..e469b599f158a7205858e6f6b48d227586703513 100644 (file)
@@ -6,6 +6,10 @@
 #.based on Ben Kulbertis cloudflare-update-record.sh found at http://gist.github.com/benkulbertis
 #.and on George Johnson's cf-ddns.sh found at https://github.com/gstuartj/cf-ddns.sh
 #.2016-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
+#
+# 2026 Wayne King
+# Added GET_REGISTERED_IP mode to retrieve the actual backend IP from Cloudflare API
+# (DNS lookups return Cloudflare's proxy IP, not the actual registered IP)
 # CloudFlare API documentation, section DNS at https://developers.cloudflare.com/api/resources/dns/
 #
 # This script is parsed by dynamic_dns_functions.sh inside send_update() function
@@ -214,6 +218,22 @@ else
        }
 fi
 
+# Check if we are being called for GET_REGISTERED_IP mode
+# This is set by get_registered_ip() in dynamic_dns_functions.sh
+if [ -n "$GET_REGISTERED_IP" ]; then
+       __RUNPROG="$__PRGBASE --request GET '$__URLBASE/zones/$__ZONEID/dns_records/$__RECID'"
+       cloudflare_transfer || return 1
+       __DATA=$(jsonfilter -i "$DATFILE" -e "@.result.content" 2>/dev/null)
+       if [ -n "$__DATA" ]; then
+               write_log 7 "Registered IP '$__DATA' detected via Cloudflare API"
+               REGISTERED_IP="$__DATA"
+               return 0
+       else
+               write_log 4 "Could not extract IP from Cloudflare API response"
+               return 127
+       fi
+fi
+
 # If dns_record_id is specified, grab the stored IP for that specific record
 # So that the IP checking behavior below works even for domains with multiple IPs
 if [ -n "$dns_record_id" ]; then
git clone https://git.99rst.org/PROJECT