SagerNet / SagerNet/sing-box

hijack-dns captures non-DNS (STUN) packets that reuse a DNS query's UDP source port → "process DNS packet: unpack request: bad rdata"

Open
#4,199 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Go
Stars
38.1k
Forks
4.6k
Avg merge
19d 15h
Merged PRs (30d)
1

Description

Update (revised report): my original title/description attributed this to a single-packet DNS sniffer false positive. After capturing the full UDP flow, the real root cause is UDP source-port reuse causing non-DNS packets to be glued to a hijacked-DNS session. Revised below.

Operating system

Linux

System version

Debian 13 (trixie), kernel 6.12.90+deb13.1-amd64, x86_64

Installation type

Original sing-box Command Line

Version
sing-box version 1.13.12

Environment: go1.25.9 linux/amd64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,badlinkname,tfogo_checklinkname0,with_musl
Revision: 1086ab2563320e0da0c23b3a491d8dfa0939dff4
CGO: enabled
Description

On a TUN gateway with hijack-dns, I get a continuous stream of:

router: process DNS packet: unpack request: bad question name: dns: bad rdata
router: process DNS packet: unpack request: ... overflow unpacking uint16
router: process DNS packet: ... buffer size too small

Full packet capture + debug logs show the cause is UDP source-port reuse, not a per-packet sniffer false positive:

A LAN client (an iPhone running a live-streaming app with P2P/PCDN) reuses a single UDP source port. It first sends one DNS query to :53, then reuses the same source port to send STUN packets to other ports (:3478, etc.). sing-box tracks the TUN UDP session by source port and makes the hijack decision on the first packet, so the whole session is locked to hijack-dns; every subsequent STUN packet on that source port is fed into the hijacked DNS session and fails to unpack.

This still happens with the hijack rule reduced to { "port": 53 } only (no protocol: dns), because the first packet genuinely targets :53.

Evidence

One erroring UDP session (connection id 4017931420, source port 57688) over ~70s — its packets grouped by destination port and payload header:

dst port packets payload bytes [4:6] meaning
53 1 0001 normal DNS query (QDCOUNT=1) — the first packet
63335 84 2112 STUN (Magic Cookie 0x2112A442)
3478 5 2112 STUN

The session logged 89 bad rdata errors ≈ the 89 STUN packets.

Debug log for this session — the hijack is decided on the first packet via port=53:

INFO  [4017931420] inbound/tun[tun-in]: inbound packet connection from 172.17.1.18:57688
INFO  [4017931420] inbound/tun[tun-in]: inbound packet connection to 198.18.2.2:53
DEBUG [4017931420] router: sniffed packet protocol: dns
DEBUG [4017931420] router: match[1] port=53 => hijack-dns

A captured STUN packet on this same source port (dst :3478):

IP 172.17.1.18.57688 > x.x.x.x.3478: UDP, length 20
  payload: 0001 0000 2112 a442 <12-byte transaction id>
           ^^^^ STUN Message Type (Binding Request)
                     ^^^^^^^^ STUN Magic Cookie (RFC 5389)

When this is handed to the DNS module, the STUN header is read as a DNS message (2112 → QDCOUNT = 8466), so it tries to parse 8466 questions out of a 20-byte packet → bad rdata / overflow unpacking uint16 / buffer size too small.

Why config workarounds do not help
  • Removing protocol: dns from the hijack rule (keeping only port: 53): no effect (verified) — the first packet really targets :53.
  • Adding { "protocol": "stun", "action": "reject" }: no effect — the STUN packets never undergo independent route evaluation; they are glued to the already-hijacked DNS session and go straight into the DNS module.
Side effect

The hijacked STUN packets are dropped on unpack failure, so the client's NAT-traversal probes do not complete — this can degrade P2P for the app, on top of spamming the log.

Reproduction
  1. TUN inbound, hijack-dns matching port 53 (with or without protocol: dns).
  2. A client behind the gateway reuses one UDP source port to send a DNS query to :53 and then STUN to another port (any WebRTC / P2P / live-streaming NAT traversal can do this).
  3. sing-box locks the source-port UDP session to hijack-dns on the first (DNS) packet; subsequent STUN packets on the same source port are processed as DNS and fail to unpack → repeated errors.
Suggested fix

Within a hijacked-DNS UDP session, packets that fail to parse as DNS should be passed through / re-routed instead of dropped with an error; and/or hijack-dns session tracking should key on the full 5-tuple and re-evaluate per packet, so a source port later reused for STUN is not captured by a prior DNS session.

Related

Same symptom as #3531 (errors while a game/app generates NAT-traversal traffic), closed as stale for lack of a capture. This report provides the capture and identifies source-port reuse as the cause.

Configuration

De-identified minimal excerpt (the errors persist with this reduced rule set):

{
  "inbounds": [
    { "type": "tun", "tag": "tun-in", "auto_route": true, "auto_redirect": true, "stack": "mixed" }
  ],
  "route": {
    "rules": [
      { "action": "sniff" },
      { "port": 53, "action": "hijack-dns" }
    ]
  }
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the TUN UDP session tracking and hijack-dns routing path described in the report, then reproduce the source-port reuse sequence with a DNS query followed by STUN packets. Trace how subsequent packets reach the DNS unpacking path. Done means STUN traffic is not treated as DNS or dropped, while the initial DNS packet is still hijacked correctly and the repeated unpack errors stop.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.