auto_redirect 接收端口被访问时,route(direct) 触发无限自拨号回环。
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 38.1k
- Forks
- 4.6k
- Avg merge
- 19d 15h
- Merged PRs (30d)
- 1
Description
操作系统
Linux
系统版本
Ubuntu 26.04 LTS(Resolute Raccoon)
安装类型
sing-box 原始命令行程序
如果您使用图形客户端程序,请提供该程序版本。
No response
版本
sing-box version 1.14.0-beta.1
Environment: go1.24.7 linux/amd64
Revision: 8bc6787c7ff785e5f6343241affdadd5ca239bd7
CGO: disabled
该二进制由 2026-07-23 同步后的 `SagerNet/sing-box` `origin/testing` 源码自行编译,HEAD 为 `v1.14.0-beta.1` / `8bc6787c7ff785e5f6343241affdadd5ca239bd7`。
描述
当 TCP 客户端访问 sing-box 自动创建的 auto_redirect TCP 接收端口,且恢复出的原始目标为“本机或本地路由地址 + 当前 auto_redirect 接收端口”时,可以稳定触发无限自拨号回环。
本次本地隔离复现中:
- auto_redirect 随机监听端口为
3723,监听在0.0.0.0:3723。 - network namespace 客户端只发起一次
10.255.255.2:51810 -> 10.255.254.1:3723连接。 - 第一轮进入
inbound/tun[tun-in],原始目标仍为10.255.254.1:3723。 - 连接命中
ip_is_private=true => route(direct)。 - direct 出站再次连接
10.255.254.1:3723。 - 从第二轮开始,来源变为本机
10.255.254.1:<随机端口>,目标始终为10.255.254.1:3723。 - 每一轮都会再次进入同一个 auto_redirect listener、命中同一条路由规则并再次 direct 拨号。
- 只发起一次客户端连接,日志中却出现了 39 次相同目标入站和 39 次相同目标 direct 出站,其中 38 次入站来源为本机。
- 本次测试设置了
ulimit -n 256,循环最终在约 1 ms 内触发socket: too many open files。如果没有文件描述符和日志限制,可能持续消耗文件描述符、CPU、conntrack 和磁盘空间。
auto_detect_interface: true 没有阻止该场景。
与 #4086 的关系
该问题与 #4086 的“TUN + direct 出站导致回环/高 CPU”症状相似,但本 Issue 的触发条件更加明确且可以在本地确定性复现:
- 不依赖远程节点、DNS、规则集、测速或长时间负载;
- 只需一次 TCP 连接;
- 目标端口明确等于当前 auto_redirect TCP 接收端口;
- 可以直接观察到第一轮来自客户端,后续轮次全部来自 sing-box 自身 direct 拨号。
源码侧观察
当前依赖的 sing-tun 版本为:
github.com/sagernet/sing-tun v0.8.12-0.20260722035624-8cededca4cc1
相关代码路径:
sing-tun/redirect_linux.go:未指定自定义端口时,在 IPv4 unspecified address(0.0.0.0)和随机端口上启动 redirect server;sing-tun/redirect_server.go:AcceptTCP()后调用control.GetOriginalDestination(conn),随后无递归检查地调用handler.NewConnectionEx(...);sing-tun/redirect_nftables.go:inet4_local_address_set只从接口地址生成,本地路由但未配置到接口的地址不会进入集合;sing-box/common/dialer/default.go:direct 的默认 dialer 会附加 auto_redirect output mark。
需要特别说明:实测的后续 direct 套接字会被设置 0x2024 output mark,通常会在 nftables output 链提前 return,因此后续连接不一定需要再次执行 redirect。但其目标本来就是 0.0.0.0:<auto_redirect 端口> 所监听的地址和端口,所以即使绕过 nftables redirect,连接仍会直接进入同一个 redirect listener。listener 恢复出的目标仍是自身地址和端口,随后又被路由到 direct,形成递归。
换言之,output mark 能避免“普通 direct 流量再次被重定向”,但不能避免“direct 直接拨打 auto_redirect listener 自身”。
期望行为
当恢复出的原始目标同时满足以下条件时:
- 目标地址属于本机或本地路由地址;
- 目标端口等于当前 auto_redirect TCP 接收端口;
sing-box 不应继续通过 direct 出站拨打自己的 auto_redirect 接收端口。程序应 bypass、reject,或通过其他方式阻止递归。
建议在 redirect listener 或路由前增加自身监听端点检查,或者增加递归深度/相同目标熔断,避免单个外部 SYN 导致无限资源消耗。
重现方式
以下命令均以 root 执行。复现前确认没有其他 sing-box 实例管理 inet sing-box 表。
本问题的对象就是 TUN auto_redirect,因此最简配置不可移除 sing-box 自身的 TUN inbound;除此之外不依赖远程服务器、代理节点、DNS、规则集、图形客户端或闭源组件。
1. 内核模块
modprobe tun
modprobe nfnetlink_queue
modprobe nft_queue
lsmod | grep -E 'nfnetlink_queue|nft_queue|tun'
本机实际输出:
nft_queue 12288 0
nfnetlink_queue 32768 0
nf_tables 409600 1 nft_queue
nfnetlink 20480 4 nfnetlink_queue,nf_tables
TUN 在本机内核中为 built-in,因此没有单独出现在 lsmod 中。测试日志中没有 nfqueue not available 或 pre-match disabled,并且 nftables 中存在 queue flags bypass to 100,说明本次 NFQUEUE 可用。
2. 创建隔离客户端网络
ip netns add sb-client
ip link add sb-host type veth peer name sb-client
ip addr add 10.255.255.1/30 dev sb-host
ip link set sb-host up
ip link set sb-client netns sb-client
ip netns exec sb-client ip link set lo up
ip netns exec sb-client ip addr add 10.255.255.2/30 dev sb-client
ip netns exec sb-client ip link set sb-client up
ip netns exec sb-client \
ip route add 10.255.254.1/32 via 10.255.255.1
3. 创建未配置到接口、但由本机处理的目标地址
auto_detect_interface: true 在本机把 direct socket 绑定到默认出接口 ens3。若将测试 local route 指向 lo,direct 拨号会在 5 秒后超时,只产生一轮,不会复现。因此本机成功复现使用与自动检测结果一致的 ens3:
ip route add table local local 10.255.254.1/32 dev ens3
ip address | grep 10.255.254.1
ip route show table local | grep 10.255.254.1
ip route get 10.255.254.1
实际结果:
# ip address | grep 10.255.254.1
# 无输出
# ip route show table local | grep 10.255.254.1
local 10.255.254.1 dev ens3 scope host
# ip route get 10.255.254.1
local 10.255.254.1 dev lo src 10.255.254.1 uid 0
cache <local>
其他机器应将 ens3 替换为 auto_detect_interface 实际选择的默认出接口。
4. 最简 sing-box 配置
保存为 /tmp/sing-box-auto-redirect-loop.json:
{
"log": {
"level": "debug",
"timestamp": true
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"address": [
"172.19.0.1/30"
],
"auto_route": true,
"strict_route": true,
"auto_redirect": true,
"dns_mode": "disabled"
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
}
],
"route": {
"rules": [
{
"ip_is_private": true,
"outbound": "direct"
}
],
"final": "direct",
"auto_detect_interface": true
}
}
配置检查:
sing-box check -c /tmp/sing-box-auto-redirect-loop.json
当前版本检查成功,不需要修改任何字段。
5. 受限启动
bash -c '
ulimit -n 256
exec sing-box run \
-c /tmp/sing-box-auto-redirect-loop.json
' >/tmp/sing-box-auto-redirect-loop.log 2>&1 &
SB_PID=$!
for i in $(seq 1 50); do
nft list table inet sing-box >/dev/null 2>&1 && break
sleep 0.1
done
PORT="$(
nft list table inet sing-box |
sed -n 's/.*redirect to :\([0-9][0-9]*\).*/\1/p' |
head -n 1
)"
echo "auto_redirect port: $PORT"
ps -fp "$SB_PID"
ss -lntp | grep ":${PORT}"
nft -a list table inet sing-box \
>/tmp/sing-box-auto-redirect-nft.txt
nft list set inet sing-box inet4_local_address_set
本次实际端口及监听:
auto_redirect port: 3723
LISTEN 0 16384 0.0.0.0:3723 0.0.0.0:* users:(("sing-box",pid=266617,fd=10))
本机地址集合:
table inet sing-box {
set inet4_local_address_set {
type ipv4_addr
flags interval
elements = { 10.255.255.0/30, 127.0.0.0/8,
172.19.0.0/30, 185.184.223.0/24 }
}
}
集合包含真实接口网段 10.255.255.0/30、loopback、TUN 网段及默认出接口网段,但不包含只有 local route、未配置到接口的 10.255.254.1。
nftables 关键规则:
chain prerouting_prematch {
type filter hook prerouting priority dstnat - 1; policy accept;
meta mark 0x00002024 ct mark set meta mark counter packets 0 bytes 0 return
ct mark 0x00002024 return
ct mark 0x00002023 return
tcp flags & (syn | ack) == syn counter packets 698 bytes 35860 queue flags bypass to 100
}
chain output_prematch {
type filter hook output priority mangle - 1; policy accept;
meta mark 0x00002024 ct mark set meta mark counter packets 483 bytes 670390 return
ct mark 0x00002024 return
ct mark 0x00002023 return
tcp flags & (syn | ack) == syn counter packets 63 bytes 4336 queue flags bypass to 100
}
chain output {
type nat hook output priority mangle + 1; policy accept;
meta mark 0x00002024 counter packets 35 bytes 2325 return
ct mark 0x00002024 counter packets 0 bytes 0 return
ip daddr @inet4_local_address_set counter packets 23 bytes 1421 return
meta nfproto ipv4 meta l4proto tcp counter packets 10 bytes 520 redirect to :3723 return
}
chain prerouting {
type nat hook prerouting priority dstnat + 1; policy accept;
ct mark 0x00002024 counter packets 0 bytes 0 return
iifname "tun0" counter packets 0 bytes 0 return
ip daddr @inet4_local_address_set counter packets 692 bytes 36163 return
meta nfproto ipv4 meta l4proto tcp counter packets 0 bytes 0 redirect to :3723 return
}
6. 只发起一次客户端连接并保持 3 秒
ip netns exec sb-client \
python3 - "$PORT" <<'PY'
import socket
import sys
import time
port = int(sys.argv[1])
sock = socket.create_connection(
("10.255.254.1", port),
timeout=3,
)
time.sleep(3)
sock.close()
PY
命令完成后立即停止:
kill "$SB_PID" 2>/dev/null || true
sleep 0.2
kill -9 "$SB_PID" 2>/dev/null || true
wait "$SB_PID" 2>/dev/null || true
7. 统计结果
grep -c \
"inbound connection to 10.255.254.1:${PORT}" \
/tmp/sing-box-auto-redirect-loop.log
grep -c \
"outbound connection to 10.255.254.1:${PORT}" \
/tmp/sing-box-auto-redirect-loop.log
grep -c \
"inbound redirect connection from 10.255.255.2:" \
/tmp/sing-box-auto-redirect-loop.log
grep -c \
"inbound redirect connection from 10.255.254.1:" \
/tmp/sing-box-auto-redirect-loop.log
实际结果:
相同目标入站:39
相同目标 direct 出站:39
客户端来源:1
本机自连接来源:38
8. bypass 对照
仅将规则改为:
{
"ip_is_private": true,
"action": "bypass",
"outbound": "direct"
}
其余配置、local route、文件描述符限制和单次 3 秒客户端连接均不变。对照实例的随机 auto_redirect 端口为 7763。
实际结果与原先“bypass 应切断循环”的预期不符:
相同目标入站:42
相同目标 direct 出站:42
客户端来源:1
本机自连接来源:41
too many open files 日志:5
ss -ant 行数:18 -> 67
nf_conntrack_count:31 -> 81
日志明确显示 bypass(direct) 后仍由 direct 拨打相同监听端口:
+0800 2026-07-24 00:11:52 INFO [2692202996 0ms] inbound/tun[tun-in]: inbound redirect connection from 10.255.255.2:59178
+0800 2026-07-24 00:11:52 INFO [2692202996 0ms] inbound/tun[tun-in]: inbound connection to 10.255.254.1:7763
+0800 2026-07-24 00:11:52 DEBUG [2692202996 0ms] router: match[0] ip_is_private=true => bypass(direct)
+0800 2026-07-24 00:11:52 INFO [2692202996 0ms] outbound/direct[direct]: outbound connection to 10.255.254.1:7763
+0800 2026-07-24 00:11:52 INFO [84296351 0ms] inbound/tun[tun-in]: inbound redirect connection from 10.255.254.1:6136
+0800 2026-07-24 00:11:52 INFO [84296351 0ms] inbound/tun[tun-in]: inbound connection to 10.255.254.1:7763
+0800 2026-07-24 00:11:52 DEBUG [84296351 0ms] router: match[0] ip_is_private=true => bypass(direct)
+0800 2026-07-24 00:11:52 INFO [84296351 0ms] outbound/direct[direct]: outbound connection to 10.255.254.1:7763
这说明在“目标就是 listener 自身端口”的场景,NFQUEUE bypass/output mark 只能绕过重定向,不能阻止 direct 直接连接 wildcard listener。本问题不能仅依赖用户添加 bypass 规则规避。
9. 清理
ip route del table local local 10.255.254.1/32 dev ens3 \
2>/dev/null || true
ip netns del sb-client 2>/dev/null || true
ip link del sb-host 2>/dev/null || true
nft delete table inet sing-box 2>/dev/null || true
日志
触发前不存在其他 sing-box 进程。本次仅执行一次 namespace 客户端连接。
第一轮客户端连接及随后两轮本机自连接:
+0800 2026-07-23 23:55:29 INFO [2563400103 0ms] inbound/tun[tun-in]: inbound redirect connection from 10.255.255.2:51810
+0800 2026-07-23 23:55:29 INFO [2563400103 0ms] inbound/tun[tun-in]: inbound connection to 10.255.254.1:3723
+0800 2026-07-23 23:55:29 DEBUG [2563400103 0ms] router: match[0] ip_is_private=true => route(direct)
+0800 2026-07-23 23:55:29 INFO [2563400103 0ms] outbound/direct[direct]: outbound connection to 10.255.254.1:3723
+0800 2026-07-23 23:55:29 INFO [840279609 0ms] inbound/tun[tun-in]: inbound redirect connection from 10.255.254.1:49330
+0800 2026-07-23 23:55:29 INFO [840279609 0ms] inbound/tun[tun-in]: inbound connection to 10.255.254.1:3723
+0800 2026-07-23 23:55:29 DEBUG [840279609 0ms] router: match[0] ip_is_private=true => route(direct)
+0800 2026-07-23 23:55:29 INFO [840279609 0ms] outbound/direct[direct]: outbound connection to 10.255.254.1:3723
+0800 2026-07-23 23:55:29 INFO [2596850063 0ms] inbound/tun[tun-in]: inbound redirect connection from 10.255.254.1:49340
+0800 2026-07-23 23:55:29 INFO [2596850063 0ms] inbound/tun[tun-in]: inbound connection to 10.255.254.1:3723
+0800 2026-07-23 23:55:29 DEBUG [2596850063 0ms] router: match[0] ip_is_private=true => route(direct)
+0800 2026-07-23 23:55:29 INFO [2596850063 0ms] outbound/direct[direct]: outbound connection to 10.255.254.1:3723
中间继续以新的本机源端口重复完全相同的四行模式。最后一轮:
+0800 2026-07-23 23:55:29 INFO [551465335 0ms] inbound/tun[tun-in]: inbound redirect connection from 10.255.254.1:49754
+0800 2026-07-23 23:55:29 INFO [551465335 0ms] inbound/tun[tun-in]: inbound connection to 10.255.254.1:3723
+0800 2026-07-23 23:55:29 DEBUG [551465335 0ms] router: match[0] ip_is_private=true => route(direct)
+0800 2026-07-23 23:55:29 INFO [551465335 0ms] outbound/direct[direct]: outbound connection to 10.255.254.1:3723
+0800 2026-07-23 23:55:29 ERROR [551465335 1ms] connection: open connection to 10.255.254.1:3723 using outbound/direct[direct]: dial tcp 10.255.254.1:3723: socket: too many open files
支持我们
- 我已经 赞助
完整性要求
- 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
- 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
- 我保证提供了可用于重现我报告的错误的最简配置,而不是依赖远程服务器、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
First reproduce the loop with the supplied TUN auto_redirect configuration and inspect the generated nftables rules. Read sing-tun/redirect_linux.go, sing-tun/redirect_server.go, sing-tun/redirect_nftables.go, and sing-box/common/dialer/default.go to trace listener acceptance, original-destination recovery, and direct dialing. Done means a connection targeting the auto_redirect listener cannot recursively create direct connections or exhaust resources, with the reproduction no longer showing repeated self-connections.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, linux
- Domain
- networking, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100