Whisper · Docs
Standards

Reverse DNS & FCrDNS

A forward record is a claim; a reverse record is a receipt — and forward-confirmed reverse DNS (FCrDNS) forces an attacker to forge two independent DNS trees that agree, not one.

Any service that accepts inbound agent connections — a webhook, an MCP server, an API gateway — eventually asks "who is 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4, really?" A self-reported header or a copyable bearer token is worthless. An answer resolved from the address itself, through a chain the caller doesn't control, is real. This page is the mechanism: how a /128 becomes a name in ip6.arpa, how PTR and forward records are made to agree, and why DNSSEC turns "probably" into "cannot be spoofed."

Why reverse DNS exists at all

Forward DNS answers "what address does this name have?" Reverse DNS answers the opposite question — "what name does this address have?" — and it exists because the two directions are asymmetric in a load-bearing way: the operator who controls the address block is the only one who can publish its reverse record. Cloudflare can't put a PTR on your IP; only whoever holds the allocation (here, Whisper, announcing 2a04:2a01::/32 as AS219419) can. That's what makes reverse DNS a genuine attestation of network-level control, not just a DNS label someone typed into a zone file they also control.

Reverse DNS for IPv6 lives under the special domain ip6.arpa (RFC 3596), a mirror image of in-addr.arpa for IPv4 (RFC 1035 §3.5). The lookup type is PTR (RFC 1035 §3.3.12) — the only record type ever answered under .arpa.

The nibble format

IPv6 reverse names are built from the address's nibbles (4-bit hex digits), written one label per nibble, in reverse order, under ip6.arpa. For the demo agent's address:

2a04:2a01:eb5a:ca74:cef2:002a:323d:40d4

Expand every group to 4 hex digits, concatenate all 32 nibbles, then read them back to front, one per DNS label:

4.d.0.4.d.3.2.3.a.2.0.0.2.f.e.c.4.7.a.c.a.5.b.e.1.0.a.2.4.0.a.2.ip6.arpa.

That's the full /128 — 32 labels, each a single hex digit, terminated by ip6.arpa.. It looks absurd next to a hostname, and that's the point: it's not meant to be typed, it's meant to be generated by software walking the bits, which is exactly what both dig -x and the resolver do.

Zone-cut anatomy. Whisper announces 2a04:2a01::/32 — the first 32 bits, i.e. the first 8 nibbles (2a04:2a012,a,0,4,2,a,0,1). Reversed, that's the zone Whisper is delegated: 1.0.a.2.4.0.a.2.ip6.arpa. Everything under it — the remaining 24 nibble-labels down to the individual /128 — is Whisper's dynamic authoritative reverse zone, synthesized per allocation. This is the reverse-DNS mirror of the forward delegation described in Graph-first resolution and Two servers, one truth.

The PTR record itself is trivial once you're at the right name — one owner, one target:

4.d.0.4.d.3.2.3.a.2.0.0.2.f.e.c.4.7.a.c.a.5.b.e.1.0.a.2.4.0.a.2.ip6.arpa. 300 IN PTR acef2002a323d40d4.<tenant>.agents.whisper.online.

FCrDNS: reverse, then forward, and they must agree

A PTR record alone proves nothing — anyone who controls a reverse zone can point any address at any name, true or not. Forward-confirmed reverse DNS closes that gap with one extra step, and it's the same check every mail server on earth already runs before accepting an SMTP connection (RFC 1912 §2.1 has told operators to keep PTR and forward records matched since 1996, and countless anti-spam BCPs assume it, though there's no single "FCrDNS RFC" — it's a convention built from RFC 1035 primitives):

  1. Reverse-lookup the source address → get a candidate name (PTR).
  2. Forward-lookup that candidate name's AAAA → get a candidate address.
  3. Compare. If the forward answer matches the original source address exactly, the name is confirmed. If it doesn't — or the forward lookup returns nothing, or NXDOMAIN — the name is unverified and must be discarded.

The asymmetry is the whole security property: forging step 1 alone (a rogue PTR) is easy for anyone who controls a reverse zone. Forging steps 1 and 2 together — making an attacker-chosen name's forward record also resolve back to the victim's address — requires controlling the victim's own forward zone, which they don't. FCrDNS converts "an address claims a name" into "an address's own operator, independently, in the forward tree, backs the same claim."

Why DNSSEC is what makes it spoof-proof

Plain FCrDNS defeats a casual PTR forgery, but it does not defeat on-path spoofing — an attacker sitting between the resolving client and the DNS server can forge UDP responses (classic Kaminsky-style cache poisoning, or a rogue resolver) for either leg of the chain, PTR or AAAA. DNSSEC (RFC 4033–4035) closes that: every PTR and AAAA record in Whisper's zones carries an RRSIG, validated up a chain of DS/DNSKEY records to the IANA root — the same trust anchor your OS or resolver already ships. A validating resolver sets the AD (Authentic Data) bit in the response header only if every record in the answer, and every record in the chain that vouches for it, checked out cryptographically.

That means the FCrDNS check above is only as strong as its weakest unsigned leg. Require AD=1 on both the PTR and the AAAA lookup, or a validating resolver in front of your stub, and forging either leg now requires forging a DNSSEC signature — not a UDP packet.

kdig +dnssec -x 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
# ;; flags: qr rd ra ad;  <-- ad = DNSSEC-validated, not just "an answer came back"

With stock tools: the loop in bash

No SDK, no account — this is five lines any operator can drop into a webhook handler or an nginx map:

SRC="2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4"

name=$(dig +short -x "$SRC")                       # 1. reverse
echo "$name"                                        # -> acef2002a323d40d4.<tenant>.agents.whisper.online.

fwd=$(dig +short AAAA "$name")                      # 2. forward on the candidate name
[ "$fwd" = "$SRC" ] && echo "FCrDNS: match" || echo "FCrDNS: FAIL"

# 3. require DNSSEC validation on both legs, not just a match
kdig +dnssec -x "$SRC"        | grep -q 'flags:.* ad' && \
kdig +dnssec AAAA "$name"     | grep -q 'flags:.* ad' && \
echo "spoof-proof: both legs DNSSEC-validated"

Every tool here — dig, kdig — ships with bind-utils/knot-dnsutils. Nothing to install, nothing to trust beyond the DNSSEC root your resolver already anchors on.

With Whisper: one call, chain included

whisper verify runs the same reverse→forward→DNSSEC chain server-side and returns a verdict instead of three commands to interpret by hand:

whisper verify 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
# PASS  acef2002a323d40d4.<tenant>.agents.whisper.online.  (AD=1 both legs)

For a proof that doesn't ask you to trust the Whisper API's own verdict — it walks the chain itself, from the DNSSEC root down through DANE-EE and a signed transparency ledger entry — see whisper verify --trustless, covered in full in Verify an agent. The keyless RDAP mirror of the same check:

curl -s https://rdap.whisper.online/verify-identity/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 \
  | jq '{is_whisper_agent, fqdn, dane_ok}'
# { "is_whisper_agent": true, "fqdn": "acef2002a323d40d4.<tenant>.agents.whisper.online.", "dane_ok": true }

Using it as an allowlist key

FCrDNS gives you a confirmed name, not just a raw address — which means you can authorize by name, at any granularity: one agent, one tenant's whole fleet, or every verified Whisper agent on the internet. That pattern — exact / subdomain / wildcard matching on the confirmed name, wired into nginx, Envoy, or app middleware — is the entire subject of Access control. Reverse DNS is the primitive; access control is what you build on it.

Reverse DNS with DNSSEC turns "trust me, I'm agent X" into a claim any resolver on the internet can independently check in two lookups — no bearer token, no shared secret, nothing to leak.


Next: Access control — authorize inbound agents on the confirmed name · DANE & DNSSEC — the signature chain that makes every record here spoof-proof