do it: dig reader
Filtering resolvers and DNSSEC
Three shapes of a block#
A policy-implementing resolver (RFC 9499 section 6) answers a blocked name one of three ways. Each operator documents its own test name; these two were read on the operator's own page and asked over DoH from a seat with nothing in the path. Both names are in unsigned zones, measured the same day: asked over DoH at 1.1.1.1 with validation off, the resolver passes on the parents' denials, no DS for testcategory.com (.com's SOA in the authority section) and none for isitblocked.org (.org's SOA with its signed NSEC3 denial), and isitblocked.org is a live registered name (SOA ns1073.ui-dns.biz, A 74.208.236.124 at 1.1.1.1 and at 8.8.8.8), so the NXDOMAIN below is the filter's, not the registry's.
Asks the malware-blocking resolver for its own documented test name, with DNSSEC asked for.
dig +https -4 @1.1.1.2 malware.testcategory.com A +dnssecGO NOERROR with 0.0.0.0 and EDE 16: the filter's own block, read as it is
NO GO the real 104.18.x addresses from this resolver, which means the block did not apply
A sinkhole: the operator's page says blocked names "return the address 0.0.0.0", and the answer carries EDE 16 Censored (RFC 8914 section 4.17). No RRSIG: the zone is unsigned, and the answer is the filter's, not the zone's. The same name at the unfiltered sibling, 1.1.1.1, answers 104.18.4.35 and 104.18.5.35.
Asks Quad9 for its documented test name.
dig +https -4 @dns.quad9.net isitblocked.org A +dnssecGO NXDOMAIN with EDE 17 and AUTHORITY: 0: the filter's synthesised denial
NO GO an address for the name, which means the block did not apply
A synthesised denial: NXDOMAIN with AUTHORITY: 0, EDE 17 Filtered, as the operator's FAQ describes. Read the flags: ad is set on this NXDOMAIN, and there is no NSEC or NSEC3 record under it. A validator behind this resolver cannot accept that denial, because proving a name does not exist needs the signed denial records (RFC 4035 section 5.4; with NSEC3, the closest encloser proof of RFC 5155 section 8.4). The flag is the filter's statement, not a proof.
The third shape is a block-page address: a real-looking A record pointing at the operator's own web server. It reads like a normal answer until you compare it with an unfiltered resolver.
What a validating forwarder does with it#
- For a name in an unsigned zone, nothing: there is no chain to check, so every forwarder passes the block through. The test names above are both in unsigned zones.
- For a name in a signed zone, the made-up answer has no RRSIG that verifies and no NSEC or NSEC3 proof. The validator marks it Bogus, and a Bogus answer to a recursive query is RCODE 2, server failure: "If the validation was being done to service a recursive query, the name server MUST return RCODE 2 to the originating client" (RFC 4035 section 5.5).
- A forwarder that can only forward stops there: the client gets SERVFAIL. The block holds, as a failure.
- A forwarder allowed to resolve on its own can fall back to resolving the name from the root itself, validate the real records and hand them to the client with AD set (RFC 4035 section 3.2.3 on the flag). The block is bypassed, and the client is told the answer is authentic. ISC's knowledge base describes the mode as trying the forwarders first and, "if they don't respond or don't respond quickly enough", attempting iterative resolution next; whether a Bogus answer from the forwarder counts as no answer is each resolver's own rule, and this page cites no source for it. The three digs below show what yours does. A forwarder with a second upstream that does not filter gives the same digs without validating at all.
- A forwarder that does not validate passes the made-up answer through unchanged. The block holds.
The CD bit separates a validation failure from a dead resolver: "When the CD bit is set, the recursive name server SHOULD, if possible, return the requested data to the originating resolver, even if the recursive name server's local authentication policy would reject the records in question" (RFC 4035 section 3.2.2). Ask the same question with +cd: if the made-up answer comes back, validation caused the SERVFAIL.
The five readings#
A block shape is one of two: a made-up denial (NXDOMAIN with EDE 15, 16, 17, 18 or 35, or with no SOA of a zone the name, or the last target of its CNAME chain, is under) or a sinkhole (NOERROR with 0.0.0.0 or :: and no RRSIG beside it; a loopback address, 127/8 or ::1, counts only with a filter code, since some zones really point a name at loopback). Validated records are an answer with AD set and an RRSIG over its A or AAAA records: AD alone is the resolver's statement, and a filter can set it on its own data, as the capture above shows. A signed denial is NXDOMAIN carrying the SOA of a parent zone (never the name's own: a name that owns an SOA exists) and NSEC or NSEC3 records with an RRSIG over them. A denial without them reads "not read" even three times over, because the digs cannot tell it from a filter that forges a parent's SOA. The reader checks that the records are present, not that the signatures verify. Four answers are read as neither and get "not read": an answer the resolver marks as filtered (an EDE filter code) that is neither a sinkhole nor a denial, which a block page looks like, and also a CNAME chain whose denial carries an SOA that is not over the chain's target, a denial with its authority section hidden (+noauthority, or a tool that prints none), and a sinkhole or loopback address with no filter code asked without +dnssec, where an RRSIG could not have shown.
All three digs have to be in before any label; each label needs its whole row:
| label | the forwarder | the forwarder with +cd | the filter |
|---|---|---|---|
| bypassed | validated records (AD) | a block shape | a block shape |
| held, as a failure | SERVFAIL | a block shape | a block shape |
| held, denied or held, sinkholed | that block shape | the same block shape | the same shape, or another block shape |
| not a block | validated records (AD, and an RRSIG over them) | the same records | the same validated records |
| not a block | a signed denial | a signed denial | a signed denial |
Anything else reads "not read", with the three readings and one next step. A block at the forwarder beside validated records at the resolver in dig 3 is one of these: a forwarder with its own blocklist in front of an unfiltered upstream gives it, and so does a dig 3 that names another resolver. "Bypassed" is a reading, not a cause: validated records reached you while the filter blocks, which a validating forwarder that resolves on its own gives, and so does a forwarder with a second upstream that does not filter. +cd answers = validation did it, not a dead resolver.
A block-page address reads like a real answer in these three digs, and two resolvers answer a CDN name differently without any block; the dig reader's step 2 asks an unfiltered resolver the same question, which tells those apart. An answer without AD or without an RRSIG over its records, or two validated answers that differ, gets no label from the three alone.
A forwarder that is itself the filter (one box doing both) has no third dig: the reader refuses a third paste answered by the same server as the first with "not read", because there is no second box in front of it to bypass. The first dig alone shows that box's block shape.
The three digs#
Run them on a machine behind the forwarder, then paste all three into the dig reader: it labels the set with the table above.
dig <blocked name> A +dnssec @<forwarder>
dig <blocked name> A +dnssec +cd @<forwarder>
dig <blocked name> A +dnssec @<filtering resolver>The blocked name has to be one your filter blocks and whose zone is signed; the DNSSEC chain check tells you whether it is. A public test name will not do: both above are unsigned, as measured.
What to do#
Decide where validation happens and let one box do it. Either the filtering resolver validates and the forwarder in front of it trusts its answers, or the forwarder validates and nothing in front of it filters. A validating forwarder in front of a filter you want to hold is the one arrangement that fails or bypasses, depending on whether it is allowed to resolve on its own.
See also#
- RFC 4035, sections 3.2.2 (CD), 3.2.3 (AD), 5.4 (authenticated denial), 5.5 (Bogus is RCODE 2)
- RFC 5155 section 8.4, validating a Name Error with NSEC3
- RFC 9499 section 6, forwarder, forwarding, policy-implementing resolver
- RFC 8914, Extended DNS Errors: 6 DNSSEC Bogus, 15 Blocked, 16 Censored, 17 Filtered
- Extended DNS Errors, DNSSEC troubleshooting, SERVFAIL