auto_redirect (iptables): UDP DNS sent to the gateway's own address is no longer hijacked since sing-tun 3763c0ee (1.15.0-alpha.3+) — leaks to the system resolver
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 38.1k
- Forks
- 4.6k
- Avg merge
- 19d 15h
- Merged PRs (30d)
- 1
Description
Operating system
Android
System version
Android 13
Installation type
Original sing-box Command Line
If you are using a graphical client, please provide the version of the client.
No response
Version
sing-box version 1.15.0-alpha.4
Environment: go1.26.8 android/arm64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_usbip,with_openvpn,with_openconnect,badlinkname,tfogo_checklinkname0
Revision: 4566ef0890e0cde8448000e8aa3223fb286daa94
CGO: enabled
Also reproduced by inspection on 1.15.0-alpha.5 (Revision 37611b41, sing-tun v0.9.4-0.20260916043548-e842d006fa65 — identical redirect_iptables.go).
Not reproducible on 1.15.0-alpha.2 (Revision b4b5af50, sing-tun v0.9.1-0.20260902150540-98e457e39c90).
Description
Since sing-tun commit 3763c0ee ("Implement fully functional auto redirect for Android"), iptablesAddExcludeRules adds to the nat chain:
-A sing-box-prerouting -m connmark --mark 0x400000/0xe00000 -j RETURN # AutoRedirectInputMark
before localGate, i.e. before the DNS DNAT rules in sing-box-local-*:
-A sing-box-local-10 -s 192.168.30.0/24 -p udp --dport 53 -j DNAT --to-destination 172.31.255.2
But the mangle sing-box-prerouting-mark chain already marks every → :53 udp packet with the input mark and saves it to connmark (sing-box-local-7 … -g sing-box-cont-6). So by the time the packet reaches nat PREROUTING, the connmark RETURN matches and the DNAT is never executed — its counters stay at 0 for the whole session.
For a non-local destination (e.g. 8.8.8.8:53) this still works because the marked packet is routed via table 2022 into the TUN. For a destination that is one of the gateway's own addresses (the normal case: DHCP hands out the gateway as DNS), lookup local (pref 0) wins over the pref 9001 rule, so the query is delivered to the local resolver (dnsmasq), which forwards it to the carrier's DNS — a DNS leak that bypasses hijack-dns entirely.
Evidence (alpha.4, ~11h session):
iptables -t nat -L sing-box-prerouting -v -n
4267 1155K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 connmark match 0x400000/0xe00000
2683 173K sing-box-include-8 all -- wlan1 * ...
iptables -t nat -L sing-box-local-10 -v -n
0 0 DNAT udp -- * * 192.168.30.0/24 0.0.0.0/0 udp dpt:53 to:172.31.255.2
ip route get 192.168.30.164 from 192.168.30.90 iif wlan1 mark 0x400000
local 192.168.30.164 from 192.168.30.90 dev lo table local mark 0x400000
/proc/net/nf_conntrack
udp src=192.168.30.90 dst=192.168.30.164 dport=53 ... mark=4194304 (no DNAT applied)
udp src= dst=202.96.128.86 dport=53 ... (dnsmasq → carrier DNS)
sing-box's log contains zero dns: exchanged lines for client queries during the session. TCP/53 is unaffected (it goes through the nat REDIRECT path).
Same config on alpha.2: the nat chain has no connmark 0x400000 RETURN, the DNAT counter grows with client queries, and client lookups appear in the log.
Expected: UDP DNS from included interfaces to the gateway's own address should still be DNATed to the TUN DNS address (or otherwise reach hijack-dns), as on alpha.2.
Possible fix: exempt --dport 53 from the nat connmark RETURN (or place localGate's DNS rules before it), so the DNAT still applies to marked DNS flows. I have not tested this on a non-Android Linux host, but the code path is not Android-specific, so a Linux gateway whose clients use it as DNS is probably affected as well.
Reproduction
{
"log": { "level": "info" },
"dns": {
"servers": [{ "type": "udp", "tag": "dns", "server": "223.5.5.5" }]
},
"inbounds": [{
"type": "tun",
"interface_name": "gwtun0",
"address": ["172.31.255.1/30"],
"auto_route": true,
"auto_redirect": true,
"include_interface": ["wlan1"]
}],
"outbounds": [{ "type": "direct", "tag": "direct" }],
"route": {
"rules": [{ "port": 53, "action": "hijack-dns" }],
"final": "direct",
"auto_detect_interface": true
}
}
- Host has a LAN/hotspot interface wlan1 (192.168.30.164/24) with a local resolver (dnsmasq) listening on 192.168.30.164:53; clients get 192.168.30.164 as DNS via DHCP.
- Start sing-box as root with the config above.
- From a client: nslookup example.com 192.168.30.164
- Observe:
iptables -t nat -L sing-box-prerouting -v -n -> "connmark match 0x400000/0xe00000 RETURN" hit count grows
iptables -t nat -L sing-box-local-* -v -n -> the "udp dpt:53 DNAT to:172.31.255.2" rule stays at 0
ip route get 192.168.30.164 from 192.168.30.90 iif wlan1 mark 0x400000 -> "local ... table local"
sing-box log has no "dns: exchanged" for the client query; the local resolver answers it instead. - Same steps on 1.15.0-alpha.2: no connmark RETURN in the nat chain, DNAT counter grows, query appears in the log.
Logs
### 1.15.0-alpha.4 — session started 2026-09-16 21:21, ran ~11 h
+0800 2026-09-16 21:21:05 INFO network: updated default interface rmnet_data2, index 16, vpn disabled
+0800 2026-09-16 21:21:05 WARN inbound/tun[hotspot-in]: enable offload: set udp offload: TUNSETOFFLOAD: invalid argument
+0800 2026-09-16 21:21:05 INFO inbound/tun[hotspot-in]: started at gwhotspot0
+0800 2026-09-16 21:21:08 INFO sing-box started (3.01s)
+0800 2026-09-16 21:21:12 INFO [4094020542 0ms] inbound/tun[hotspot-in]: inbound redirect connection from 192.168.30.174:36526
+0800 2026-09-16 21:21:12 INFO [4094020542 0ms] inbound/tun[hotspot-in]: inbound connection to 35.163.192.149:443
...
# Over the whole 11 h session: 15591 log lines, 1472 "inbound packet connection",
# but only 151 "inbound DNS packet from <client>" lines — and those come from clients that
# use an external resolver (destination != gateway address, so table 2022 still routes them
# into the TUN). Not a single log entry corresponds to the queries sent to the gateway's own
# address 192.168.30.164:53 (the DNS handed out by DHCP); the system dnsmasq answered them.
# iptables -t nat -L sing-box-prerouting -v -n (same session)
4267 1155K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 connmark match 0x400000/0xe00000
2683 173K sing-box-include-8 all -- wlan1 * 0.0.0.0/0 0.0.0.0/0
# iptables -t nat -L sing-box-local-10 -v -n
0 0 DNAT udp -- * * 192.168.30.0/24 0.0.0.0/0 udp dpt:53 to:172.31.255.2
# ip route get 192.168.30.164 from 192.168.30.90 iif wlan1 mark 0x400000
local 192.168.30.164 from 192.168.30.90 dev lo table local mark 0x400000
# /proc/net/nf_conntrack (excerpt)
ipv4 2 udp 17 46 src=192.168.30.90 dst=192.168.30.164 sport=51959 dport=53 src=192.168.30.164 dst=192.168.30.90 sport=53 dport=51959 [ASSURED] mark=4194304 <- no DNAT
ipv4 2 udp 17 44 src=10.18.113.88 dst=202.96.134.133 sport=9140 dport=53 src=202.96.134.133 dst=10.18.113.88 sport=53 dport=9140 [ASSURED] mark=2097152 <- dnsmasq -> carrier DNS
### 1.15.0-alpha.2 — same config, same host, session started 2026-09-17 08:19
+0800 2026-09-17 08:19:23 INFO inbound/tun[hotspot-in]: started at gwhotspot0
+0800 2026-09-17 08:19:33 INFO [1495695534 0ms] inbound/tun[hotspot-in]: inbound DNS packet from 192.168.30.90:59123
+0800 2026-09-17 08:19:33 INFO [1495695534 143ms] dns: exchanged CNAME 49-courier2.push.apple.com. 4 IN CNAME 49.courier2-push-apple.com.akadns.net.
+0800 2026-09-17 08:19:33 INFO [1495695534 143ms] dns: exchanged A apac-asia-courier-vs.push-apple.com.akadns.net. 4 IN A 17.57.145.135
# 1009 "inbound DNS packet" lines within the first ~40 min (vs. 151 in 11 h on alpha.4)
# iptables -t nat -L sing-box-prerouting -v -n -> no "connmark 0x400000 RETURN" rule
# iptables -t nat -L sing-box-local-10 -v -n
341 26615 DNAT udp -- * * 192.168.30.0/24 0.0.0.0/0 udp dpt:53 to:172.31.255.2
# conntrack: zero flows from dnsmasq to the carrier DNS
Supporter
- I am a sponsor
Integrity requirements
- I confirm that I have read the documentation, understand the meaning of all the configuration items I wrote, and did not pile up seemingly useful options or default values.
- I confirm that I have provided the server and client configuration files and process that can be reproduced locally, instead of a complicated client configuration file that has been stripped of sensitive data.
- I confirm that I have provided the simplest configuration that can be used to reproduce the error I reported, instead of depending on remote servers, TUN, graphical interface clients, or other closed-source software.
- I confirm that I have provided the complete configuration files and logs, rather than just providing parts I think are useful out of confidence in my own intelligence.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in redirect_iptables.go at iptablesAddExcludeRules, and compare the nat-chain setup with sing-tun commit 3763c0ee and the alpha.2 behavior. Reproduce the supplied Android configuration, inspect iptables counters and conntrack, and confirm that UDP DNS sent to the gateway reaches the TUN DNS path rather than the local resolver, without regressing non-local DNS handling.
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
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100