feat(resolver): filter_aaaa for IPv4-only networks #119

Merged
razvandimescu merged 7 commits from feat/filter-aaaa into main 2026-04-19 12:31:28 +08:00
razvandimescu commented 2026-04-19 00:52:27 +08:00 (Migrated from github.com)

Closes #112.

Summary

  • Opt-in filter_aaaa flag under [server] (default false). When enabled, AAAA queries short-circuit to NODATA (NOERROR + empty answer) so Happy Eyeballs v2 clients don't stall on a v6 address they can't use (RFC 8305 / RFC 6555).
  • Also strips the ipv6hint SvcParam (key 6) from HTTPS/SVCB answers per RFC 9460, so Chrome ≥103, Firefox, and Safari don't bypass the AAAA filter via the HTTPS record path. New src/svcb.rs module with a minimal RDATA walker — only handles what we need to strip the hint.
  • Local data is preserved: overrides, zones, the .numa service proxy, and the blocklist sinkhole all keep their v6 records. The filter only intercepts the cache/forward/recursive path.

Why NODATA, not NXDOMAIN

RFC 2308 §2.2: NOERROR + empty answer means "this name exists, but not for this type." Using NXDOMAIN would incorrectly signal "name doesn't exist at all" and break the subsequent A query (RFC 8020).

Prior art

  • dnsdist: addAction(QTypeRule(DNSQType.AAAA), RCodeAction(DNSRCode.NOERROR))
  • dnsmasq: filter-AAAA
  • Unbound: private-address ::/0 (different mechanism, same effect)

Test plan

  • cargo test --lib — 313 passed
  • cargo build clean
  • 6 new tests cover: default-off config, config parse, NODATA for AAAA, A queries untouched, override wins over filter, ipv6hint stripped from cached HTTPS record
  • Manual smoke on IPv4-only network: verify dig AAAA example.com @127.0.0.1 -p 5335 returns NOERROR/0 answers and dig HTTPS cloudflare.com shows no ipv6hint= param
Closes #112. ## Summary - Opt-in `filter_aaaa` flag under `[server]` (default `false`). When enabled, AAAA queries short-circuit to **NODATA** (NOERROR + empty answer) so Happy Eyeballs v2 clients don't stall on a v6 address they can't use (RFC 8305 / RFC 6555). - Also strips the `ipv6hint` SvcParam (key 6) from HTTPS/SVCB answers per RFC 9460, so Chrome ≥103, Firefox, and Safari don't bypass the AAAA filter via the HTTPS record path. New `src/svcb.rs` module with a minimal RDATA walker — only handles what we need to strip the hint. - Local data is **preserved**: overrides, zones, the `.numa` service proxy, and the blocklist sinkhole all keep their v6 records. The filter only intercepts the cache/forward/recursive path. ## Why NODATA, not NXDOMAIN RFC 2308 §2.2: NOERROR + empty answer means "this name exists, but not for this type." Using NXDOMAIN would incorrectly signal "name doesn't exist at all" and break the subsequent A query (RFC 8020). ## Prior art - dnsdist: `addAction(QTypeRule(DNSQType.AAAA), RCodeAction(DNSRCode.NOERROR))` - dnsmasq: `filter-AAAA` - Unbound: `private-address ::/0` (different mechanism, same effect) ## Test plan - [x] `cargo test --lib` — 313 passed - [x] `cargo build` clean - [x] 6 new tests cover: default-off config, config parse, NODATA for AAAA, A queries untouched, override wins over filter, `ipv6hint` stripped from cached HTTPS record - [ ] Manual smoke on IPv4-only network: verify `dig AAAA example.com @127.0.0.1 -p 5335` returns NOERROR/0 answers and `dig HTTPS cloudflare.com` shows no `ipv6hint=` param
Sign in to join this conversation.