SagerNet / SagerNet/sing-box

[Windows][TUN] direct outbound loops into the same TUN / direct 出站回环进入同一 TUN

Open
#4,415 0 comments 0 reactions 0 assignees View on GitHub

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: truestrict_route: trueroute.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, Go 1.26.5
  • sing-box:1.13.18windows/amd64,Go 1.26.5
  • sing-box revision: 45ca32dcb966f07f97fc888fe8586e359dbe8405
  • sing-box 修订版本:45ca32dcb966f07f97fc888fe8586e359dbe8405
  • Tailscale: 1.98.2 (aaf7caef13becf6989e9e81f66412f3edc564c38)
  • Tailscale:1.98.2aaf7caef13becf6989e9e81f66412f3edc564c38
  • 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 / 复现步骤

  1. Start the Tailscale Windows service.
    启动 Tailscale Windows 服务。
  2. Run sing-box as Administrator: sing-box run -c minimal.json.
    以管理员身份运行 sing-box:sing-box run -c minimal.json
  3. Run tailscale netcheck, or wait for Tailscale port-mapping discovery.
    运行 tailscale netcheck,或者等待 Tailscale 自动进行端口映射探测。
  4. 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:1900172.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 路径上并未触发:

https://github.com/SagerNet/sing-box/blob/45ca32dcb966f07f97fc888fe8586e359dbe8405/protocol/direct/outbound.go

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

  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 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.