esc
↑↓ move ↵ open

DNS64

Synthesising AAAA records so IPv6-only clients can reach IPv4-only services.

[dns64]
enabled  = true
prefixes = ["64:ff9b::/96"]

An IPv6-only client asks for a AAAA record; the service only has an A record. DNS64 (RFC 6147) synthesises a AAAA by embedding the IPv4 address inside a Pref64 prefix, which a NAT64 gateway then translates. Off by default.

When synthesis happens

When a client’s AAAA query returns NOERROR with no data, or any nonzero RCODE other than NXDOMAIN, sdns issues an A query for the same name and synthesises one AAAA per (A record, prefix) pair.

In these cases no A lookup is made and nothing is synthesised; the reply goes to the client as the resolver produced it:

  • NXDOMAIN. The name does not exist. Synthesising anything would invent it.
  • SERVFAIL carrying a DNSSEC-failure Extended DNS Error. DNS64 must never mask a validation failure (RFC 6147 §5.5), so a signed name that failed to validate stays failed.
  • Clients that set RD=0 or CD=1. Both say “do not do anything clever on my behalf”, and DNS64 is the definition of clever.
  • Truncated replies, and replies with no question section.
  • Cached failures. A failure served from the RFC 9520 failure cache (EDE 13, Cached Error) must not trigger a new outgoing query while its backoff runs, so no A lookup is made.
  • Failures local to the request, including one that hit the resolution attempt limit. Asking again for the A record would only repeat them.
  • SERVFAIL from the recursion firewall. When the work budget ends a resolution, the client gets that SERVFAIL rather than a second lookup.

A pass-through is not always byte for byte. AAAA records inside exclude_aaaa_networks are removed even when the reply is otherwise passed through (see below). When that removes anything, AD is cleared, and if the upstream reply had AD set, EDE 4 (Forged Answer) is attached. The same holds when the removal leaves no AAAA and the A lookup then fails: the stripped reply that goes out instead carries no AD either.

Prefixes

prefixes = ["64:ff9b::/96"]

Lengths must be one of /32, /40, /48, /56, /64 or /96 (RFC 6052). List several to synthesise one AAAA per prefix, so a client sees every reachable NAT64 path in a single reply.

64:ff9b::/96 is the IANA Well-Known Prefix and the usual choice. If DNS64 is enabled with no prefixes configured, that is the runtime default.

Scoping

client_networks = ["2001:db8:1::/48"]
exclude_zones   = ["example.com."]

client_networks limits synthesis to given client CIDRs; empty means every client. Restrict it to your IPv6-only subnets so dual-stack clients keep their original answers.

exclude_zones names zones that must never be synthesised. The match is by suffix: "example.com." covers the zone and everything under it.

Address exclusions

exclude_aaaa_networks = ["::ffff:0:0/96"]
exclude_a_networks    = ["10.0.0.0/8", "192.168.0.0/16"]  # list shortened here

exclude_aaaa_networks filters AAAA records out of the upstream response before deciding between pass-through and synthesis (RFC 6147 §5.1.4). The default is the IPv4-mapped range: an upstream that wrongly returns ::ffff:... AAAAs is treated as having returned no AAAA at all, so sdns synthesises a routable address from the corresponding A instead of handing the client something unusable.

exclude_a_networks lists IPv4 networks not to synthesise from when the Well-Known Prefix is active, since RFC 6052 §3.1 forbids embedding non-global addresses in it. The shipped defaults mirror the IANA Special-Purpose Address Registry. A list you set replaces those defaults rather than adding to them, so copy the defaults into it when you only mean to extend them; an explicit [] turns the exclusion off. Operator-chosen network-specific prefixes ignore this list, since the constraint is specific to the Well-Known Prefix.

Reverse lookups

A PTR query for an ip6.arpa name inside one of the configured prefixes is translated (RFC 6147 §5.3.1). sdns extracts the embedded IPv4 address and answers with a CNAME to the matching in-addr.arpa name, with a TTL of 600 seconds. It then looks up that PTR itself and, if the lookup succeeds, appends the PTR records to the same reply; if it fails, the client gets the CNAME alone and can follow it. Two failures are the exception and answer SERVFAIL instead: a lookup the recursion firewall stopped for exceeding its work budget, and one that hit the request’s resolution attempt limit.

The same gates apply as for AAAA: RD=1, CD=0 and a client inside client_networks. Under the Well-Known Prefix an address in exclude_a_networks is not translated. An ip6.arpa name outside every configured prefix resolves normally.

Watching it

dns64_synthesised_total         AAAA queries answered with synthesised records
dns64_passthrough_total         queries left untouched, by reason
dns64_a_lookup_failures_total   secondary A lookups that gave nothing usable, by reason
dns64_ptr_translated_total      ip6.arpa PTR queries answered with a CNAME

dns64_passthrough_total with reason="aaaa_present" is the normal case: the name has a real AAAA. The internal, no_rd, cd_bit and client_excluded reasons are counted before the query type is checked, so they include queries of every type, not only AAAA.