Skip to content

feat: filter ipv6hint in HTTPS/SVCB RR when AAAA is filtered - #2070

Merged
0xERR0R merged 4 commits into
mainfrom
feat/filter-https-ipv6hint
May 22, 2026
Merged

0xERR0R merged 4 commits into
mainfrom
feat/filter-https-ipv6hint

Conversation

@0xERR0R

@0xERR0R 0xERR0R commented May 22, 2026

Copy link
Copy Markdown
Owner

Closes #1706

Problem

When filtering.queryTypes includes AAAA (to keep clients off IPv6), Blocky still returns HTTPS/SVCB records (RFC 9460) that carry an ipv6hint SvcParam. Modern clients use those hints to connect directly over IPv6, bypassing the AAAA filter entirely:

www.cloudflare.com. 402 IN HTTPS 1 . alpn="h3,h2" ipv4hint=... ipv6hint=2606:4700::6810:7b60,...

AdGuard Home strips these hints; Blocky did not.

Change

FilteringResolver now, only when AAAA is among the filtered query types, removes the ipv6hint SvcParam from every HTTPS and SVCB answer record. All other SvcParams (alpn, ipv4hint, ECH, …) are preserved, so HTTP/3/2 negotiation and IPv4 hints keep working; a record left with no hints simply makes the client fall back to an A lookup.

Notes

  • Cache-safe: responses reaching FilteringResolver are never shared with the cache — CachingResolver unpacks a fresh *dns.Msg on every hit and stores a packed copy on miss — so in-place mutation is safe and matches the existing setTTLInCachedResponse pattern.
  • Handles both HTTPS (type 65) and SVCB (type 64); non-SVCB answers are untouched.
  • Behavior is automatic (no new config), consistent with how AAAA filtering already works.

Tests

  • New Ginkgo specs in resolver/filtering_resolver_test.go: strips ipv6hint from HTTPS while keeping alpn/ipv4hint; strips from SVCB; leaves non-SVCB answers alone; keeps the hint when AAAA is not filtered.

Docs

  • Documented under the Filtering section in docs/configuration.md.

When `filtering.queryTypes` contains AAAA, also remove the ipv6hint SvcParam
from HTTPS/SVCB (RFC 9460) answers, so clients can't reach IPv6 endpoints
advertised in those records and bypass the AAAA filter. Other SvcParams
(alpn, ipv4hint, ...) are left untouched.

Closes #1706
Copilot AI review requested due to automatic review settings May 22, 2026 10:23
@codecov

codecov Bot commented May 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.45455% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.34%. Comparing base (fdcf351) to head (dd1a3c0).

Files with missing lines Patch % Lines
resolver/filtering_resolver.go 95.45% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2070      +/-   ##
==========================================
- Coverage   86.34%   86.34%   -0.01%     
==========================================
  Files         108      108              
  Lines        8061     8104      +43     
==========================================
+ Hits         6960     6997      +37     
- Misses        897      901       +4     
- Partials      204      206       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR closes #1706 by preventing IPv6 connectivity bypass when filtering.queryTypes includes AAAA: it strips the ipv6hint SvcParam from HTTPS/SVCB answers so modern clients can’t learn IPv6 endpoints via RFC 9460 hints.

Changes:

  • Update FilteringResolver to remove ipv6hint from HTTPS and SVCB answer RRs when AAAA is configured as a filtered query type.
  • Add Ginkgo specs covering HTTPS and SVCB stripping behavior, plus non-SVCB no-op and “AAAA not filtered” behavior.
  • Document the behavior in the Filtering section of the configuration docs.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
resolver/filtering_resolver.go Adds response post-processing to strip ipv6hint from HTTPS/SVCB answers when AAAA filtering is enabled.
resolver/filtering_resolver_test.go Adds unit tests validating ipv6hint stripping and non-regression cases.
docs/configuration.md Documents automatic stripping of ipv6hint when AAAA query filtering is configured.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread resolver/filtering_resolver.go
Stripping ipv6hint mutates the HTTPS/SVCB RRset after the DNSSEC resolver in
the chain may have set AD=1 and passed RRSIGs through. Leaving AD=1 / invalid
signatures would serve DNSSEC-inconsistent data.

When a hint is actually removed, clear AuthenticatedData and drop the RRSIGs
covering HTTPS/SVCB, mirroring the DNS64 resolver. Responses with no ipv6hint
are left untouched so their DNSSEC status is preserved.
@0xERR0R
0xERR0R requested a review from Copilot May 22, 2026 10:35
@0xERR0R 0xERR0R added the 🔨 enhancement New feature or request label May 22, 2026
@0xERR0R 0xERR0R added this to the v0.31.0 milestone May 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comment thread resolver/filtering_resolver.go Outdated
Comment on lines +46 to +56
// removeIPv6Hints strips the ipv6hint SvcParam from any HTTPS/SVCB record in the response.
// The records are owned by the current request (freshly produced upstream or unpacked from
// cache), so they are modified in place.
//
// Modifying a signed RRset invalidates its DNSSEC signatures, so when a hint is actually
// removed the AD bit is cleared and the now-invalid RRSIGs covering the modified RRsets are
// dropped, to avoid serving DNSSEC-inconsistent data (mirrors the DNS64 resolver behavior).
func removeIPv6Hints(msg *dns.Msg) {
modified := false

for _, rr := range msg.Answer {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Narrowed the comment to the answer section in 6c0f991. Only the answer section is processed: the authority section never carries HTTPS/SVCB service records, and the reported leak (#1706) is in the answer section. Extending to the additional section would be a separate enhancement.

Comment thread resolver/filtering_resolver.go Outdated
Comment on lines +83 to +99
if !modified {
return
}

msg.AuthenticatedData = false
msg.Answer = removeSVCBSignatures(msg.Answer)
}

// removeSVCBSignatures returns the answers with any RRSIG covering HTTPS or SVCB records removed.
func removeSVCBSignatures(answers []dns.RR) []dns.RR {
filtered := make([]dns.RR, 0, len(answers))

for _, rr := range answers {
if sig, ok := rr.(*dns.RRSIG); ok &&
(sig.TypeCovered == dns.TypeHTTPS || sig.TypeCovered == dns.TypeSVCB) {
continue
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c0f991. removeIPv6Hints now records the Rrtype of each record it actually modifies and removeSignaturesCovering only drops RRSIGs whose TypeCovered is in that set, so stripping a hint from an HTTPS RR no longer removes valid SVCB signatures (and vice versa). Added a spec asserting that with HTTPS modified, only the HTTPS-covering RRSIG is dropped while SVCB- and CNAME-covering signatures are kept.

Comment thread resolver/filtering_resolver.go Outdated
Comment on lines +68 to +81
filtered := make([]dns.SVCBKeyValue, 0, len(*values))

for _, kv := range *values {
if kv.Key() == dns.SVCB_IPV6HINT {
modified = true

continue
}

filtered = append(filtered, kv)
}

*values = filtered
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6c0f991. A containsIPv6Hint pre-check now skips records that carry no ipv6hint, so the Value slice is only rebuilt and reassigned for records actually being modified.

…cords

Address review feedback on the ipv6hint stripping:
- Only drop RRSIGs covering record types that were actually modified, so a
  hint removed from an HTTPS RR no longer invalidates unrelated SVCB signatures.
- Skip rebuilding the SvcParam slice for records that carry no ipv6hint.
- Narrow the doc comment to the answer section (the only section processed).

Also fixes lint: drop unused helper param and unnecessary conversion in tests.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

resolver/filtering_resolver.go:57

  • removeIPv6Hints always allocates modifiedTypes := map[uint16]struct{}{} even when there are no HTTPS/SVCB records (or no ipv6hint) in the answer. To reduce allocations on the hot path, consider lazily initializing this map only when a record is actually modified (e.g., keep it nil until the first removal).
func removeIPv6Hints(msg *dns.Msg) {
	modifiedTypes := map[uint16]struct{}{}

	for _, rr := range msg.Answer {

Comment thread resolver/filtering_resolver.go Outdated

// When AAAA lookups are filtered, also drop IPv6 hints from HTTPS/SVCB answers so that
// clients can't reach the IPv6 endpoints advertised via SvcParams (RFC 9460).
if resp != nil && resp.Res != nil && r.cfg.QueryTypes.Contains(dns.Type(dns.TypeAAAA)) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in dd1a3c0. The call is now gated by the request query type (isSVCBQuery), so removeIPv6Hints only runs for HTTPS/SVCB queries. Since those record types can only appear in the answer section of HTTPS/SVCB queries, other traffic (A/AAAA/MX/...) skips the answer scan and the per-request map allocation entirely. Added a spec asserting an A-query response is never scanned.

Gate the ipv6hint stripping behind the request query type. HTTPS/SVCB
records can only appear in the answer section of HTTPS/SVCB queries, so
A/AAAA/MX and other traffic no longer pays for the answer scan and the
per-request map allocation when AAAA filtering is enabled.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@0xERR0R
0xERR0R enabled auto-merge (squash) May 22, 2026 11:39
@0xERR0R
0xERR0R disabled auto-merge May 22, 2026 11:39
@0xERR0R
0xERR0R merged commit 99ae703 into main May 22, 2026
26 checks passed
@0xERR0R
0xERR0R deleted the feat/filter-https-ipv6hint branch May 22, 2026 11:39
dadezzz pushed a commit to dadezzz/infra_docker-compose that referenced this pull request Jun 7, 2026
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/0xerr0r/blocky](https://github.com/0xERR0R/blocky) | minor | `v0.30.0` → `v0.31.0` |

---

### Release Notes

<details>
<summary>0xERR0R/blocky (ghcr.io/0xerr0r/blocky)</summary>

### [`v0.31.0`](https://github.com/0xERR0R/blocky/releases/tag/v0.31.0)

[Compare Source](0xERR0R/blocky@v0.30.0...v0.31.0)

#### Changelog

##### Features

- [`132c42f`](0xERR0R/blocky@132c42f): feat(config): validate and log ports.dohPath ([#&#8203;2072](0xERR0R/blocky#2072)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`e43b5e5`](0xERR0R/blocky@e43b5e5): feat(querylog): add SQLite query log target ([#&#8203;2080](0xERR0R/blocky#2080)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`5d806b6`](0xERR0R/blocky@5d806b6): feat(server): opt-in IP\_FREEBIND for DNS listeners ([#&#8203;2078](0xERR0R/blocky#2078)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`f18e52b`](0xERR0R/blocky@f18e52b): feat: capability-aware image that runs under drop:ALL ([#&#8203;2071](0xERR0R/blocky#2071)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`bf19d6d`](0xERR0R/blocky@bf19d6d): feat: config JSON schema generation and schema-driven validation ([#&#8203;2066](0xERR0R/blocky#2066)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`99ae703`](0xERR0R/blocky@99ae703): feat: filter ipv6hint in HTTPS/SVCB RR when AAAA is filtered ([#&#8203;2070](0xERR0R/blocky#2070)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`c44017a`](0xERR0R/blocky@c44017a): feat: load sensitive config values from files ([#&#8203;2077](0xERR0R/blocky#2077)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`e6b41db`](0xERR0R/blocky@e6b41db): feat: per-client DNS rate limiting ([#&#8203;2063](0xERR0R/blocky#2063)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`f457ec9`](0xERR0R/blocky@f457ec9): feat: read bootstrap nameservers from a resolv.conf file (resolvFile) ([#&#8203;2068](0xERR0R/blocky#2068)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`fdcf351`](0xERR0R/blocky@fdcf351): feat: resolve client names from hosts file and custom DNS ([#&#8203;1964](0xERR0R/blocky#1964)) ([#&#8203;2069](0xERR0R/blocky#2069)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))

##### Bug fixes

- [`93cc238`](0xERR0R/blocky@93cc238): fix(caching): sync prefetched entries to Redis ([#&#8203;2079](0xERR0R/blocky#2079)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`69b0663`](0xERR0R/blocky@69b0663): fix(config): handle DNS stamp port on hostname field and bootstrap IPs ([#&#8203;2065](0xERR0R/blocky#2065)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))
- [`31f8df3`](0xERR0R/blocky@31f8df3): fix(querylog): build SQLite target only on modernc-supported platforms ([#&#8203;2082](0xERR0R/blocky#2082)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))

##### Build and dependencies

- [`a5cdeb2`](0xERR0R/blocky@a5cdeb2): build(deps): bump github.com/go-chi/chi/v5 from 5.2.5 to 5.3.0 ([#&#8203;2074](0xERR0R/blocky#2074)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`5a25f67`](0xERR0R/blocky@5a25f67): build(deps): bump github.com/mattn/go-colorable from 0.1.14 to 0.1.15 ([#&#8203;2075](0xERR0R/blocky#2075)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`1da222a`](0xERR0R/blocky@1da222a): build(deps): bump github.com/oapi-codegen/runtime from 1.4.0 to 1.4.1 ([#&#8203;2064](0xERR0R/blocky#2064)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`6ff1d13`](0xERR0R/blocky@6ff1d13): build(deps): bump golang.org/x/net from 0.54.0 to 0.55.0 ([#&#8203;2067](0xERR0R/blocky#2067)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`63f8d9a`](0xERR0R/blocky@63f8d9a): build: upgrade Go to 1.26.2 and golangci-lint to v2.12.2 ([#&#8203;2062](0xERR0R/blocky#2062)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))

##### Misc

- [`e2de6ae`](0xERR0R/blocky@e2de6ae): docs(redis): document unix socket support and add e2e test ([#&#8203;2076](0xERR0R/blocky#2076)) ([@&#8203;0xERR0R](https://github.com/0xERR0R))

***

#### ❤️ Support Blocky

Blocky is free and open source, developed in my spare time — no telemetry, no ads, no hidden
filtering. If it's useful to you, please consider supporting its continued development:

[GitHub Sponsors](https://github.com/sponsors/0xERR0R) ·
[thanks.dev](https://thanks.dev/u/gh/0xERR0R) ·
[Liberapay](https://liberapay.com/spx01) ·
[Ko-fi](https://ko-fi.com/0xerr0r) ·
[PayPal](https://paypal.me/spx01)

Thank you to everyone supporting Blocky! 🙏

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMDUuMyIsInVwZGF0ZWRJblZlciI6IjQzLjIwNS4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request] Filter ipv6hints in HTTPS RR

2 participants