[Windows][TUN] direct outbound loops into the same TUN / direct 出站回环进入同一 TUN
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 38.1k
- Forks
- 4.6k
- Avg merge
- 19d 15h
- Merged PRs (30d)
- 1
Description
Summary / 问题概述
On Windows with the system TUN stack, auto_route: true, strict_route: true, and route.auto_detect_interface: true, UDP packets addressed to the sing-box TUN peer/gateway can be routed through direct and immediately re-enter the same TUN. This creates a very fast packet and log loop.
在 Windows 上使用 system TUN 栈,并启用 auto_route: true、strict_route: true 和 route.auto_detect_interface: true 时,发往 sing-box TUN 对端/网关的 UDP 数据包可能经由 direct 出站后立即重新进入同一个 TUN,从而形成高速的数据包与日志循环。
Tailscale is the trigger in this reproduction: its normal NAT-PMP/PCP and UPnP gateway-discovery probes target UDP ports 5351 and 1900. The underlying problem appears to be that sing-box does not reject or escape traffic sent to its own TUN range before direct sends it back to the same interface.
本复现中 Tailscale 是触发器:它正常执行 NAT-PMP/PCP 和 UPnP 网关探测时,会访问 UDP 5351 和 1900 端口。根本问题似乎是 sing-box 没有拒绝或绕开目的地址属于自身 TUN 网段的流量,导致 direct 又把数据包送回同一接口。
I used the blank issue form because the bug-report form requires attesting that the minimal reproduction does not depend on TUN. This report is specifically about the TUN implementation, so I cannot truthfully check that box.
我使用 Blank issue,是因为错误反馈模板要求确认最小复现不依赖 TUN;但此问题本身就是 TUN 实现故障,因此无法如实勾选该项。
Environment / 环境
- OS: Windows NT build 22000.2538 (Windows 11 21H2)
- 操作系统:Windows NT build 22000.2538(Windows 11 21H2)
- sing-box:
1.13.18,windows/amd64, Go1.26.5 - sing-box:
1.13.18,windows/amd64,Go1.26.5 - sing-box revision:
45ca32dcb966f07f97fc888fe8586e359dbe8405 - sing-box 修订版本:
45ca32dcb966f07f97fc888fe8586e359dbe8405 - Tailscale:
1.98.2(aaf7caef13becf6989e9e81f66412f3edc564c38) - Tailscale:
1.98.2(aaf7caef13becf6989e9e81f66412f3edc564c38) - Installation: official sing-box command-line binary
- 安装方式:sing-box 官方命令行二进制程序
Minimal configuration / 最小配置
The following local-only configuration does not require any remote proxy server.
以下配置仅使用本地环境,不依赖任何远程代理服务器。
{
"log": { "level": "trace", "timestamp": true },
"inbounds": [
{
"type": "tun",
"tag": "tun",
"address": ["172.19.0.1/30", "fdfe:dcba:9876::1/126"],
"auto_route": true,
"strict_route": true,
"stack": "system"
}
],
"outbounds": [
{ "type": "direct", "tag": "direct" }
],
"route": {
"rules": [
{ "action": "sniff", "timeout": "300ms" },
{ "protocol": "dns", "action": "hijack-dns" },
{ "ip_is_private": true, "outbound": "direct" }
],
"final": "direct",
"auto_detect_interface": true
}
}
Reproduction / 复现步骤
- Start the Tailscale Windows service.
启动 Tailscale Windows 服务。 - Run sing-box as Administrator:
sing-box run -c minimal.json.
以管理员身份运行 sing-box:sing-box run -c minimal.json。 - Run
tailscale netcheck, or wait for Tailscale port-mapping discovery.
运行tailscale netcheck,或者等待 Tailscale 自动进行端口映射探测。 - Observe the sing-box trace log.
观察 sing-box 的 trace 日志。
Tailscale reports the sing-box TUN addresses as the current gateway/self pair:
Tailscale 将 sing-box 的 TUN 地址识别为当前网关和本机地址:
portmap: monitor: gateway and self IP changed: gw=172.19.0.2 self=172.19.0.1
sing-box then repeatedly logs the same flow with continuously changing source ports:
随后 sing-box 会持续记录同一流量,源端口不断变化:
INFO inbound/tun[tun]: inbound packet connection from 172.19.0.1:50287
INFO inbound/tun[tun]: inbound packet connection to 172.19.0.2:5351
DEBUG router: match[0] => sniff(300ms)
DEBUG router: ip_is_private=true => route(direct)
INFO outbound/direct[direct]: outbound packet connection
If only UDP 5351 is blocked, the loop moves to UDP 1900:
如果只拦截 UDP 5351,循环会转移到 UDP 1900:
INFO inbound/tun[tun]: inbound packet connection to 172.19.0.2:1900
DEBUG router: ip_is_private=true => route(direct)
INFO outbound/direct[direct]: outbound packet connection
Observed impact / 实际影响
- About 33,550 connections were created in approximately 21 seconds for UDP 5351, peaking around 2,078 connections per second.
- UDP 5351 在约 21 秒内产生约 33,550 个连接,峰值约为每秒 2,078 个连接。
- After blocking only 5351, about 394,336 UDP 1900 connections were logged within a few minutes.
- 仅拦截 5351 后,UDP 1900 在几分钟内又产生约 394,336 条连接日志。
- The trace log grew to approximately 308 MB.
- trace 日志增长到约 308 MB。
Expected behavior / 预期行为
Packets targeting the sing-box TUN peer or any address in the same TUN range should not be emitted through direct back into that same TUN. They should either be rejected as a TUN loop or sent through a non-TUN interface when that is valid.
发往 sing-box TUN 对端或同一 TUN 网段内地址的数据包,不应通过 direct 再次进入同一个 TUN。它们应当被识别为 TUN 回环并拒绝,或者在条件允许时通过非 TUN 接口发送。
Verified workaround / 已验证的临时解决方案
Placing the following rule before ip_is_private => direct stops the loop:
将以下规则放在 ip_is_private => direct 之前,可以停止循环:
{
"inbound": ["tun"],
"network": ["udp"],
"ip_cidr": ["172.19.0.2/32", "fdfe:dcba:9876::2/128"],
"port": [1900, 5351],
"action": "reject",
"method": "drop"
}
After applying this rule and restarting sing-box, tailscale netcheck produces no forwarded connections to 172.19.0.2:1900 or 172.19.0.2:5351, and the log no longer grows abnormally.
应用该规则并重启 sing-box 后,运行 tailscale netcheck 不再产生转发到 172.19.0.2:1900 或 172.19.0.2:5351 的连接,日志也不再异常增长。
Source-level observations / 源码层面的观察
Tailscale's portmapper intentionally probes gateway UDP ports 5351 and 1900:
Tailscale 的 portmapper 会按设计探测网关的 UDP 5351 和 1900 端口:
https://github.com/tailscale/tailscale/blob/v1.98.2/net/portmapper/portmapper.go
sing-box interface auto-detection first calls interfaceFinder.ByAddr(remoteAddr):
sing-box 的接口自动检测会先调用 interfaceFinder.ByAddr(remoteAddr):
https://github.com/SagerNet/sing-box/blob/45ca32dcb966f07f97fc888fe8586e359dbe8405/route/network.go
The direct outbound contains isMyLoopbackAddress, which appears intended to reject connections to the TUN range, but that guard did not trigger in this Windows/system-stack UDP path:
direct 出站中包含 isMyLoopbackAddress,看起来用于拒绝发往 TUN 网段的连接,但在本次 Windows/system-stack UDP 路径上并未触发:
A related loop was reported in #4086, but on a different platform and with a different trigger.
#4086 报告了相关的回环现象,但平台和触发条件不同。
Request / 请求
Could you please confirm whether isMyLoopbackAddress is expected to catch 172.19.0.2 for a TUN configured as 172.19.0.1/30, and prevent this direct-outbound re-entry on Windows?
请确认:当 TUN 配置为 172.19.0.1/30 时,isMyLoopbackAddress 是否应当识别并拦截 172.19.0.2,从而阻止 Windows 上 direct 出站重新进入同一 TUN?
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 with route/network.go's interfaceFinder.ByAddr path and protocol/direct/outbound.go's isMyLoopbackAddress guard. Reproduce on Windows with minimal.json, sing-box run, and tailscale netcheck while tracing UDP 5351 and 1900. Done means packets targeting the TUN peer or range do not re-enter the same TUN and the trace log no longer grows in a loop.
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
- 58/100