Cloudflare DDNS: multiple bugs in dyndns.class (2.8.1) — UNKNOWN ERROR, 0.0.0.0 cache, no record create
-
We hit several issues with the native Cloudflare Dynamic DNS client on pfSense 2.8.1-RELEASE (amd64). Cloudflare API works from the same firewall via curl/PHP with the same Zone ID + API token, but the built-in client fails and the GUI shows Cached IP 0.0.0.0.
Environment
pfSense 2.8.1-RELEASE, PHP 8.3.19
WAN public IP detected correctly: 103.160.130.243
Cloudflare zone keekar.au, Zone ID in username field, scoped API token (cfat_…) with DNS edit permissions
Multiple Cloudflare DDNS clients (fw, nas, vpn, oscal subdomains)
Symptoms
No-IP / Custom (DuckDNS) clients: cached IP correct
All Cloudflare clients: Cached IP 0.0.0.0, status red / UNKNOWN ERROR
System log examples:
phpDynDNS (fw): UNKNOWN ERROR -
phpDynDNS (fw): PAYLOAD:
Earlier (wrong username format):
Invalid format for X-Auth-Key header
Root causes identified in /etc/inc/dyndns.class
Bug 1 — base64_decode() corrupts API token if password not stored as base64
Location: constructor ~line 381:$this->dnsPass = base64_decode($dnsPass);
GUI-saved passwords are base64-encoded in config.xml (e.g. No-IP). If the password is written as plain text (config restore, automation, write_config() from script), base64_decode() silently mangles a cfat… API token into binary garbage. Cloudflare rejects auth; client logs UNKNOWN ERROR with empty payload.Repro (CLI on firewall):
$token = "cfat_<valid_token>";
$broken = base64_decode($token); // NOT valid base64, but PHP returns ~39 bytes garbage
// Authorization: Bearer $broken → Cloudflare failure
Suggested fix: base64_decode($dnsPass, true) and if false, use $dnsPass as-is; or always normalize on write_config().Bug 2 — No POST/create path; update-only (PUT)
Location: Cloudflare case in _update() ~lines 1178–1240.Flow:
GET /dns_records?name={FQDN}&type=A to find record ID
Only if ID exists: PUT update
If record does not exist: no POST to create; outer curl_exec() still runs
New Cloudflare zones/records cannot be bootstrapped by pfSense alone.Suggested fix: if GET returns empty result[], POST new A/AAAA record.
Bug 3 — _checkStatus() assumes PUT response shape; fails on GET/list JSON
Location: ~lines 2757–2770:$output = json_decode($data);
if ($output->result->content === $this->_dnsIP) { /* success */ }
...
else {
$status = ... "UNKNOWN ERROR" - " . $output->errors[0]->message;
}
When the outer request returns a list response ("result":[]) or empty body, $output->result->content is invalid, $output->errors[0] is undefined → UNKNOWN ERROR - (empty message). Cache is never updated; GUI stays at 0.0.0.0 (initial placeholder from _detectChange()).Suggested fix: handle list vs object responses; treat HTTP 200 PUT with matching result.content as success; clear error messages when errors array is empty.
Bug 4 — Misleading auth modes / docs
If username contains @, code uses legacy X-Auth-Email + X-Auth-Key. Putting a modern API token in the password field yields: Invalid format for X-Auth-Key header.Recommended Cloudflare setup (Zone ID username + Bearer token) works only when username is hex Zone ID (no @). GUI/docs should state clearly: do not use email with API tokens; use Zone ID + token, or email + Global API Key only.
Error text still says "use API Key for password field" which is outdated for token-based auth.
Workaround (what we did)
Custom cron script calling Cloudflare API v4 directly + writing /conf/dyndns_wancloudflare'…'.cache files. Disabled native client by removing <enable> from Cloudflare entries so pfSense does not overwrite cache with failed updates.Related existing issues (different root cause?)
#15557 — UNKNOWN ERROR due to CURLOPT_INTERFACE / bridge member without IP
#12877 — multiple Cloudflare records / timeouts
Our case is not curl error 7; curl to api.cloudflare.com succeeds with the same token when not corrupted by base64_decode().Happy to provide sanitized logs, config.xml snippets (redacted), and test again on request.
-
We ran into a few issues with the native Cloudflare DDNS client on pfSense 2.8.1. The same Zone ID and API token work fine when calling the Cloudflare API directly with curl/PHP, but the built-in client consistently reports
Cached IP 0.0.0.0andUNKNOWN ERROR. After tracing the code, we found what appear to be several problems: API tokens can be corrupted by unconditionalbase64_decode(), Cloudflare updates only existing records (no create path), status checking assumes a specific response format, and the authentication guidance is confusing for API tokens versus Global API Keys. -
@keekar said in Cloudflare DDNS: multiple bugs in dyndns.class (2.8.1) — UNKNOWN ERROR, 0.0.0.0 cache, no record create:
Bug 1 — base64_decode() corrupts API token if password not stored as base64
There where you set the password, Services > Dynamic DNS > Dynamic DNS Clients > Edit
the password is stored in the config using "base64_encode()".
I tens to say this password, as most, if not any, are stored encoded with base64_encode().Why do you think this password is stored 'in clear' in the config ? Is it ?
Privacy Policy · Cookie Policy