fix: truncate udp dns replies to the client's advertised buffer size - #3043
Merged
Conversation
9 tasks
onesyue
pushed a commit
to onesyue/mihomo
that referenced
this pull request
Aug 11, 2026
…etaCubeX#3043) (cherry picked from commit 9574a46)
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.
What this fixes
Two UDP reply paths ignored the client's advertised EDNS0 buffer size:
dns/server.go): oversized UDP replies were sent withoutany truncation. Replies larger than the client's buffer get IP-fragmented
(and commonly dropped on 1280–1420 MTU links) or are discarded by strict
resolvers, and without the TC bit those clients never fall back to TCP —
large answers (e.g.
128-a.size.dns.netmeister.org, 128 A records ≈ 2 KB)simply fail to resolve.
component/resolver/relay.go): replies were onlycapped at a fixed
SafeDnsPacketSize(2048), not at the size the clientactually advertised.
Related: #3034, #3041.
Why truncation must happen on the responder side
TC is a bit in the DNS response header — only the party composing the
reply can set it:
RFC 2181 §9
defines both halves of the contract: the responder sets TC when required data
does not fit, and
The client's TCP fallback is only triggered by TC, so omitting it breaks
the fallback entirely. And per
RFC 6891 §6.2.6
each hop is a separate transaction: the upstream cannot know each downstream
client's advertised size, so enforcing it on the client-facing hop is
mihomo's job alone (mihomo already handles the upstream hop's TC by retrying
over TCP in
dns/client.go).Fix
Add
RequestUDPSize()returning the reply-size budget for a request: theclient's advertised EDNS0 buffer size, or 512 bytes
(RFC 1035 §4.2.1)
when the request carries no OPT record. Then:
unrestricted);
min(client size, SafeDnsPacketSize).Already handled by the upstream library
Two normative details are implemented inside
miekg/dns Msg.Truncate(), sothis PR intentionally does not duplicate them:
Floor for small advertised values —
RFC 6891 §6.2.5:
Msg.Truncateclamps:if size < MinMsgSize { size = MinMsgSize }.Minimal content of a truncated reply —
RFC 6891 §7:
Msg.Truncatepops the OPT record, reserves its size in the budget, andre-appends it after dropping answer records, so the header, question and
OPT always survive truncation; it sets TC whenever records were dropped.
Verification