OpenWrt 上 IPv6 UDP 经 direct 出站重新进入 TUN,单个数据包被放大为持续路由回环。
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 38.2k
- Forks
- 4.6k
- Avg merge
- 19d 15h
- Merged PRs (30d)
- 1
Description
操作系统
Linux
系统版本
ImmortalWrt 24.10.6 r33869-cf234f8de6d5 Linux 6.6.133 架构:aarch64 目标平台:rockchip/armv8
安装类型
sing-box 原始命令行程序
如果您使用图形客户端程序,请提供该程序版本。
No response
版本
sing-box version 1.14.0-alpha.46
Environment: go1.25.12 linux/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_naive_outbound,with_usbip,badlinkname,tfogo_checklinkname0,with_musl
Revision: 8cbc2e4e1379d260764ad9884330da2433b1d4d3
CGO: enabled
描述
在 OpenWrt/ImmortalWrt 上使用 TUN inbound,并同时开启 auto_route、auto_redirect、strict_route 和 route.auto_detect_interface 后,
发往公网 IPv6 地址的 UDP 流量经 direct outbound 出站时会重新进入 sing-box 自己的 TUN。
一个局域网客户端只需偶尔发送一个 1~16 字节的 UDP 数据包,进入 sing-box 后就会被放大成约 200 个新 UDP packet connection/秒,并持续循环。
实际表现:
- 在 br-lan/eth1 上,原始客户端数秒甚至十几秒才发送一个 UDP 包。
- 在 tun0 上,同一目的地址和相同 UDP payload 会在约 0.47 秒内出现 100 次。
- 每次循环都会生成新的随机 UDP 源端口。
- sing-box 因此将每次循环识别为新的 packet connection。
- 每轮产生多条 inbound、sniff、route 和 outbound 日志。
- 日志曾在约 17 分钟内增长超过 400 MiB,轮转时达到约 541 MiB、410 万行。
- 将日志级别改为 warn 只能隐藏现象,不能终止数据包循环。
原始客户端并没有以该速率发送流量,放大发生在 sing-box TUN/direct 路径内部。
这个问题与以下 issue 很相似:
https://github.com/SagerNet/sing-box/issues/4086
但本问题能够通过 IPv6 UDP 小包稳定触发,并且在 1.14.0-alpha.46(revision 8cbc2e4e)上仍然存在。
初步定位到的疑似源码原因如下。
direct outbound 使用 common/dialer 创建默认 Dialer:
protocol/direct/outbound.go:51-74
当 route.auto_detect_interface=true 时,common/dialer/default.go 原本会安装 AutoDetectInterfaceFunc:
common/dialer/default.go:93-135
但是,只要 auto_redirect 注册了 autoRedirectOutputMark,route/network.go 中的 AutoDetectInterfaceFunc 就会直接返回 nil,不再执行
BindToInterface:
return func(network, address string, conn syscall.RawConn) error {
if r.autoRedirectOutputMark != 0 {
return nil
}
return bindFunc(network, address, conn)
}
位置:
route/network.go:345-375
随后只对 socket 设置 auto-redirect output mark:
return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn)
位置:
route/network.go:394-412
在本机 OpenWrt IPv6 环境中,WAN 默认路由是按公网源前缀匹配的 source-specific route。
当 UDP direct socket 没有绑定 pppoe-wan,也没有预先选择公网 IPv6 源地址时,仅依靠 fwmark 无法保证选中 PPPoE 的 source-specific
IPv6 默认路由。该 socket 最终选择了 TUN 地址 fdfe:dcba:9876::1,并再次进入 table 2022。
由于 direct outbound 的 loopback 棹查只判断“目的地址是否属于本机 TUN 网段”,而本问题的目的地址是公网 IPv6,因此无法阻止这种“公网目的地址被内核重新路由回 TUN”的循环。
相关代码:
protocol/direct/outbound.go:115-145
预期行为:
route.auto_detect_interface=true 应保证 direct outbound 不会重新进入 sing-box 自己的 TUN。
即使启用了 auto_redirect,也应该满足以下至少一种行为:
- direct outbound 仍绑定到检测出的默认物理接口;
- mark 路由能够确保从物理 WAN 表出站,包括 OpenWrt 的 IPv6 source-specific route;
- 在 direct socket 实际解析回 sing-box 自己的 TUN 时检测并拒绝该连接。
目前已知的临时规避方法是给公网 direct outbound 显式添加:
"bind_interface": "pppoe-wan"
已有 #4086 报告该方法能够阻止回环。
但是,同一个 direct outbound 往往还承担 ip_is_private/LAN 直连。直接绑定 pppoe-wan 可能破坏局域网访问,因此需要额外拆分 LAN direct 和 WAN direct。
重现方式
使用下面的完整最简配置。该配置不依赖代理服务器,也不需要目的 UDP 服务端返回任何数据;只需要系统具有可用的公网 IPv6 路由。
config.json:
{
"log": {
"level": "debug",
"timestamp": true
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"interface_name": "tun-test",
"address": [
"172.19.0.1/30",
"fdfe:dcba:9876::1/126"
],
"auto_route": true,
"auto_redirect": true,
"strict_route": true
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
}
],
"route": {
"auto_detect_interface": true,
"final": "direct"
}
}
启动:
sing-box check -c config.json
sing-box run -c config.json
从局域网客户端向任意可路由的公网 IPv6 地址发送一个很小的 UDP 包。
本次实际触发使用的目标为:
[240e:930:1000:500:800::3e]:20001
[240e:f7:8013:409:800::e]:20002
例如:
printf x | nc -u -w 1 240e:930:1000:500:800::3e 20001
同时抓取 LAN 和 TUN:
tcpdump -ni br-lan -e -vv \
'ip6 and udp and dst host 240e:930:1000:500:800::3e and dst port 20001'
tcpdump -ni tun-test -vv \
'ip6 and udp and dst host 240e:930:1000:500:800::3e and dst port 20001'
实际结果:
- br-lan 上只出现客户端原始 UDP 包。
- TUN 中相同 payload 持续重复出现。
- 每轮使用不同的随机源端口。
- sing-box 日志持续生成新的 inbound packet connection。
- 即使客户端不以高频率继续发送,TUN 内仍会在一段时间内保持高频循环。
本机实际抓包中,100 个 TUN 数据包约 0.47 秒完成,约为 200 包/秒。
路由对照:
ip -6 route get 240e:930:1000:500:800::3e
结果:
240e:930:1000:500:800::3e from :: via fdfe:dcba:9876::2 dev tun0 table 2022 src fdfe:dcba:9876::1
指定 PPPoE 公网源地址:
ip -6 route get 240e:930:1000:500:800::3e from <WAN_PUBLIC_IPV6>
结果:
240e:930:1000:500:800::3e from <WAN_PUBLIC_IPV6> via <WAN_GATEWAY> dev pppoe-wan
指定 LAN 获得的公网 IPv6:
ip -6 route get 240e:930:1000:500:800::3e from <LAN_PUBLIC_IPV6>
结果同样通过 pppoe-wan。
指定 TUN 源地址:
ip -6 route get 240e:930:1000:500:800::3e from fdfe:dcba:9876::1
结果:
240e:930:1000:500:800::3e from fdfe:dcba:9876::1 via fdfe:dcba:9876::2 dev tun0 table 2022
建议的对照实验:
将 direct outbound 临时改为:
{
"type": "direct",
"tag": "direct",
"bind_interface": "pppoe-wan"
}
重新启动后执行相同 UDP 测试。
预期对照结果是数据包只从 pppoe-wan 发出,不再重新进入 TUN。
日志
以下为循环期间的连续日志片段。连接 ID 和源端口每次变化,但目标地址、目标端口和 payload 保持一致:
+0800 2026-07-17 22:18:17 INFO [3875553281 0ms] inbound/tun[0]: inbound packet connection from [fdfe:dcba:9876::1]:35994
+0800 2026-07-17 22:18:17 INFO [3875553281 0ms] inbound/tun[0]: inbound packet connection to [240e:930:1000:500:800::3e]:20001
+0800 2026-07-17 22:18:17 DEBUG [3875553281 0ms] router: match[3] => sniff
+0800 2026-07-17 22:18:17 DEBUG [3875553281 0ms] router: match[15] rule_set=[geoip-cn ...] => route(直连)
+0800 2026-07-17 22:18:17 INFO [3875553281 0ms] outbound/direct[直连]: outbound packet connection
随后立即出现新的随机源端口:
+0800 2026-07-17 22:18:17 INFO [NEW_CONNECTION_ID 0ms] inbound/tun[0]: inbound packet connection from
[fdfe:dcba:9876::1]:NEW_RANDOM_PORT
+0800 2026-07-17 22:18:17 INFO [NEW_CONNECTION_ID 0ms] inbound/tun[0]: inbound packet connection to
[240e:930:1000:500:800::3e]:20001
+0800 2026-07-17 22:18:17 DEBUG [NEW_CONNECTION_ID 0ms] router: match[3] => sniff
+0800 2026-07-17 22:18:17 DEBUG [NEW_CONNECTION_ID 0ms] router: match[15] rule_set=[geoip-cn ...] => route(直连)
+0800 2026-07-17 22:18:17 INFO [NEW_CONNECTION_ID 0ms] outbound/direct[直连]: outbound packet connection
实际 LAN 抓包:
5c:ea:1d:xx:xx:xx > ROUTER_MAC, ethertype IPv6:
LAN_PUBLIC_IPV6.13728 > 240e:930:1000:500:800::3e.20001:
UDP, length 1
约 12 秒内只捕获到一个对应 LAN 包。
实际 TUN 抓包:
fdfe:dcba:9876::1.48663 > 240e:930:1000:500:800::3e.20001: UDP, length 1
fdfe:dcba:9876::1.41459 > 240e:930:1000:500:800::3e.20001: UDP, length 1
fdfe:dcba:9876::1.38228 > 240e:930:1000:500:800::3e.20001: UDP, length 1
fdfe:dcba:9876::1.39461 > 240e:930:1000:500:800::3e.20001: UDP, length 1
fdfe:dcba:9876::1.48841 > 240e:930:1000:500:800::3e.20001: UDP, length 1
100 packets captured
214 packets received by filter
0 packets dropped by kernel
100 个包约在 0.47 秒内完成。
防火墙计数器同时显示大量流量来自 tun0:
iifname "tun0" counter packets 2032033 bytes 380267339 accept
轮转前日志统计:
567697722 bytes
4102830 lines
该日志从 22:00 左右开始迅速增长,到 22:17 轮转时达到约 541.4 MiB。
支持我们
- 我已经 赞助
完整性要求
- 我保证阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值。
- 我保证提供了可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件。
- 我保证提供了可用于重现我报告的错误的最简配置,而不是依赖远程服务器、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
Reproduce the minimal IPv6 UDP loop with the provided config, using ip -6 route get and tcpdump on br-lan and tun-test. Read protocol/direct/outbound.go, common/dialer/default.go, and route/network.go at the cited ranges, then trace auto_detect_interface and auto_redirect together. Done means direct traffic no longer re-enters the TUN while LAN direct routing remains usable, with a regression test or documented validation for the reproduced case.
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
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100