Skip to content

fix: truncate udp dns replies to the client's advertised buffer size - #3043

Merged
wwqgtxx merged 1 commit into
MetaCubeX:Alphafrom
TWO666:fix-dns-udp-truncate
Jul 29, 2026
Merged

fix: truncate udp dns replies to the client's advertised buffer size#3043
wwqgtxx merged 1 commit into
MetaCubeX:Alphafrom
TWO666:fix-dns-udp-truncate

Conversation

@TWO666

@TWO666 TWO666 commented Jul 29, 2026

Copy link
Copy Markdown

What this fixes

Two UDP reply paths ignored the client's advertised EDNS0 buffer size:

  • DNS listener (dns/server.go): oversized UDP replies were sent without
    any 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.
  • TUN hijack path (component/resolver/relay.go): replies were only
    capped at a fixed SafeDnsPacketSize (2048), not at the size the client
    actually 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:

TrunCation - specifies that this message was truncated due to length
greater than that permitted on the transmission channel.
RFC 1035 §4.1.1

RFC 2181 §9
defines both halves of the contract: the responder sets TC when required data
does not fit, and

When a DNS client receives a reply with TC set, it should ignore that
response, and query again, using a mechanism, such as a TCP connection,
that will permit larger replies.

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: the
client's advertised EDNS0 buffer size, or 512 bytes
(RFC 1035 §4.2.1)
when the request carries no OPT record. Then:

  • the DNS listener truncates UDP replies to that budget (TCP replies remain
    unrestricted);
  • the TUN hijack path truncates to min(client size, SafeDnsPacketSize).

Already handled by the upstream library

Two normative details are implemented inside miekg/dns Msg.Truncate(), so
this PR intentionally does not duplicate them:

  • Floor for small advertised values
    RFC 6891 §6.2.5:

    Values lower than 512 MUST be treated as equal to 512.

    Msg.Truncate clamps: if size < MinMsgSize { size = MinMsgSize }.

  • Minimal content of a truncated reply
    RFC 6891 §7:

    The minimal response MUST be the DNS header, question section, and an
    OPT record. This MUST also occur when a truncated response (using the
    DNS header's TC bit) is returned.

    Msg.Truncate pops the OPT record, reserves its size in the budget, and
    re-appends it after dropping answer records, so the header, question and
    OPT always survive truncation; it sets TC whenever records were dropped.

Verification

$ dig @127.0.0.3 1024.size.dns.netmeister.org A +notcp +noall +comments +stat +bufsize=1017
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56039
;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 60, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; Query time: 1 msec
;; SERVER: 127.0.0.3#53(127.0.0.3) (UDP)
;; WHEN: Tue Jul 28 16:58:49 PDT 2026
;; MSG SIZE  rcvd: 1017

$ # 1017 > min(1232, 1016) Truncated
$ dig @127.0.0.3 1024.size.dns.netmeister.org A +notcp +noall +comments +stat +bufsize=1016
;; Truncated, retrying in TCP mode.
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61864
;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 60, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; Query time: 1 msec
;; SERVER: 127.0.0.3#53(127.0.0.3) (TCP)
;; WHEN: Tue Jul 28 16:58:53 PDT 2026
;; MSG SIZE  rcvd: 1017

Copilot AI review requested due to automatic review settings July 29, 2026 00:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wwqgtxx
wwqgtxx merged commit 9574a46 into MetaCubeX:Alpha Jul 29, 2026
@TWO666
TWO666 deleted the fix-dns-udp-truncate branch July 29, 2026 00:55
onesyue pushed a commit to onesyue/mihomo that referenced this pull request Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants