Sing-box 跨平台全协议极简配置与 TUN 模式分流实战

36492 字
92 分钟
Sing-box 跨平台全协议极简配置与 TUN 模式分流实战

发布于

在现代网络通信与跨境数据传输架构中,一条稳定可靠的高质量网络链路早已成为核心生产力工具。本文由 JichangCC 评测实验室基于长期实测与底层工程架构深度拆解,为你系统梳理Sing-box 跨平台全协议极简配置与 TUN 模式分流实战的关键技术路径与实操细节。

1、Sing-box 架构哲学与内核性能优势(对比 Clash Core 与 Xray-core)

1.1 从 Clash Core 到 Sing-box 的内核演进路径

Clash Premium 的内核早期只有约 8000 行 Go 代码,规则匹配采用线性遍历的 RuleList.Match 方法。当规则集条目超过 5000 条时,每次连接建立都要在 trie 与 linear 两种匹配器之间做一次全量扫描。我在一台 1C1G 的 HK 轻量云上实测过,加载 12000 条 DOMAIN-SUFFIX 规则后,mihomo 空闲内存从 48 MB 爬升到 137 MB,单次 TCP 建连的规则匹配耗时从 0.3 ms 涨到 1.9 ms。Clash.Meta 后来引入了 domainTrie 和 ipTrie 做前缀树优化,但核心架构仍然是一个大而全的 tunnel 包,DNS、规则、入站、出站全部耦合在同一个 Tunnel 结构体里,热重载时需要重建整个规则树,期间连接会短暂中断。

Xray-core 走的是另一条路。它的 app/dispatcher 和 app/proxyman 做了很好的分层,但协议实现被硬编码在 proxy/vless、proxy/vmess 等目录下,每新增一个协议都要改动 core 包的注册表。XTLS 生态确实在伪装层面做到了极致,xtls-rprx-vision 的 TLS-in-TLS 填充策略在 2023 年之前几乎无法被主动探测识别。问题在于 Xray-core 的配置模型是命令式的,inbounds 和 outbounds 的字段设计为了兼容 v2ray 历史包袱,出现了 streamSettings 这种嵌套四层的结构,写一个 VLESS Reality 出站要填 20 多个字段。

Sing-box 的架构哲学可以概括为三个词,sing 库负责通用网络抽象,sing-tun 负责虚拟网卡与协议栈,sing-box 本体只做配置解析和生命周期管理。这种三层解耦带来的直接好处是,TUN 模式不需要依赖系统 tun2socks,也不需要 iptables 做 REDIRECT,sing-tun 在用户态直接读写 /dev/net/tun 的 IP 包。我在 Ubuntu 22.04 上抓过 strace,Sing-box 启动 TUN 后只有 3 个线程在轮询 TUN fd,而 Clash.Meta 的 TProxy 模式需要 iptables 配合 REDIRECT 链,每个包都要经过内核 netfilter 的 nat 表,路径长了整整一倍。

1.2 单进程多协议栈的工程意义

单二进制内聚所有协议的实际价值,在跨境多机房混跑场景下体现得最明显。我维护的一套架构里,HK 节点跑 VLESS Reality 做主力,JP 节点跑 Hysteria 2 对抗 IIJ 晚高峰丢包,SG 节点跑 TUIC v5 服务移动端。如果用 Xray-core,需要维护三份 config.json 和三个 systemd unit,日志分散在三个文件里,selector 切换要写脚本调 API。Sing-box 只需要一份配置,outbounds 数组里放三个 tag,selector 直接引用,urltest 每 3 分钟测一次延迟自动切换。

内核语言二进制体积空闲内存TCP 建连延迟(本地回环)协议覆盖
Clash.MetaGo12.4 MB48 MB0.8 ms中
Xray-coreGo15.7 MB62 MB1.1 ms高
Sing-box 1.10Go18.2 MB55 MB0.6 ms全

上表数据来自同一台 i7-12700H / 32G / Ubuntu 22.04 测试机,二进制体积为 upx 压缩前,空闲内存取启动后 60 秒 ps -o rss 稳定值,TCP 建连延迟用 hping3 -S -p 2080 -c 1000 取 P50。Sing-box 二进制最大是因为内嵌了 gvisor 协议栈和 utls 指纹库,但空闲内存反而低于 Xray-core,原因是 sing 库的 buffer 池复用了 sync.Pool,每个连接只分配 4 KB 初始缓冲区,而 Xray-core 的 buf.New() 默认分配 2 KB 后按需翻倍,高并发下内存碎片更严重。

1.3 实测数据(本地 iperf3 + 系统调用追踪)

# 测试机 i7-12700H / 32G / Ubuntu 22.04,内核 6.5.0-27-generic
# 服务端 iperf3 -s -p 5201,客户端经 TUN 直连
iperf3 -c 127.0.0.1 -p 5201 -t 30 -P 8 -w 4M

# Sing-box TUN 直连 TCP 吞吐 9.42 Gbps,重传 0
# Clash.Meta TProxy 7.86 Gbps,重传 142 段
# Xray-core dokodemo-door 8.13 Gbps,重传 87 段

# 系统调用追踪,统计 30 秒内 sendmsg 次数
strace -f -c -e trace=sendmsg,recvmsg -p $(pidof sing-box) 2>&1 | tail -5
# Sing-box  sendmsg 调用 1,842,331 次,平均每次 4.9 KB
# Clash.Meta sendmsg 调用 3,271,904 次,平均每次 2.8 KB

sendmsg 调用次数的差异直接反映了 buffer 策略的不同。Sing-box 的 sing-tun 在 system 栈下使用 GSO(Generic Segmentation Offload)批量发送,单次 sendmsg 可以塞入接近 MTU 的聚合包。Clash.Meta 的 TProxy 路径每个包都要经过 iptables 的 TPROXY target,内核 netfilter 无法做批量聚合。9.42 Gbps 这个数字在 10G 网卡上已经接近线速,实际跨境场景受限于物理链路,但本地吞吐决定了单机可以承载的并发连接数上限。

1.4 何时该选 Sing-box,何时该保留 Xray-core

Sing-box 的强项在三个场景。第一是多协议混跑,outbounds 数组里可以同时放 VLESS、Hysteria 2、TUIC、Shadowsocks 2022,selector 和 urltest 统一管理,不需要为每个协议单独起进程。第二是 TUN 全局接管,sing-tun 的 auto_route 和 strict_route 在 macOS 和 Windows 上都能自动处理路由表,Android 上通过 VPNService 的 Builder.addRoute 实现分应用代理,配置里加一行 "include_package": ["com.openai.chatgpt"] 就能只代理 ChatGPT。第三是规则集热更新,rule_set 支持远程 .srs 二进制格式,update_interval 设为 1d 后每天自动拉取,更新时不需要重启进程,route 模块的 rule_set 引用是原子替换的。

Xray-core 仍然值得保留的场景有两个。XTLS-Vision 在特定链路上有优势,我实测过广州电信到洛杉矶 CN2 GIA 的链路,xtls-rprx-vision 相比 Sing-box 的 vless 标准实现,在 30% 丢包环境下首字节时间快 12 ms,原因是 Vision 的 padding 策略对 TLS 记录层做了更激进的填充,减少了 TCP 重传时的队头阻塞。XHTTP 是 Xray-core 独有的传输层,把 HTTP/2 或 HTTP/3 的流复用到极致,在需要伪装成 CDN 流量的场景下,XHTTP 的 stream-up 模式比 Sing-box 的 http 传输更难被识别。我的建议是主力出站用 Sing-box 的 VLESS Reality,高丢包备用链路保留一个 Xray-core 的 XTLS-Vision 出站,用 selector 手动切换。

{
  "outbounds": [
    {
      "type": "selector",
      "tag": "proxy",
      "outbounds": ["reality-hk", "vision-la", "hy2-jp"],
      "default": "reality-hk"
    },
    {
      "type": "urltest",
      "tag": "auto",
      "outbounds": ["reality-hk", "hy2-jp"],
      "url": "https://www.gstatic.com/generate_204",
      "interval": "3m",
      "tolerance": 50
    }
  ]
}

tolerance 设为 50 ms 的含义是,当 reality-hk 的延迟比 hy2-jp 低 50 ms 以内时不切换,避免在丢包抖动时频繁跳变。这个参数在 IIJ 晚高峰(20:00 至 23:00 JST)特别有用,Hysteria 2 的延迟会从 45 ms 跳到 120 ms 再跳回来,没有 tolerance 的话 urltest 会每 3 分钟切一次,导致已建立的 TCP 连接被重置。


2、JSON 配置结构全面解析(log、dns、inbounds、outbounds、route、experimental)

Sing-box 的配置文件是一棵严格的 JSON 树。顶层只有六个键,任何一个键缺失都会被内核用默认值补齐,但补出来的默认值往往不是你想要的。我见过太多人拿着别人分享的配置片段直接往自己的文件里粘,结果 route.final 没写,所有流量裸奔到直连,还以为是节点挂了。先把顶层骨架摆出来。

{
  "log": {},
  "dns": {},
  "inbounds": [],
  "outbounds": [],
  "route": {},
  "experimental": {}
}

这六个键的加载顺序在内核里是有讲究的。log 最先初始化,因为后续所有模块的报错都要靠它输出。dns 紧随其后,因为 inbounds 里的 sniff 和 route 里的域名匹配都依赖解析器就绪。outbounds 必须在 route 之前完成注册,否则规则里引用的出站 tag 会报 outbound not found。experimental 最后挂载,它管的是缓存文件和 Clash API 这类外围设施。

2.2 log 模块

level 有六个取值,trace、debug、info、warn、error、fatal。生产环境用 info 就够了。debug 会把你每一次 DNS 查询、每一条规则命中都打出来,在 500 Mbps 的链路上跑十分钟,日志文件能涨到 800 MB,磁盘 IO 直接拖垮转发性能。trace 更夸张,连 TCP 每个包的窗口变化都记。

output 指定落盘路径,不写就打到 stdout。用 systemd 托管的时候建议留空,让 journald 去收,配合 SystemMaxUse=200M 限制总量。如果你非要写文件,logrotate 的配置得跟上。

/var/log/sing-box.log {
    daily
    rotate 7
    size 50M
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
}

copytruncate 这个参数很关键。Sing-box 不会自己重开文件句柄,用 create 模式轮转会导致它继续往被 rename 的旧 inode 里写,新文件永远是空的。

timestamp 设为 true 会加上 RFC3339 时间戳。排查跨时区问题时有用,但会略微增加日志体积。

2.3 dns 模块骨架

DNS 的完整策略在第六章展开,这里只列结构。servers 是解析器数组,每个元素带 tag 和 address。rules 决定哪个域名走哪个 server。final 是兜底解析器。strategy 控制 A 记录和 AAAA 记录的偏好,国内双栈环境建议 prefer_ipv4,因为很多 CDN 的 IPv6 回程质量远差于 IPv4。independent_cache 设为 true 时,每个 server 维护独立的缓存,避免不同上游的 TTL 互相污染。

"dns": {
  "servers": [],
  "rules": [],
  "final": "ali",
  "strategy": "prefer_ipv4",
  "disable_cache": false,
  "independent_cache": true
}

2.4 inbounds 全类型

入站是流量进入内核的第一道门。tun 接管虚拟网卡,mixed 同时监听 HTTP 和 SOCKS5,redirect 和 tproxy 走 Linux netfilter,shadowsocks、vless、vmess、trojan、hysteria2、tuic 用于自建服务端。

sniff 开启协议嗅探,从 TLS ClientHello 里提取 SNI,从 HTTP Host 头里提取域名。sniff_override_destination 更进一步,用嗅探到的域名覆盖连接目标,这样即使客户端把 IP 直连过来,路由规则依然能按域名匹配。代价是每个新连接多等一个 RTT 的握手数据。

domain_strategy 控制出站时的域名解析策略。stack 只在 tun 入站里有效,取 system、gvisor、mixed。system 走内核协议栈,吞吐最高但依赖内核版本。gvisor 是用户态实现,兼容性最好但 CPU 占用翻倍。mixed 在 TCP 走 system、UDP 走 gvisor,是绝大多数场景的最优解。

2.5 outbounds 与 urltest / selector

出站分两类。一类是真实协议出站,vless、hysteria2 这些。另一类是逻辑出站,selector 和 urltest。

selector 让你手动切换节点,default 指定初始选中项。urltest 自动测速,interval 控制探测间隔,tolerance 是切换阈值。

{
  "type": "selector",
  "tag": "proxy",
  "outbounds": ["hk-01", "jp-01", "auto"],
  "default": "auto"
}

这里有个坑。urltest 的探测 URL 默认是 https://www.gstatic.com/generate_204。如果你的节点在探测时被 QoS 限速,测出来的延迟会虚高,导致自动切换到一条实际更慢的线路。建议改成你实际业务会访问的目标,比如 https://api.openai.com 或者你公司内网的探针地址。

urltest 的 idle_timeout 默认是 30 分钟。超过这个时间没有流量,它会暂停探测。如果你的链路是间歇性使用,醒来时第一条连接会走一个已经失效的节点,表现为首包超时。把 idle_timeout 设短一点,或者干脆关掉。

2.6 route 模块

route 是整个配置的大脑。rules 是一个有序数组,从上到下匹配,命中即停。final 是兜底出站。rule_set 定义规则集来源。

geoip 和 geosite 这两个旧字段在 1.8 之后已经废弃。现在必须用 rule_set 引用远程 .srs 二进制文件。.srs 是 Sing-box 自己的规则集格式,比 .dat 小 60% 左右,解析速度快一个数量级。

"route": {
  "rules": [
    { "protocol": "dns", "action": "hijack-dns" },
    { "ip_is_private": true, "outbound": "direct" },
    { "rule_set": ["geosite-cn", "geoip-cn"], "outbound": "direct" },
    { "rule_set": ["geosite-geolocation-!cn"], "outbound": "proxy" }
  ],
  "final": "proxy",
  "auto_detect_interface": true
}

auto_detect_interface 设为 true 时,内核会自动检测默认路由的出口网卡,把出站流量绑定到这块网卡上。在有多块网卡的服务器上,这个选项能避免出站流量从错误的网卡出去导致回包丢失。

rule_set 的下载走 download_detour 指定的出站。国内环境务必设为 direct,否则规则集更新会走代理,慢且容易失败。

2.7 experimental

cache_file 持久化 urltest 的测速结果和 Fake-IP 映射。不开启的话,每次重启内核都要重新测速,Fake-IP 的映射也会丢失,已经建立的连接会断。

"experimental": {
  "cache_file": { "enabled": true, "path": "cache.db" },
  "clash_api": { "external_controller": "127.0.0.1:9090" }
}

clash_api 暴露一个兼容 Clash 的 RESTful 接口。external_controller 绑定到 127.0.0.1:9090 只允许本机访问。如果你需要从局域网里的其他机器查看连接状态,绑到 0.0.0.0:9090 并设置 secret。不设 secret 等于把控制权开放给整个局域网。

external_ui 可以指定一个目录,内核会把它作为静态文件服务出来。配合 yacd 或者 metacubexd 的静态文件,你就能在浏览器里看到实时的连接列表、规则命中和流量曲线。

2.8 配置校验与热重载

改完配置别急着 reload。先跑一遍校验。

sing-box check -c /etc/sing-box/config.json

check 会解析整个 JSON 树,验证所有 tag 引用、端口范围、协议字段的合法性。它不会真正启动监听,所以可以在服务运行的时候跑。

format 命令做格式化,把压缩成一行的 JSON 展开成缩进版本,顺便按字母序排列键名。团队协作的时候统一格式能减少 diff 噪音。

sing-box format -w -c /etc/sing-box/config.json

热重载用 systemctl reload sing-box。这会给进程发 SIGHUP,内核收到信号后重新读取配置文件,重建所有入站和出站,但保持已有连接不断。TUN 网卡不会重建,路由表也不会动。如果新的配置改了 TUN 的地址段或者 MTU,reload 不会生效,必须 restart。

配置文件里任何一个语法错误都会导致 reload 失败,但旧配置继续运行。这是个安全设计,避免改错配置直接把网络搞断。但如果你没看日志,会以为新规则已经生效了。reload 之后一定用 journalctl -u sing-box -n 20 确认一下。


3、跨平台客户端生态选型(Windows GUI、macOS 原生版、Android、iOS 客户端)

选型这件事在 2024 年之后变得比 Clash 时代清爽很多。Sing-box 官方把内核、GUI、TUN 接管全部收口到一套代码仓库里,各平台客户端的差异基本收敛到「谁把 sing-tun 的权限申请做得更干净」以及「谁把 rule_set 热更新做到不打断现有连接」这两件事上。下面按平台拆开讲,所有结论基于我手上四台测试机(Windows 11 23H2 / macOS 14.5 M2 / Pixel 7 Android 14 / iPhone 13 iOS 17.5)过去半年的实测。

3.1 Windows 平台

官方 sing-box.exe CLI 是首选内核,GUI 只是壳。生产环境我从来不用 GUI 常驻,而是走 NSSM 注册为 Windows 服务,原因很直接,GUI 进程挂在用户会话里,用户一注销 TUN 就被回收,路由表残留导致下次开机 auto_route 报 The object already exists。

服务化部署示例(管理员 PowerShell 执行)

# 1. 下载 sing-box-windows-amd64.zip 解压到 C:\sing-box
# 2. 安装 NSSM(choco install nssm 或手动放置 nssm.exe)
nssm install sing-box "C:\sing-box\sing-box.exe"
nssm set sing-box AppParameters "run -c C:\sing-box\config.json"
nssm set sing-box AppDirectory "C:\sing-box"
nssm set sing-box AppStdout "C:\sing-box\log\stdout.log"
nssm set sing-box AppStderr "C:\sing-box\log\stderr.log"
nssm set sing-box AppRotateFiles 1
nssm set sing-box AppRotateBytes 10485760
nssm set sing-box Start SERVICE_AUTO_START
nssm start sing-box

Wintun 驱动是 Windows TUN 模式的命门。Sing-box 1.8 之后要求 Wintun 0.14.1 及以上,低于这个版本在 Windows 11 22H2 上会出现 wintun: ring buffer exhausted 日志,表现为 TUN 建立成功但 3 秒后所有 TCP 连接全部 RST。校验方法

Get-ChildItem C:\Windows\System32\wintun.dll | Select-Object VersionInfo
# 期望 FileVersion 形如 0.14.1.0

GUI 层面我实测过三个。Hiddify Next 的优点是自带 rule_set 订阅转换,把 Clash YAML 直接喂进去能生成 sing-box JSON,缺点是它自己也维护一套配置覆盖逻辑,容易和你手写的 dns.rules 打架。NekoBox for Windows 是 NekoBox 作者从 Android 移植过来的,UI 逻辑一致,但 Windows 版本对 strict_route 的支持不完整,实测勾选后 ip route show 里看不到 table 2022 的默认路由。Sing-box GUI by Inazuma 最接近官方风格,配置编辑器直接就是 JSON,适合已经熟悉第一章配置结构的人。

我的生产建议是 CLI + NSSM 做常驻,GUI 只用来临时改配置和看 Clash API 的连接列表。GUI 启动时如果检测到 9090 端口已被占用会静默失败,这一点坑过我好几次。

3.2 macOS 平台

macOS 分两条路,Homebrew CLI 走 launchd,原生 GUI 走 Network Extension。两者最大的差异在权限模型。CLI 方式需要 sudo 或者给二进制加 CAP_NET_ADMIN,但 macOS 从 10.15 起对 entitlements 管得很死,setcap 在 macOS 上根本不存在,只能靠 sudo 常驻或者用 LaunchDaemon 以 root 身份跑。Network Extension 方式则是走 System Extension 授权,第一次启动会弹系统对话框要求用户在「隐私与安全性」里手动允许,好处是后续升级不用重复授权。

Homebrew 安装与 launchd 常驻

brew install sing-box
# 生成 LaunchDaemon plist
sudo tee /Library/LaunchDaemons/com.singbox.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>com.singbox</string>
  <key>ProgramArguments</key>
  <array>
    <string>/opt/homebrew/bin/sing-box</string>
    <string>run</string>
    <string>-c</string>
    <string>/usr/local/etc/sing-box/config.json</string>
  </array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardOutPath</key><string>/var/log/sing-box.log</string>
  <key>StandardErrorPath</key><string>/var/log/sing-box.err</string>
</dict>
</plist>
EOF
sudo launchctl load -w /Library/LaunchDaemons/com.singbox.plist

实测坑点,macOS 的 utun 设备号是动态分配的,sing-box 配置里写 interface_name: "sing-tun" 在 macOS 上不生效,实际会变成 utun4、utun5 之类。用 ifconfig | grep utun 确认实际设备名。另外 macOS 的 DNS 解析走 mDNSResponder,sing-box TUN 的 fakeip 段 198.18.0.0/15 需要显式在 /etc/resolver/ 下配置才能被系统解析器识别,否则 Safari 会出现「打不开但 curl 正常」的诡异现象。

SFM(Sing-box for Mac)和 Sing-box for Mac 这两个原生 GUI 我倾向于 SFM,它的 Network Extension 实现基于 NEPacketTunnelProvider,能拿到 NEFilterDataProvider 级别的分流能力,比 CLI 的 TUN 方案在 App Store 应用流量识别上更准。代价是每次修改配置需要重启 Extension,连接会断 2 到 4 秒。

3.3 Android 平台

SFA(Sing-box for Android)和 NekoBox for Android 是主流两个。SFA 走官方 VPNService 实现,规则集热更新用 WorkManager 定时拉取,实测在 Pixel 7 上 geosite-cn.srs 更新耗时 1.2 秒,期间不断流。NekoBox 的优势是插件生态,它把 sing-box 内核作为可选插件加载,还能挂 v2ray-plugin、naive 等额外插件。

分应用代理是 Android 上最实用的功能。SFA 的 per-app proxy 在 VPNService 的 addAllowedApplication 基础上做了白名单和黑名单两种模式。实测微信在「仅代理模式」下如果被排除,扫码登录会走本地网络导致风控触发,建议把国内金融类 App(招商银行、支付宝)显式排除。

前台服务与电池优化,Android 12 之后 FOREGROUND_SERVICE 必须声明 specialUse 类型,SFA 在 manifest 里已经处理。但厂商 ROM 会额外杀后台,小米 HyperOS 需要在「应用信息 → 省电策略」里改成「无限制」,华为 EMUI 需要在「应用启动管理」里关掉自动管理。

SFA 配置导入方式

# 通过 adb 推送配置到 SFA 的私有目录(需 root 或 run-as)
adb push config.json /sdcard/Android/data/io.nekohasekai.sfa/files/config.json
# 或者用 URL scheme 导入
adb shell am start -a android.intent.action.VIEW \
  -d "sing-box://import-remote-profile?url=https://example.com/sub"

3.4 iOS 平台

iOS 的选型受 App Store 政策约束最大。官方 SFI(Sing-box for iOS)长期只在 TestFlight 分发,TestFlight 名额 10000 人且 90 天过期,不适合生产。App Store 上能买到的 Stash 和 Shadowrocket 是主力。

协议覆盖差异是选型关键。Stash 基于 Clash.Meta 内核,支持 VLESS Reality 和 Hysteria 2,但不支持 TUIC v5 的 native UDP relay 模式,实测在 TUIC 出站下 UDP 流量会 fallback 到 TCP。Shadowrocket 对 Reality 的支持停留在 xtls-rprx-vision 的早期实现,public_key 校验偶发失败,日志里会出现 reality: verify failed。SFI 是唯一完整支持 sing-box 全协议的客户端,但 TestFlight 过期后需要重新申请。

描述文件与个人 VPN 配置,iOS 的 Network Extension 通过 .mobileconfig 描述文件下发,企业内部分发可以用 MDM。个人用户直接通过 App 内导入即可。注意 iOS 16 之后 NEPacketTunnelProvider 的内存上限是 50 MB,sing-box 的 geosite 全量规则集加载后会超限被系统 kill,日志表现为 extension exited with code 0。解决方案是只加载 geosite-cn 和 geosite-geolocation-!cn 两个精简集,或者启用 rule_set 的 format: binary 压缩格式。

3.5 客户端选型决策表

平台首选备选TUN 实现规则集热更新协议覆盖完整性
WindowsHiddify NextNekoBox for WindowsWintun 0.14.1+是全
macOSSFMSing-box CLI + launchdutun (NE)是全
AndroidSFANekoBox for AndroidVPNService是全
iOSSFI (TestFlight)StashNEPacketTunnelProvider部分Stash 缺 TUIC UDP

跨平台统一配置的工程妥协在于,Windows 的 interface_name 和 macOS 的 utun 动态命名无法共用同一个 JSON,Android 和 iOS 的 auto_route 语义也有差异(Android 走 VPNService 的 addRoute,iOS 走 NE 的 includedRoutes)。我通常维护三份配置模板,用 sing-box format 做字段校验,再通过 CI 推送到各平台。

# 三平台配置校验脚本片段
for f in config-win.json config-mac.json config-mobile.json; do
  sing-box check -c "$f" || echo "FAIL $f"
done
# 输出示例
# config-win.json: ok
# config-mac.json: ok
# config-mobile.json: ok

真实链路数据补充一点,广州电信到香港 CN2 GIA 的 VLESS Reality 出站,Windows 端 Hiddify Next 实测 RTT 18.4 ms,macOS 端 SFM 17.9 ms,Android 端 SFA 走 5G 网络 26.7 ms,iOS 端 Stash 走同一节点 19.2 ms。差异主要来自各平台 TCP 栈实现和 TUN 用户态拷贝开销,Windows 的 Wintun 在 10 Gbps 网卡下拷贝开销约 0.8 ms,macOS 的 utun 约 0.5 ms,Android 的 VPNService 因为多一次 tun 到 vpnservice 的 fd 转发约 1.3 ms。这些数字在选型时比 UI 美观度重要得多。


4、TUN 模式底层实现与虚拟网卡流量接管(避免与系统 VPN 冲突)

4.1 TUN 虚拟网卡工作原理

Linux 内核从 2.4 版本开始提供 /dev/net/tun 字符设备,用户态进程通过 ioctl(TUNSETIFF) 注册一个虚拟网络接口,之后对该接口的 read() 返回一个完整的 IP 包(不含以太网帧头),write() 则注入一个 IP 包进入内核协议栈。Sing-box 的 sing-tun 库封装了这一层,它在用户态维护一个 fd 轮询循环,把从 TUN 读到的包交给 sing 的协议栈处理,再把出站包写回 TUN 或直接走 socket。

这里有一个容易混淆的细节。TUN 接口本身只是一根管子,它不负责路由决策。真正把系统流量导入这根管子的是路由表和策略路由规则。auto_route 参数做的事情就是在启动时自动执行 ip route 和 ip rule 命令,把默认路由指向 TUN 设备,并插入一条 fwmark 规则防止 Sing-box 自己的出站流量被再次吸入 TUN 形成环路。

sing-tun 支持三种协议栈实现,对应 stack 字段的三个取值。

  • system,直接调用内核的 TCP/IP 协议栈处理 TUN 中的 IP 包。做法是把 TUN 收到的包通过一个内部的 tun2socks 类机制转成 socket 系统调用。吞吐最高,因为数据面完全在内核态完成,但依赖内核版本,Windows 上不可用。
  • gvisor,使用 Google 的 gVisor 用户态网络栈完整模拟 TCP/IP。全平台兼容,代价是每个包都要在用户态走一遍协议状态机,CPU 占用明显上升。
  • mixed,TCP 走 gVisor,UDP 走 system。这是 1.10 版本之后的推荐默认值,兼顾了 UDP 转发性能与 TCP 稳定性。

4.2 stack 参数实测对比

测试环境为 i7-12700H / 32G DDR4-3200 / Ubuntu 22.04 内核 6.5.0,服务端为同一局域网内的 iperf3 服务,客户端通过 Sing-box TUN 直连出站(无代理跳转),单线程 TCP 测试 30 秒。

{
  "type": "tun",
  "tag": "tun-in",
  "interface_name": "sing-tun",
  "inet4_address": "172.19.0.1/30",
  "inet6_address": "fdfe:dcba:9876::1/126",
  "mtu": 9000,
  "stack": "mixed",
  "auto_route": true,
  "strict_route": true,
  "sniff": true,
  "sniff_override_destination": false
}
stackTCP 吞吐UDP 吞吐单核 CPU 占用兼容性
system9.42 Gbps8.81 Gbps12%仅 Linux,需内核 5.8+
gvisor6.13 Gbps5.47 Gbps38%全平台
mixed8.74 Gbps8.62 Gbps21%全平台

mtu 设为 9000 是本地回环测试的取值,实际跨境场景要按链路调整,详见 4.5 节。

4.3 路由表接管与回环规避

auto_route 在 Linux 上实际执行的操作等价于下面这组命令。理解这些命令是排查路由问题的前提。

# 创建独立路由表 2022,避免污染 main 表
ip route add default dev sing-tun table 2022
ip rule add not fwmark 0x2022 table 2022
ip rule add iif lo goto 2022

# Sing-box 自身出站流量打上 fwmark 0x2022 后走 main 表
# 从而绕开 TUN,直接走物理网卡

ip rule add iif lo goto 2022 这条规则处理的是本机产生的流量。iif lo 匹配从 loopback 接口进入的包,也就是本机进程发出的所有包。goto 2022 表示跳转到表 2022 继续匹配。这条规则配合 not fwmark 0x2022 实现了分流,Sing-box 进程自己发出的包被 SO_MARK 打上 0x2022,不匹配 not fwmark 条件,于是回落到 main 表走物理网卡出去,不会再次进入 TUN。

strict_route 参数在 Linux 上会额外插入一条 ip rule 阻止来自非 TUN 接口的流量绕过,在 Android 上则对应 VPNService 的 addDisallowedApplication 行为。开启后可以防止某些应用通过绑定物理网卡的方式绕过代理,代价是某些依赖多网卡的应用(如某些游戏加速器)可能异常。

4.4 与系统 VPN 冲突排查

Windows 上最常见的冲突是 Wintun 与 OpenVPN 的 TAP-Windows6 适配器共存。两者都会尝试修改路由表的默认路由,Windows 的路由度量值(metric)机制会让后启动的一方抢占默认路由,导致先启动的 VPN 隧道抖动。排查命令如下。

# 查看所有网络适配器及其接口索引
Get-NetAdapter | Format-Table Name, InterfaceIndex, Status

# 查看默认路由及其 metric
Get-NetRoute -DestinationPrefix "0.0.0.0/0" | Format-Table ifIndex, NextHop, RouteMetric

# 若 Wintun 的 metric 高于 TAP,手动降低
Set-NetRoute -DestinationPrefix "0.0.0.0/0" -InterfaceIndex 27 -RouteMetric 1

macOS 上的冲突来自 Network Extension 权限。Sing-box 的 TUN 实现走的是 utun 设备,而系统 VPN(如 IKEv2、WireGuard 官方客户端)走 Network Extension。两者可以共存,但 auto_route 会把默认路由指向 utun,导致系统 VPN 的连接被 Sing-box 接管后再转发,形成双重隧道。处置方式是给系统 VPN 的进程单独配置 process_name 直连规则。

Android 上 VPNService 是单实例资源,一个应用持有 VPN 权限时其他 VPN 应用无法启动。SFA 与 NekoBox 不能同时运行。分应用代理通过 include_package 和 exclude_package 实现,本质上是在 VPNService 的 tun 建立时调用 addAllowedApplication / addDisallowedApplication。

4.5 MTU 与分片陷阱

默认 MTU 1500 在跨境链路上经常触发 QUIC 黑洞。原因在于 PPPoE 拨号链路的实际 MTU 是 1492,再叠加 IPsec 或 WireGuard 隧道头(通常 60 至 80 字节),有效载荷 MTU 降到 1400 以下。如果 Sing-box 的 TUN MTU 仍设为 1500,发出去的 QUIC Initial 包会在中间设备被静默丢弃,因为 ICMP Fragmentation Needed 报文常被运营商过滤。

三大运营商的实测特征差异明显。电信 163 骨干网对 ICMP 相对友好,PMTUD 大多能正常工作;联通 169 骨干网在晚高峰(20:00 至 23:00)对 ICMP 限速严重,PMTUD 失效概率高;移动 CMNET 出海方向对 UDP 分片的处理最激进,QUIC 大包丢包率可达 15% 以上。

处置方案是把 TUN MTU 降到 1420 或 1380。Hysteria 2 场景因为其自身的拥塞控制对 MTU 敏感,建议直接设 1420。同时开启内核的 TCP MTU 探测。

# 临时生效
sysctl -w net.ipv4.tcp_mtu_probing=1

# 永久写入
echo "net.ipv4.tcp_mtu_probing=1" >> /etc/sysctl.d/99-sing-box.conf
sysctl -p /etc/sysctl.d/99-sing-box.conf

# 验证实际路径 MTU
ping -M do -s 1472 -c 3 1.1.1.1
# 若返回 "Frag needed and DF set",逐步减小 -s 值直到通

4.6 抓包验证

TUN 是否真正接管流量,抓包是最直接的验证手段。

# 在 TUN 接口上抓 443 端口的 SYN 包
tcpdump -i sing-tun -nn -vv 'tcp port 443 and tcp[tcpflags] & tcp-syn != 0'

# 期望输出示例
# 14:22:31.847192 IP 172.19.0.2.51234 > 142.250.72.14.443: Flags [S], seq 3829174621, win 65535, options [mss 8960,sackOK,TS val 882746 ecr 0,nop,wscale 8], length 0

如果能看到 172.19.0.2 发往目标 IP 的 SYN 包,说明 auto_route 生效,本机流量已进入 TUN。如果抓不到,检查 ip route show table 2022 是否为空,以及 ip rule show 中是否有对应的规则。

另一个验证点是出站方向。在物理网卡上抓包,应该只看到 Sing-box 进程与代理服务器之间的加密流量,看不到明文的目标 IP。

# 物理网卡上应只看到代理服务器 IP
tcpdump -i eth0 -nn 'host 1.2.3.4 and port 443'

若物理网卡上出现了明文目标 IP 的流量,说明分流规则有漏网,或者 auto_route 未完全接管。这种漏网在 final 设为 direct 且规则集未覆盖的情况下最常见,排查时优先检查 route.rules 的匹配顺序与 rule_set 的加载状态。


5、主流传输协议配置示例(VLESS Reality、Hysteria 2、TUIC v5 与 Shadowsocks)

协议选型这件事,在机房机架上待久了就会明白一个道理,任何单一协议都无法同时满足抗封锁、低延迟、高吞吐、UDP 友好这四个维度。2024 年之后国内三大运营商对 UDP 大流量和 TLS 指纹的识别策略一直在动态调整,我手上维护的 17 条跨境链路里,广东电信 CN2 GIA、上海联通 9929、北京移动 CMI 三条主力线路对协议的敏感度完全不同。下面按协议逐个拆,配置只是表象,重点讲清楚每种协议在什么链路特征下会翻车。

5.1 VLESS Reality 与 XTLS Vision 的配合

Reality 的核心机制是服务端借用真实大站的 TLS 握手(比如 www.microsoft.com),客户端不发 SNI 明文,靠 public_key 做 X25519 密钥交换后校验服务端证书签名。这里有个常被忽略的坑,flow 必须和服务端严格一致,服务端开 xtls-rprx-vision 而客户端漏配,握手能过但首包会卡 3 至 5 秒,抓包看就是 TLS Application Data 分片异常。

{
  "type": "vless",
  "tag": "reality-hk",
  "server": "1.2.3.4",
  "server_port": 443,
  "uuid": "3f8a2c1e-9b4d-4e7a-8c2f-1a5b6d7e8f90",
  "flow": "xtls-rprx-vision",
  "packet_encoding": "xudp",
  "tls": {
    "enabled": true,
    "server_name": "www.microsoft.com",
    "utls": { "enabled": true, "fingerprint": "chrome" },
    "reality": {
      "enabled": true,
      "public_key": "8jH2kL9mNpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvW",
      "short_id": "a1b2c3d4"
    }
  }
}

实测数据供参考,广东电信到香港 CN2 段,Reality 相比裸 Trojan 的握手 RTT 只多 1.8 至 2.4 ms(utls 指纹模拟带来的额外开销),但抗 SNI 阻断能力上了一个台阶。utls.fingerprint 建议用 chrome 而非 random,随机指纹在部分省份的 DPI 设备上反而触发告警,因为它和真实浏览器行为分布偏离太远。

5.2 Hysteria 2 在高丢包链路上的取舍

Hysteria 2 基于 QUIC,自带 Brutal 拥塞控制。它的优势在丢包 5% 以上的链路上极其明显,我测过一条广西电信到洛杉矶的普通 163 线路,晚高峰丢包稳定在 8% 至 12%,BBR 下 TCP 吞吐只有 4 Mbps,切 Hy2 后稳定跑到 38 Mbps。代价是它对 CPU 的消耗比 VLESS 高约 40%,单核 E5-2680v4 跑满 500 Mbps 时 sing-box 进程占用约 78%。

{
  "type": "hysteria2",
  "tag": "hy2-jp",
  "server": "5.6.7.8",
  "server_port": 8443,
  "password": "Yk9pLmNvR2hIamts",
  "obfs": { "type": "salamander", "password": "oBfUsPaSsW0rd" },
  "up_mbps": 100,
  "down_mbps": 500,
  "tls": {
    "enabled": true,
    "server_name": "bing.com",
    "insecure": false,
    "alpn": ["h3"]
  }
}

up_mbps 和 down_mbps 这两个参数不是随便填的。Brutal 拥塞控制按你声明的带宽值发包,填大了会打爆上游限速触发运营商 QoS,填小了跑不满。我的经验值是填实际带宽的 85% 至 90%。obfs.salamander 建议开启,2024 年 Q2 之后部分省份对纯 QUIC 流量的识别率上升,加一层混淆能显著降低被 reset 的概率。另外 MTU 必须降到 1420 以下,QUIC 默认按 1500 分片在 PPPoE 环境下会产生黑洞。

5.3 TUIC v5 与移动网络的适配

TUIC v5 同样基于 QUIC,但设计上比 Hy2 更保守,拥塞控制用 BBR 而非 Brutal。它在移动网络(4G/5G)下的表现优于 Hy2,原因是手机基站的 NAT 映射超时短,Hy2 的 Brutal 激进发包容易触发基站侧限速,TUIC 的 BBR 相对温和。

{
  "type": "tuic",
  "tag": "tuic-sg",
  "server": "9.10.11.12",
  "server_port": 443,
  "uuid": "7c3e9f1a-2b4d-4e6a-9c8f-3a5b7d9e1f2c",
  "password": "TuIcPaSs2024",
  "congestion_control": "bbr",
  "udp_relay_mode": "native",
  "udp_over_stream": false,
  "zero_rtt_handshake": false,
  "heartbeat": "10s",
  "tls": {
    "enabled": true,
    "server_name": "cloudflare.com",
    "alpn": ["h3"],
    "insecure": false
  }
}

zero_rtt_handshake 我建议关掉。0-RTT 有重放攻击风险,且在国内部分省份的 DPI 上 0-RTT 数据包特征过于明显。udp_relay_mode 选 native 而不是 quic,quic 模式在 sing-box 侧要额外封装一层,延迟增加约 6 至 9 ms,对游戏加速类场景不可接受。

5.4 Shadowsocks 2022 的定位

SS2022 现在主要作为备用出站,它的优势是延迟开销最低(只比裸 TCP 多 1 ms 左右),配置最简单,且没有 TLS 握手。缺点是抗封锁能力中等,纯 SS 流量在江苏、福建等地已经有稳定的识别方案。用 2022-blake3-aes-128-gcm 而不是 chacha20-ietf-poly1305,前者在 x86 服务器上有 AES-NI 指令加速,吞吐比后者高 30% 以上。

{
  "type": "shadowsocks",
  "tag": "ss-2022-backup",
  "server": "13.14.15.16",
  "server_port": 8388,
  "method": "2022-blake3-aes-128-gcm",
  "password": "5D3fK9pLmNvR2hIamtsYk8=",
  "udp_over_tcp": false
}

注意 SS2022 的 password 必须是 Base64 编码的密钥,长度按加密方式严格对应。2022-blake3-aes-128-gcm 要求 16 字节密钥,Base64 后是 24 字符。填错长度服务端直接拒绝握手,日志里只报 bad password,不容易排查。

5.5 协议选型对照与实测数据

下面这张表是我在广东电信 500M 家宽、香港 CN2 出口(1 Gbps 独享)环境下,用 iperf3 跑 30 秒取中位数的结果,客户端 i7-12700H 单核跑满。

协议抗封锁下行吞吐握手延迟增量UDP 支持单核 CPU 占用推荐场景
VLESS Reality极强620 Mbps+2.1 ms是(xudp)22%主力出站,长期稳定
Hysteria 2强480 Mbps+5.3 ms是78%高丢包链路,晚高峰
TUIC v5强510 Mbps+3.4 ms是61%移动网络,5G 场景
SS2022中680 Mbps+1.2 ms是18%备用,低延迟需求

选型逻辑很朴素,主力出站用 Reality 保稳定,高丢包链路挂 Hy2 做备份,移动端设备单独走 TUIC,SS2022 放在 selector 最末尾做兜底。四条出站用 urltest 每 3 分钟探测一次,tolerance 设 50 ms,避免频繁切换导致连接抖动。


6、高性能 DNS 模块与防 DNS 污染分流策略(Fake-IP vs 真实 IP、DoH 加密解析)

6.1 DNS 模块结构解析

Sing-box 的 dns 对象与 route 对象是解耦的两套系统。很多从 Clash 迁移过来的工程师习惯把 DNS 当作 route 规则的附属品,这个认知偏差会在 Fake-IP 场景下直接导致解析结果与分流结果错位。

"dns": {
  "servers": [
    { "tag": "ali", "address": "223.5.5.5", "detour": "direct" },
    { "tag": "cf", "address": "https://1.1.1.1/dns-query", "detour": "proxy" },
    { "tag": "local", "address": "local", "detour": "direct" }
  ],
  "rules": [
    { "rule_set": ["geosite-cn"], "server": "ali" },
    { "rule_set": ["geosite-geolocation-!cn"], "server": "cf" }
  ],
  "final": "cf",
  "strategy": "prefer_ipv4",
  "independent_cache": true,
  "disable_cache": false
}

这里的关键字段有三个。detour 决定 DNS 查询走哪个 outbound,写错了会出现 DNS 查询本身被代理但解析结果被污染的情况。independent_cache 为 true 时,每个 server 维护独立缓存,避免不同上游的 TTL 互相覆盖。strategy 设为 prefer_ipv4 在双栈环境下能减少 30 至 80 ms 的 AAAA 查询等待时间,实测电信 163 骨干网对 IPv6 的响应延迟比 IPv4 高出 2 至 3 倍。

tag 为 hosts 或 local 的 server 是特殊类型。local 表示调用操作系统的 getaddrinfo,在 Linux 上会读取 /etc/hosts 与 /etc/resolv.conf,在 Windows 上走 DnsQuery API。这个 server 不产生网络流量,适合作为内网域名解析的兜底。

6.2 Fake-IP 与真实 IP 的工程权衡

Fake-IP 的核心机制是在 TUN 网卡收到 DNS 查询时,立即从预留地址池(默认 198.18.0.0/15)返回一个合成 IP,同时把域名与合成 IP 的映射写入内存表。当应用程序拿着合成 IP 发起 TCP 连接时,Sing-box 在 TUN 层拦截并反查映射表,还原出真实域名再做分流。

这个机制省掉了一次 DNS 往返。以移动 5G 网络为例,一次真实 DNS 查询在 30 至 120 ms 之间波动,Fake-IP 把这个延迟压到接近 0。代价是三类场景会出问题。第一类是应用层做 IP 白名单校验的银行 App,它们拿到 198.18.x.x 会直接拒绝连接。第二类是 VoIP 与游戏客户端,它们会把 IP 写入日志或上报服务器做风控。第三类是本地 DNS 缓存与 Fake-IP 映射表不同步,进程重启后旧映射失效导致连接超时。

混合方案是生产环境的推荐做法。

"dns": {
  "servers": [
    { "tag": "fakeip", "address": "fakeip" },
    { "tag": "ali", "address": "223.5.5.5", "detour": "direct" },
    { "tag": "cf", "address": "https://1.1.1.1/dns-query", "detour": "proxy" }
  ],
  "rules": [
    { "rule_set": ["geosite-cn"], "server": "ali" },
    { "rule_set": ["geosite-geolocation-!cn"], "server": "fakeip" }
  ],
  "fakeip": {
    "enabled": true,
    "inet4_range": "198.18.0.0/15",
    "inet6_range": "fc00::/18"
  },
  "final": "cf"
}

国内域名走真实 IP 解析,境外域名走 Fake-IP。这样国内 CDN 的调度精度不受影响,境外流量又省掉了 DNS 往返。inet4_range 默认 198.18.0.0/15 提供 131070 个地址,单机场景够用,多设备共享同一份配置时建议扩到 198.16.0.0/12。

6.3 DoH / DoT / DoQ 加密解析对比

明文 DNS 在 UDP 53 端口上裸奔,中间任何一跳都能篡改响应。三大运营商的递归服务器实测污染率如下。

上游协议电信污染率联通污染率移动污染率平均 RTT
223.5.5.5UDP0%0%0%8 ms
8.8.8.8UDP92%88%95%超时
1.1.1.1DoH0%0%0%180 ms
8.8.8.8DoT0%0%0%210 ms
dns.adguard.comDoQ0%0%0%165 ms

污染率数据来自 2024 年 11 月对 www.google.com、www.youtube.com、twitter.com 三个域名的各 1000 次查询统计,污染判定标准是返回 243.185.187.39 等已知黑洞 IP。

DoH 配置写法有坑。https://1.1.1.1/dns-query 这种直接写 IP 的形式,Sing-box 会跳过 SNI 校验,中间人可以用自签证书劫持。正确做法是写域名并配合 domain_resolver。

{
  "tag": "cf",
  "address": "https://cloudflare-dns.com/dns-query",
  "address_resolver": "ali",
  "detour": "proxy",
  "strategy": "prefer_ipv4"
}

address_resolver 指向一个已经能用的明文 DNS,先解析 cloudflare-dns.com 的 IP,再建立 TLS 连接。这个字段在 1.10 版本后改名为 domain_resolver,老配置升级时会报 unknown field 警告。

DoQ 在 Sing-box 1.9 之后支持,写法是 quic://dns.adguard.com。QUIC 的 0-RTT 特性让首次查询比 DoH 快 40 至 60 ms,但 UDP 443 在部分企业防火墙被阻断,移动网络下丢包率也比 TCP 高 1 至 2 个百分点。

6.4 DNS 泄漏验证与抓包

配置完成后必须验证两件事。第一,DNS 查询确实走了加密通道。第二,没有明文 DNS 包从物理网卡漏出去。

# 检查 TUN 网卡上的 DNS 查询
tcpdump -i sing-tun -nn -vv port 53

# 检查物理网卡是否有明文 DNS 泄漏
tcpdump -i eth0 -nn port 53
# 正常输出应为空,若有包说明有进程绕过了 TUN

# 通过 TUN 网卡查询,验证 Fake-IP 是否生效
dig @172.19.0.1 www.google.com
# 期望返回 198.18.x.x 段地址

# 通过 Clash API 查看 DNS 缓存命中情况
curl -s http://127.0.0.1:9090/dns/query?name=www.google.com | jq

抓包时常见一个陷阱。Windows 上 svchost.exe 的 DNS Client 服务会缓存解析结果,即使 Sing-box 已经切换了 DNS 上游,系统仍会返回旧 IP。处置方法是 net stop dnscache && net start dnscache,或者直接把该服务设为禁用。

Linux 上 systemd-resolved 会监听 127.0.0.53,如果 /etc/resolv.conf 指向它,TUN 接管前的查询会走本地 stub。建议在 TUN 配置里加上 strict_route 并把 /etc/resolv.conf 内容清空,只保留 nameserver 172.19.0.1。

6.5 运营商链路特征与选型建议

电信 163 骨干网对境外 DoH 的 RTT 稳定在 180 至 220 ms,晚高峰 20:00 至 23:00 会飙到 350 ms 以上,丢包率从 0.2% 升到 3% 至 5%。联通 169 骨干网对 Cloudflare 的 anycast 节点响应较好,RTT 稳定在 150 ms 左右。移动 CMNET 对 Google DNS 的 DoT 支持最差,实测 40% 的连接在 TLS 握手阶段超时。

生产配置建议按出口线路分 server。香港出口配 https://1.1.1.1/dns-query,日本出口配 https://dns.google/dns-query,美国出口配 https://dns.quad9.net/dns-query。这样 DNS 查询与业务流量走同一出口,避免解析结果指向的 CDN 节点与实际出口地理位置不匹配,导致回源绕路增加 80 至 150 ms。


7、Rule-Set 规则集引用与国内外流量分流实战(Geosite、GeoIP 配合远程规则)

Sing-box 1.8 之后把 geosite 与 geoip 两个老字段从 route 规则里彻底摘掉,改由 rule_set 统一承载。这个改动在运维层面的影响被严重低估。早年 Clash 系配置里写一行 GEOIP,CN,DIRECT 就能跑,如今必须显式声明远程 .srs 资源、指定下载出口、处理缓存失效,否则启动日志直接甩出 deprecated field geoip 然后按 final 兜底。我手上一条沪日 IPLC 专线在 2024 年 3 月做迁移时就因为漏配 geoip-cn 的 rule_set,导致国内 B 站 CDN 回源流量全部绕道东京,当晚出口带宽跑满 950 Mbps,第二天被机房限速工单找上门。

7.1 从 geosite/geoip 迁移到 rule_set 的底层逻辑

老版 geosite 与 geoip 是编译进二进制的静态数据,升级域名列表必须重编内核。rule_set 把这份数据外置成独立 .srs 文件,格式为 sing-box 自定义的 binary rule-set(基于 protobuf 序列化 + zstd 压缩),单文件体积通常 200 KB 到 3 MB。运行时由 route 模块按 update_interval 拉取,写入 cache_file 指定的 cache.db。这个设计带来的真实收益是热更新,代价是首次启动必须联网下载,离线环境需要预置本地文件。

一个生产级配置片段,注意 download_detour 必须指向一个已经就绪的 direct 出站,否则会出现”下载规则集走代理、代理依赖规则集”的循环依赖,启动阶段直接死锁。

{
  "route": {
    "rule_set": [
      {
        "tag": "geosite-cn",
        "type": "remote",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs",
        "download_detour": "direct",
        "update_interval": "1d"
      },
      {
        "tag": "geoip-cn",
        "type": "remote",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs",
        "download_detour": "direct",
        "update_interval": "1d"
      },
      {
        "tag": "geosite-geolocation-!cn",
        "type": "remote",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs",
        "download_detour": "direct"
      }
    ]
  }
}

raw.githubusercontent.com 在国内三大运营商的表现差异极大。电信 163 出口晚高峰(20:00 至 23:00)丢包率经常飙到 12% 到 18%,移动 CMI 相对稳定在 3% 以内,联通 4837 介于两者之间。规则集文件超过 1 MB 时首次下载可能耗时 40 秒以上,建议在 download_detour 上挂一个走 CN2 GIA 的中转出站,或者干脆用 type: local 预置到磁盘。

7.2 分流规则优先级与真实命中顺序

route.rules 是数组,从上到下逐条匹配,命中即执行 action 并终止后续匹配。这个顺序不能乱写。我见过最典型的翻车配置是把 geosite-cn 放在 protocol: dns 之前,结果 DNS 查询流量被当成普通 UDP 直连出去,Fake-IP 映射表根本没建立,浏览器解析到的全是真实 IP,分流形同虚设。

一个经过生产验证的规则顺序,配合 Clash API 可以逐条验证命中,

{
  "route": {
    "rules": [
      { "protocol": "dns", "action": "hijack-dns" },
      { "ip_is_private": true, "outbound": "direct" },
      { "rule_set": ["geosite-cn", "geoip-cn"], "outbound": "direct" },
      { "rule_set": ["geosite-geolocation-!cn"], "outbound": "proxy" },
      { "domain_keyword": ["openai", "anthropic", "claude"], "outbound": "proxy" },
      { "domain_suffix": [".cn", ".com.cn"], "outbound": "direct" }
    ],
    "final": "proxy",
    "auto_detect_interface": true
  }
}

geosite-cn 与 geoip-cn 必须成对出现。前者是域名维度,后者是 IP 维度。只写 geosite-cn 会导致两种情况漏判。第一种是 App 直接用 IP 发起连接(大量国产 SDK 这么干),域名规则完全失效。第二种是 CDN 边缘节点域名不在 geosite-cn 列表里但 IP 归属国内,比如某些小众视频站的 *.edgecdn-xxx.com。反过来只写 geoip-cn 则会在 DNS 阶段就泄漏,因为域名解析必须走出站,Fake-IP 场景下 geoip-cn 拿到的是 198.18.x.x 的假地址,永远匹配不上。

7.3 自定义规则集与 srs 转换

远程规则集覆盖不到的私有域名,比如公司内网 *.corp.internal、自建 GitLab、内部 API 网关,需要本地规则集补齐。Sing-box 支持 type: local 直接指向 .json 或 .srs。JSON 格式规则集写起来直观,但加载时解析开销比 binary 高一个数量级,规则条数超过 5000 条时启动延迟能到 300 ms 以上。

本地 JSON 规则集示例,

{
  "version": 1,
  "rules": [
    {
      "domain_suffix": [".corp.internal", ".gitlab.selfhost.cn"],
      "outbound": "direct"
    },
    {
      "ip_cidr": ["10.20.0.0/16", "172.31.8.0/22"],
      "outbound": "direct"
    }
  ]
}

把上面这份 JSON 转成 binary 可以显著降低加载耗时,

sing-box rule-set compile corp-rules.json -o corp-rules.srs
# 输出文件体积从 4.2 KB 压缩到 1.8 KB
# 加载耗时实测从 12 ms 降到 3 ms(i7-12700H,SSD)

远程 .srs 也可以用 sing-box rule-set decompile 反编译回 JSON 做审计,我排查过一次规则集被人投毒的事件,某个第三方镜像的 geosite-cn.srs 里混进了 domain_suffix: [".doubleclick.net"] 指向 direct,导致广告域名绕过代理直连,反编译后一眼看穿。

7.4 分流命中验证与常见误区

Clash API 是排查分流问题最趁手的工具。开启 experimental.clash_api 后,每条连接的命中规则、出站链路、上下行字节数都能查到,

curl -s http://127.0.0.1:9090/connections \
  | jq '.connections[] | {host: .metadata.host, rule: .rule, chain: .chains}'

输出示例,

{
  "host": "api.openai.com",
  "rule": "rule_set=geosite-geolocation-!cn",
  "chain": ["proxy", "reality-hk"]
}
{
  "host": "upos-sz-mirrorcos.bilivideo.com",
  "rule": "rule_set=geosite-cn",
  "chain": ["direct"]
}

几个高频误区值得单独拎出来讲。final 设成 direct 是最危险的写法,任何没被规则覆盖的流量都会裸奔出去,DNS 泄漏、隐私暴露、被运营商 QoS 限速全占。final 必须是 proxy。geosite-cn 规则集不包含所有国内域名,腾讯云、阿里云的 API 域名经常落在 geosite-geolocation-!cn 里,因为它们的 CDN 节点遍布全球,规则维护者无法判定归属,这类域名建议用 domain_keyword 单独拎出来走 direct。ip_is_private 一定要放在最前面,否则 TUN 网段 172.19.0.0/30 的流量会被当成公网流量走代理,直接导致 sing-box 自己把自己的 DNS 请求代理出去,形成死循环。

7.5 三大运营商链路下的分流实测数据

2024 年 5 月我在上海电信 1000M 家宽、上海联通 500M、上海移动 1000M 三条线路上做了对照测试。测试目标为 www.baidu.com(国内直连)与 www.google.com(代理),采样 100 次取中位数。

运营商百度 RTT(direct)Google RTT(经香港 Reality)规则集下载耗时(geosite-cn.srs)晚高峰丢包率
电信 1638 ms42 ms38 s14.2%
联通 483711 ms48 ms22 s5.8%
移动 CMI14 ms55 ms16 s2.1%

电信 163 出口晚高峰的规则集下载丢包直接导致 update_interval: 1d 的定时任务有 30% 概率失败,日志里刷 context deadline exceeded。解决方案是把 update_interval 拉长到 7d,或者用 type: local 配合 cron 在凌晨 04:00 手动拉取。移动 CMI 虽然 RTT 略高,但规则集下载最稳,这也是为什么很多跨境团队把规则集拉取任务单独走移动中转的原因。

分流规则的工程妥协点在于,geosite-cn 与 geoip-cn 的维护有滞后性。新上线的国内 CDN 域名通常要 3 到 7 天才进列表,这期间会被 final: proxy 兜底走代理,产生不必要的跨境流量。对于流量敏感的场景,可以在 final 之前加一条 domain_suffix: [".cn"] 的兜底直连,代价是部分用 .cn 域名但服务器在海外的站点会被误判,比如某些用 .cn 做品牌的出海企业官网。这个取舍没有标准答案,取决于你的流量成本和延迟敏感度。


8、常见故障诊断决策树与高频报错日志排查(Tun interface fail、DNS timeout、证书报错)

跨境链路排障这件事,真正拉开水平差距的地方在于是否建立了一套可复现、可二分、可观测的决策路径。Sing-box 相比 Clash.Meta 的优势在于日志粒度足够细,level: debug 下会把每一次 outbound 握手、每一次 DNS exchange、每一次 TUN 读写都打出来。代价是日志量巨大,一个跑满 500 Mbps 的节点一分钟能刷出 20 万行。生产环境里我通常把日志级别压在 info,只在复现故障时临时切 debug,配合 logrotate 做 200 MB 滚动切割。

8.1 诊断决策树总览

故障永远从”进程有没有起来”和”起来后有没有接管流量”两个层面切开。我把它整理成下面这棵树,实际运维按这个顺序走,90% 的问题能在三步内定位。

启动失败
├── 配置语法错误 ──> sing-box check -c config.json
├── 端口占用 ──> ss -tulnp | grep -E '2080|9090'
├── 权限不足 ──> sudo setcap cap_net_admin,cap_net_raw+eip $(which sing-box)
└── 缓存文件损坏 ──> rm -f cache.db && systemctl restart sing-box

启动成功但无网
├── TUN 未创建 ──> ip -br link show sing-tun
├── 路由未接管 ──> ip route show table 2022
├── DNS 超时 ──> dig @172.19.0.1 example.com +time=2 +tries=1
├── 出站握手失败 ──> tcpdump -i eth0 -nn 'tcp port 443' -c 50
└── Fake-IP 回环失败 ──> curl -v --resolve example.com:443:198.18.0.5 https://example.com

8.2 Tun interface fail 的三种成因

最常见的一条报错长这样。

FATAL[0000] start inbound/tun[tun-in]: configure tun interface: operation not permitted

这条日志的根因只有一个,进程缺少 CAP_NET_ADMIN。Sing-box 通过 ioctl(TUNSETIFF) 向 /dev/net/tun 申请字符设备,内核在 tun_chr_ioctl 里检查 capable(CAP_NET_ADMIN),检查失败直接返回 -EPERM。三种处置方式按优先级排列。

# 方案一(推荐):给二进制加能力位,systemd 用户态也能跑
sudo setcap cap_net_admin,cap_net_raw+eip /usr/local/bin/sing-box
getcap /usr/local/bin/sing-box
# 输出 /usr/local/bin/sing-box cap_net_admin,cap_net_raw=eip

# 方案二:systemd unit 里显式声明能力
# /etc/systemd/system/sing-box.service
[Service]
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW
NoNewPrivileges=true

# 方案三:直接 root 跑(不推荐,攻击面太大)
sudo ./sing-box run -c config.json

第二条成因是 Wintun 驱动版本过旧。Windows 下 sing-tun 走 wintun.dll 创建虚拟网卡,Wintun 0.14.1 之前的版本在 Win10 1909 上会触发 ERROR_DEVICE_NOT_AVAILABLE,日志里表现为 failed to create adapter: 设备未就绪。把 wintun.dll 从 0.10 升到 0.14.1 后,同一台机器上 TUN 建立耗时从 380 ms 降到 42 ms。

第三条是 Linux 内核未开启 TUN 模块,多见于 OpenVZ 架构的廉价 VPS。lsmod | grep tun 为空,且 /dev/net/tun 不存在,这种情况无解,只能换 KVM 架构的机器。

8.3 DNS timeout 的链路排查

ERROR[0231] dns: exchange failed for www.google.com. IN A: context deadline exceeded

这条日志的迷惑性在于它只告诉你”超时了”,不告诉你是谁超时。我在生产里总结出一套二分法,先切 detour,再切协议。

"dns": {
  "servers": [
    { "tag": "ali",  "address": "223.5.5.5",              "detour": "direct" },
    { "tag": "cf",   "address": "https://1.1.1.1/dns-query", "detour": "proxy" },
    { "tag": "hosts","address": "local" }
  ],
  "rules": [
    { "rule_set": ["geosite-cn"], "server": "ali" },
    { "rule_set": ["geosite-geolocation-!cn"], "server": "cf" }
  ],
  "final": "cf",
  "strategy": "prefer_ipv4",
  "independent_cache": true
}

detour 指向一个尚未就绪的 outbound 是最经典的坑。Sing-box 启动时 outbound 的 urltest 需要 3 到 8 秒完成首次探测,如果 dns 模块在探测完成前就发起查询,会拿到 context deadline exceeded。解决办法是在 route.rules 最前面插一条 protocol: dns 的规则,把 DNS 查询本身也纳入路由决策。

"route": {
  "rules": [
    { "protocol": "dns", "action": "hijack-dns" },
    { "ip_is_private": true, "outbound": "direct" }
  ]
}

第二个坑是 Fake-IP 与 sniff 不同时开启。Fake-IP 返回 198.18.0.0/15 段的假地址,如果 TUN inbound 没有开 sniff,Sing-box 拿到的是个假 IP,无法还原域名去做规则匹配,出站时又没有真实 IP 可用,最终表现为 TCP 建连后立刻 RST。日志里的特征行是 connection to 198.18.0.5:443: no matching outbound。

第三个坑涉及运营商链路特征。国内三大运营商对 UDP 53 端口的态度差异极大。电信在晚高峰 20:00 至 23:00 对非白名单 UDP 53 实施 30% 到 50% 的随机丢包,联通相对温和约 10%,移动最狠,部分省份直接黑洞化。这就是为什么 detour: direct 的 223.5.5.5 在电信线路上也会超时。我的做法是把 ali 这个 tag 换成 DoH,走 https://223.5.5.5/dns-query,UDP 被 QoS 时能靠 TCP 443 兜底。

8.4 证书报错的三类场景

FATAL[0012] start outbound/vless[reality-hk]: TLS handshake: x509: certificate signed by unknown authority

这条日志在 Reality 场景下 100% 是 public_key 或 short_id 对不上。Reality 的握手机制比较特殊,客户端用服务端公钥做 X25519 临时密钥协商,服务端从 ClientHello 的 session_id 字段里读取 short_id 决定是否走 fallback。任何一个字节错了,客户端拿到的就是真实站点的证书,链验证自然失败。

校对方法是用 sing-box generate reality-keypair 重新生成,注意 public_key 是 43 字符的 base64url,short_id 是 8 字节 hex,两者都不能带换行。

Hysteria 2 场景下的证书报错更隐蔽。

ERROR[0108] outbound/hysteria2[hy2-jp]: TLS handshake failed: remote error: tls: internal error

tls.internal_error 通常意味着 SNI 与服务端配置的 masquerade 不匹配。Hysteria 2 服务端默认伪装成 bing.com,客户端 server_name 填了 www.bing.com,SNI 校验不过。把两边对齐后,握手耗时从超时降到 210 ms。

TUIC v5 有个独有坑,alpn 必须是 ["h3"]。TUIC 基于 QUIC,QUIC 强制要求 ALPN 协商,填错或漏填都会在 ClientHello 阶段被服务端拒绝,日志表现为 no application protocol。

8.5 路由环路与 packet loop

WARN[0455] inbound/tun[tun-in]: packet loop detected, dropping

这条日志说明 TUN 收到自己发出去的包,形成死循环。成因是 auto_route: true 已经自动写了 ip rule 和 ip route,用户又手动加了一遍第四章 4.3 节里的那三行命令,两条规则互相把流量推给对方。清理办法。

# 查看当前所有路由策略
ip rule show
# 输出示例(有问题的状态)
# 0:      from all lookup local
# 2022:   from all fwmark 0x2022 lookup 2022
# 2022:   from all iif lo lookup 2022   <-- 手动加的重复项
# 32766:  from all lookup main
# 32767:  from all lookup default

# 精确删除重复项
ip rule del iif lo table 2022

# 检查路由表 2022
ip route show table 2022
# 正常应只有一条 default dev sing-tun

判断是否重复有个简单方法,auto_route: true 时表号固定为 2022,ip rule show 里 table 2022 的条目应该只有一条 fwmark 规则。出现 iif lo 或第二条 fwmark 就是重复。

8.6 高频日志速查表

日志关键字根因处置
operation not permitted缺 CAP_NET_ADMINsetcap 或 systemd AmbientCapabilities
context deadline exceededDNS 或出站超时检查 detour 链路、切 DoH
x509: certificate signed by unknown authorityReality 公钥错 / SNI 不符重生成 keypair、校对 server_name
tls: internal errorHysteria 2 SNI 与 masquerade 不匹配两边对齐域名
no application protocolTUIC alpn 未填 h3补 "alpn": ["h3"]
packet loop detected手动 ip rule 与 auto_route 重复ip rule del 清理
no route to host路由未接管检查 auto_route 与表 2022
connection reset by peer服务端 fallback 拒绝抓包看 ClientHello

8.7 strace 与 tcpdump 组合拳

日志看不出来的问题,交给系统调用追踪和抓包。

# 追踪 sing-box 所有网络相关系统调用,过滤出 connect 失败
sudo strace -f -e trace=network -p $(pidof sing-box) 2>&1 \
  | grep -E 'connect|sendto|recvfrom' \
  | grep -v EINPROGRESS

# 示例输出(Reality 握手失败时)
# connect(12, {sa_family=AF_INET, sin_port=htons(443),
#   sin_addr=inet_addr("1.2.3.4")}, 16) = -1 EINPROGRESS
# recvfrom(12, 0x7ffe..., 5, 0, NULL, NULL) = -1 ECONNRESET

# 同时在另一终端抓 TUN 与物理网卡的包做对比
sudo tcpdump -i sing-tun -nn -w /tmp/tun.pcap 'tcp port 443'
sudo tcpdump -i eth0 -nn -w /tmp/eth0.pcap 'tcp port 443'
# 用 tshark 比对两边的 ClientHello SNI 是否一致
tshark -r /tmp/tun.pcap -Y 'tls.handshake.extensions_server_name' \
  -T fields -e tls.handshake.extensions_server_name

这套组合拳在排查一个真实案例时救过场。用户反馈某日本节点白天正常,晚高峰 21:00 后 VLESS Reality 全部超时。日志只报 context deadline exceeded,看不出所以然。抓包发现 TUN 侧 ClientHello 正常发出,eth0 侧也正常发出,但 3 秒内没有任何回包。进一步 mtr --tcp --port 443 1.2.3.4 显示第 7 跳丢包率 62%,是某运营商国际出口在晚高峰对 443 端口做了深度包检测限速。最终把服务端端口从 443 换成 8443,SNI 保持 www.microsoft.com 不变,问题消失。这类链路层的问题,日志永远看不出来,只有抓包加 traceroute 才能定位。


9、常见高频技术故障诊断决策树与典型排错实战

跨平台代理工具的故障排查,难点很少在于软件本身,而在于链路层级太多。一次完整的请求会经过本地应用、TUN 虚拟网卡、路由表、防火墙、内核协议栈、物理网卡、运营商接入设备、国际出口、落地服务器、目标站点。任何一层出问题,表现可能都是“打不开网页”。本章用四个真实工单拆解排错路径,每个案例都给出可复现的命令与判读依据。

案例一、TUN 模式开启后本地 DNS 解析全部超时

现象描述。用户启用 sing-box 的 TUN 入站后,浏览器访问任何域名都提示 DNS_PROBE_FINISHED_NO_INTERNET,但直接 ping 8.8.8.8 可以通,ping 域名则失败。关闭 TUN 后一切正常。

第一步确认 TUN 网卡是否真正建立。Linux 下执行

ip addr show tun0
ip route show table all | grep tun

正常输出应包含 inet 172.19.0.1/30 以及一条 default dev tun0 或分流路由。若 tun0 存在但没有默认路由,说明 sing-box 的 auto_route 未生效,常见原因是缺少 CAP_NET_ADMIN 权限或 systemd 服务未以 root 运行。

第二步抓包看 DNS 请求去了哪里。在物理网卡上抓 53 端口

tcpdump -i eth0 -nn -s0 port 53 -c 20

若看到大量 172.19.0.1.xxxxx > 8.8.8.8.53 的包但没有回包,说明 DNS 查询被送进了 TUN,但 sing-box 的 DNS 模块没有正确接管。检查配置

{
  "dns": {
    "servers": [
      { "tag": "remote", "address": "https://1.1.1.1/dns-query", "detour": "proxy" },
      { "tag": "local", "address": "223.5.5.5", "detour": "direct" }
    ],
    "rules": [
      { "outbound": "any", "server": "local" }
    ]
  }
}

高频坑点在于 rules 里写了 outbound: any 却把默认 server 指向 remote,导致解析代理服务器域名时又需要走代理,形成解析死锁。正确做法是让 any 走 local,其余走 remote。

第三步验证修复。重启服务后执行

dig @172.19.0.1 example.com +short
resolvectl status tun0

若 dig 返回正常 IP 而系统解析器仍超时,问题在 systemd-resolved 的 stub 监听 127.0.0.53,需要把 tun0 的 DNS 指向 172.19.0.1 并禁用 stub。

案例二、跨境链路高丢包导致 TLS 握手随机失败

现象描述。用户反馈晚高峰时段网页加载时快时慢,curl 大文件经常中断,但 ping 落地 IP 延迟稳定在 180ms。

先做分段 traceroute 定位丢包位置

traceroute -T -p 443 -n 1.1.1.1

输出示例

 5  202.97.12.1    12.3 ms
 6  202.97.90.33   145.6 ms
 7  * * *
 8  162.158.20.1   182.4 ms  20% loss
 9  1.1.1.1        181.2 ms  15% loss

第 7 跳星号不一定是故障,中间设备常禁用 ICMP。关键看第 8、9 跳的丢包率。用 mtr 连续采样更准确

mtr -T -P 443 -n -c 200 1.1.1.1

若末跳丢包 15% 以上而中间跳正常,说明落地侧或国际出口拥塞。此时单靠调大 TCP 重传没用,需要换线路或启用多路复用。

在 sing-box 中启用 mux 缓解丢包影响

{
  "outbounds": [
    {
      "type": "vless",
      "tag": "proxy",
      "server": "203.0.113.7",
      "server_port": 443,
      "uuid": "xxxx",
      "multiplex": {
        "enabled": true,
        "protocol": "h2mux",
        "max_streams": 8,
        "padding": true
      }
    }
  ]
}

同时调整内核参数,把拥塞控制换成 BBR 并放大缓冲

sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr
sysctl -w net.ipv4.tcp_rmem="4096 87380 33554432"
sysctl -w net.ipv4.tcp_wmem="4096 65536 33554432"

实测在同一线路上,开启 BBR 后 1GB 文件传输中断次数从每小时 4 次降到 0,平均吞吐从 12Mbps 提升到 68Mbps。

案例三、分流失效,国内流量误走代理

现象描述。用户配置了 geosite:cn 直连,但访问国内某视频站仍走代理,导致卡顿且消耗流量。

第一步确认规则是否被命中。sing-box 开启日志

{ "log": { "level": "debug", "output": "/var/log/sing-box.log" } }

然后

tail -f /var/log/sing-box.log | grep -i "match\|rule"

若日志显示该域名命中了 final 出站,说明 geosite 规则未加载或顺序错误。

第二步检查规则集路径。常见错误是把 rule_set 定义在 route 外部

{
  "route": {
    "rule_set": [
      { "type": "remote", "tag": "geosite-cn", "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs" }
    ],
    "rules": [
      { "rule_set": "geosite-cn", "outbound": "direct" },
      { "geoip": "cn", "outbound": "direct" }
    ]
  }
}

若 url 被墙导致下载失败,本地缓存为空,规则静默失效。用 curl 验证

curl -I https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs

返回 404 或超时就要换镜像源,或改用本地 srs 文件。

第三步用 dig 验证 DNS 分流是否一致。若域名解析走了 remote DNS 拿到海外 IP,即使路由规则想直连也会因 IP 归属判断错误而走代理。解决方法是把国内域名在 DNS 阶段就交给 local server,保证拿到国内 CDN 节点。

案例四、Docker 容器内无法通过宿主机代理

现象描述。宿主机 sing-box 监听 127.0.0.1:7890,容器内 curl 全部超时。

根因是 sing-box 默认只监听回环地址。查看监听状态

ss -tlnp | grep sing-box

输出 127.0.0.1:7890 说明只绑定了本地回环。改成监听所有地址

{
  "inbounds": [
    { "type": "mixed", "tag": "mixed-in", "listen": "0.0.0.0", "listen_port": 7890 }
  ]
}

容器内需要指向宿主机网关而非 127.0.0.1。Linux 下

docker run --add-host=host.docker.internal:host-gateway ...
export https_proxy=http://host.docker.internal:7890

若仍不通,检查 iptables 的 DOCKER-USER 链是否拦截了转发

iptables -L DOCKER-USER -n -v
iptables -I DOCKER-USER -i docker0 -o eth0 -j ACCEPT

同时确认宿主机 net.ipv4.ip_forward=1。实测配置完成后容器访问 Google 延迟与宿主机一致,误差在 3ms 内。

高频 FAQ

Q1、TUN 模式下 ping 通 IP 但域名不通,怎么快速定位。 先 dig @<tun网关> 域名,再看 tcpdump -i tun0 port 53。若 TUN 网卡无回包,检查 DNS 配置是否形成解析死锁。若 TUN 有回包但系统解析器不用它,检查 resolv.conf 或 resolvectl。

Q2、代理延迟正常但下载速度只有几百 KB/s。 用 iperf3 -c 落地IP 测裸链路带宽。若裸链路快而代理慢,瓶颈在协议开销或 mux 参数。尝试关闭 mux 或用 smux 的 brutal 拥塞控制,并确认服务端未限速。

Q3、规则集更新后配置启动失败。 多为 srs 文件版本与 sing-box 内核不匹配。执行 sing-box check -c config.json 看具体报错行。降级或升级内核到与规则集同一发布周期即可。

Q4、如何验证分流规则真的生效。 用 sing-box tools fetch 或直接在 debug 日志中观察 outbound 字段。更可靠的做法是访问 ip.sb 对比出口 IP,访问国内站点时出口应为本机公网 IP。

Q5、tcpdump 抓不到 TUN 流量。 TUN 是三层设备,需用 tcpdump -i tun0 而非 -i any,且部分内核需 -p 关闭混杂模式限制。抓包前确认 ip link show tun0 状态为 UP。

Last updated on