<!doctype html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div class="default-style" style="">
<div class="default-style" style="">Hi all,</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">I'm running into behaviour I can't explain and I'd appreciate a second pair of eyes before I assume it's a bug. I could easily be doing something obvious and wrong.</div>
<div class="default-style" style=""> </div>
<span style="text-decoration: underline;"><strong>Setup</strong></span>
<div class="default-style" style="">
<ul>
<li>PowerDNS Recursor 5.4.0 in Docker (image: powerdns/pdns-recursor-54:latest)</li>
<li>Host: Debian 13 VM (UmbrelOS - using the Portainer app to manage Docker), Docker host network mode</li>
<li>Full recursion, no forwarders</li>
<li>`dnssec.validation: validate`, built-in root trust anchor</li>
<li>Root zone loaded via `zonetocaches` from Internic (ZONEMD validated Secure at startup)</li>
<li>7 RPZ zones loaded via `rpzPrimary` from IPFire DBL (xfr.dbl.ipfire.org) — ads, gambling, malware, phishing, piracy, smart-tv, violence. Open AXFR, no TSIG.</li>
<li>`extended_resolution_errors: true`, per-RPZ `extendedErrorCode: 15` and `extendedErrorExtra` set</li>
</ul>
Full YAML config and docker-compose.yml available on request.
</div>
<div class="default-style" style=""> </div>
<strong><span style="text-decoration: underline;">Symptom</span></strong>
<div class="default-style" style="">DNSSEC-bogus domains return NOERROR with data instead of SERVFAIL:</div>
<div class="default-style" style="">
<code> $ dig @<host_ip> dnssec-failed.org +dnssec</code>
<br>
<code> ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18515</code>
<br>
<code> ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1</code>
<br>
<code> ;; ANSWER SECTION:</code>
<br>
<code> dnssec-failed.org. 218 IN A 96.99.227.255</code>
<br>
<code> dnssec-failed.org. 218 IN RRSIG A 13 2 300 ...</code>
</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">Confirmed validation mode is actually loaded:</div>
<div class="default-style" style="">
<code> $ rec_control get-parameter dnssec</code>
<br>
<code> dnssec:</code>
<br>
<code> validation: validate</code>
<br>
<code> disabled_algorithms:</code>
<br>
<code> - '1'</code>
<br>
<code> - '3'</code>
<br>
<code> - '5'</code>
<br>
<code> - '6'</code>
<br>
<code> - '7'</code>
<br>
<code> - '12'</code>
<br>
<code> log_bogus: true</code>
</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">Query log shows `validationState="Indeterminate"` for the query, not Bogus.</div>
<div class="default-style" style=""> </div>
<strong><span style="text-decoration: underline;">Root cause (as far as I can tell)</span></strong>
</div>
<div class="default-style" style="">
<div class="default-style" style="">A rec_control trace-regex 'dnssec-failed.org' trace shows validation tries to fetch the root DNSKEY and hits the first RPZ in the config list:</div>
<div class="default-style" style="">
<code> .|DNSKEY:: RPZ Hit; PolicyName=ads.rpz.ipfire.org; Trigger=.; Hit=; Type=QName; Kind=Local Data</code>
<br>
<code> .: Retrieved 0 DNSKeys, state is Indeterminate</code>
<br>
<code> org: Updating validation state with cache content for org to Indeterminate</code>
<br>
<code> dnssec-failed.org: Updating validation state with cache content for dnssec-failed.org to Indeterminate</code>
<br>
<code> dnssec-failed.org: Validation status is Indeterminate</code>
</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">Confirming this from a client:</div>
<div class="default-style" style="">
<code> $ dig @<host_ip> . NS</code>
<br>
<code> ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14776</code>
<br>
<code> ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1</code>
<br>
<code> ; EDE: 15 (Blocked): (Blocked: advertising)</code>
<br>
<code> ;; QUESTION SECTION:</code>
<br>
<code> ;. IN NS</code>
</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">Zero-answer NOERROR on `. NS`, tagged by the first RPZ. This is not specific to the ads list — I tested by removing ads.rpz.ipfire.org from config, and whichever RPZ moved into first position then shows the same behaviour on root queries. So every IPFire RPZ I'm loading exhibits this, which makes me think it's something about the way they're structured or the way PDNS loads/evaluates them, rather than a specific zone being poisoned.</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">What dump-rpz shows:</div>
<div class="default-style" style="">
<code> $ rec_control dump-rpz ads.rpz.ipfire.org /tmp/ads.txt</code>
<br>
<code> $ head -5 /tmp/ads.txt</code>
<br>
<code> ads.rpz.ipfire.org. IN SOA primary.dbl.ipfire.org. hostmaster.ipfire.org. 1776630606 3600 600 3600000 60</code>
<br>
<code> stbg.stanbicbank.co.zw.ads.rpz.ipfire.org. 60 IN CNAME .</code>
<br>
<code> stats.zpl.zone.ads.rpz.ipfire.org. 60 IN CNAME .</code>
<br>
<code> *.a.userscript.zone.ads.rpz.ipfire.org. 60 IN CNAME .</code>
<br>
<code> a.userscript.zone.ads.rpz.ipfire.org. 60 IN CNAME .</code>
</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">Apex has only SOA (NS stripped). Nothing in the dumped zone should produce a `.` QName trigger as far as I can read it — but the trace clearly shows one matching, with `Hit=` empty.</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">The IPFire source zone at xfr.dbl.ipfire.org contains at its apex: SOA, NS, and an `_info IN TXT` record one label below the apex. Nothing else unusual that I can see.</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">
<span style="text-decoration: underline;"><strong>RPZ config (abbreviated)</strong></span>
</div>
<div class="default-style" style="">
<code> recursor:</code>
<br>
<code> rpzs:</code>
<br>
<code> - name: ads.rpz.ipfire.org</code>
<br>
<code> addresses: [ 'xfr.dbl.ipfire.org:53' ]</code>
<br>
<code> policyName: ads.rpz.ipfire.org</code>
<br>
<code> extendedErrorCode: 15</code>
<br>
<code> extendedErrorExtra: 'Blocked: advertising'</code>
<br>
<code> - name: gambling.rpz.ipfire.org</code>
<br>
<code> addresses: [ 'xfr.dbl.ipfire.org:53' ]</code>
<br>
<code> policyName: gambling.rpz.ipfire.org</code>
<br>
<code> extendedErrorCode: 15</code>
<br>
<code> extendedErrorExtra: 'Blocked: gambling'</code>
<br>
<code> # ... 5 more in same pattern</code>
<br>
<code> system_resolver_ttl: 300</code>
<br>
<code> extended_resolution_errors: true</code>
</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">No `defpol` is set on any zone.</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">
<div class="default-style" style="">
<div class="default-style" style="">Am I configuring these RPZs wrong in a way that causes `.` to match as a QName trigger? I can't find anything in docs or the RFC that would point there.</div>
</div>
<div class="default-style" style="font-family: -apple-system, BlinkMacSystemFont, helvetica, sans-serif;"> </div>
</div>
<div class="default-style" style="">Happy to provide the full YAML, full trace log, full zone dump, or anything else useful. I didn't attach them here to keep the email readable.</div>
<div class="default-style" style=""> </div>
<div class="default-style" style="">
Thanks,
<br>
Chris
</div>
</div>
</body>
</html>