Fix EDNS OPT record corruption in DNS cache#3997
Merged
nekohasekai merged 1 commit intoSagerNet:testingfrom Apr 10, 2026
Merged
Fix EDNS OPT record corruption in DNS cache#3997nekohasekai merged 1 commit intoSagerNet:testingfrom
nekohasekai merged 1 commit intoSagerNet:testingfrom
Conversation
The TTL computation and assignment loops treat OPT record's Hdr.Ttl as a regular TTL, but per RFC 6891 it encodes EDNS0 metadata (ExtRCode|Version|Flags). This corrupts cached responses causing systemd-resolved to reject them with EDNS version 255. Also fix pointer aliasing: storeCache() stored raw *dns.Msg pointer so subsequent mutations by Exchange() corrupted cached data. - Skip OPT records in all TTL loops (Exchange + loadResponse) - Use message.Copy() in storeCache() to isolate cache from mutations
6967a17 to
a2aa2ac
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes EDNS OPT record corruption when caching DNS responses. Two interrelated bugs in
dns/client.go:Bug 1: OPT record TTL field treated as actual TTL
Per RFC 6891 Section 6.1.3, the OPT record's
Hdr.Ttlfield encodes(ExtRCode << 24) | (Version << 16) | DO | Z— it is NOT a time-to-live. The TTL computation and assignment loops iterate overresponse.Extra(which contains OPT) and read/writerecord.Header().Ttluniformly, destroying the EDNS0 metadata. Cached responses end up withEDNS version 255and garbage flags.Bug 2:
storeCache()stores raw*dns.MsgpointerAfter
storeCache()saves the pointer,Exchange()continues mutatingresponse(EDNS version downgrade modifiesresponse.Extra). All mutations affect the cached object sincefreelrustores pointers as-is.Steps to reproduce
hijack-dns+independent_cache: true+ HTTPS DNS upstream (e.g. Cloudflare)pypi.org):Not all domains trigger this. The corruption depends on the OPT record content from the upstream — specifically whether the MBZ/Z bits (stored in the lower 16 bits of OPT's "TTL" field) are nonzero. Domains like
github.comandgoogle.comhappen to work because their OPT metadata produces values that don't catastrophically corrupt the version byte.Evidence
sing-box logs show the corruption at cache write time:
systemd-resolved debug logs:
Changes
dns.TypeOPTrecords in all TTL computation and assignment loops inExchange()andloadResponse()(5 locations)message.Copy()instoreCache()to isolate cache from caller mutations (4 cache calls)Related
Why previous fixes didn't resolve this
Several commits addressed symptoms but not the root cause:
storeCache()beforeresponse.Id = messageId. Fixes ID mutation, but the EDNS version downgrade code after it still mutatesresponse.Extraon the same pointer as the cached object.Ttl == 0. Works as an accidental filter — OPT records with nonzero EDNS0 metadata haveTtl != 0, bypassing cache. But some upstream OPT records have nonzero MBZ bits, hitting the corruption.None of these address the actual problem: the TTL loops modify the OPT record's TTL field.