在 Linux 旁路由上同时运行 UFW 和 sing-box TUN 时,可能出现一种容易误判的故障:sing-box 本机运行正常,但局域网设备通过旁路由访问互联网失败。即使放行了当时被拦截的 TCP 端口,重启 sing-box 后问题也可能再次出现。

本文记录一种实际观察到的原因和待回归验证的修复方式。示例均使用文档保留地址,不对应真实网络。

环境和症状

  • Linux 旁路由,LAN 接口为 eth0,地址 192.0.2.1(仅作示例)。
  • 下游终端 192.0.2.20,使用旁路由作为 IPv4 网关。
  • sing-box 的 TUN 接口名为 tun0,开启 auto_route 和 auto_redirect。
  • UFW 默认拒绝未经允许的入站流量。

sing-box 的关键配置:

{
  "type": "tun",
  "interface_name": "tun0",
  "auto_route": true,
  "auto_redirect": true
}

故障期间日志类似:

[UFW BLOCK] IN=eth0 OUT= SRC=192.0.2.20 DST=192.0.2.1 PROTO=TCP DPT=24461 SYN

DPT=24461 只是一次运行时观察到的动态端口,不应该把它永久写死在 UFW 里。OUT= 为空,提示包正在进入本机的 INPUT 路径,而不只是穿过 FORWARD 路径。

追踪 NAT 重定向

检查 sing-box 监听:

sudo ss -lntup | grep sing-box

检查 nftables:

sudo nft -a list ruleset | grep -n -C 8 -E 'redirect|tproxy'

故障场景中能看到类似规则:

meta l4proto tcp redirect to :24461
# 内部端口不允许直接连接
tcp dport 24461 ct status ! dnat reject with tcp reset

也就是说,sing-box 先将目标 TCP 连接重定向到本机监听端口,再由 UFW 的 INPUT 规则决定是否允许。将这一类流量当成普通 FORWARD 包去放行,通常无法消除观察到的 INPUT 拦截。

修复思路:匹配 DNAT 连接,而不是动态端口

对于明确可信的单个下游终端,可以临时尝试:

sudo iptables -I ufw-before-input 1 \
  -i eth0 \
  -s 192.0.2.20/32 \
  -d 192.0.2.1 \
  -p tcp \
  -m conntrack --ctstate DNAT \
  -j ACCEPT

注意把示例 IP 和接口替换为实际值。DNAT 属于 --ctstate 的有效虚拟状态;不是 --ctstatus,后者会报错 Bad ctstatus "DNAT"。

这条规则仍有安全边界:它匹配符合条件的 DNAT TCP 连接,而不仅是 sing-box 创建的连接。因此应该限制来源和入口接口,不要为了省事全局放行 INPUT。

验证

在下游设备运行:

curl -4 -v --connect-timeout 5 https://www.cloudflare.com/

在旁路由观察规则计数和拦截日志:

sudo iptables -L ufw-before-input -n -v --line-numbers
sudo journalctl -k --since '10 minutes ago' --grep='UFW BLOCK' --no-pager

如计数上升、联网恢复,并且 sing-box 重启、内部端口发生变化后仍正常,才可认为此方法适用于当前部署。

持久化

确认验证成功后,先备份:

sudo cp -a /etc/ufw/before.rules /etc/ufw/before.rules.bak
sudo nano /etc/ufw/before.rules

在 *filter 区域中,所有链定义之后、现有 ufw-before-input 处理规则之前添加:

-A ufw-before-input -i eth0 -s 192.0.2.20/32 -d 192.0.2.1 -p tcp -m conntrack --ctstate DNAT -j ACCEPT

检查并重载:

sudo iptables-restore --test < /etc/ufw/before.rules
sudo ufw reload

回滚时用备份恢复文件并 sudo ufw reload。临时测试规则也可以通过 iptables -D 携带与插入时完全一致的匹配参数删除。

注意事项

  1. 不要直接使用 ufw default allow routed 或关闭整个 UFW 作为长期方案。
  2. 此方案主要针对 IPv4 TCP 重定向后的 INPUT 拦截,不是对所有 UDP、IPv6 或 WireGuard 故障的通用修复。
  3. 其他明确配置的 API、DNS、SOCKS 等监听端口,应继续按实际用途限制访问来源。
  4. UFW 与 sing-box 都可能修改 nftables;如果规则匹配计数增加但连接仍失败,还应检查其他 base chain 的 hook 优先级及后续丢弃规则。

参考