Issue: nftables 规则缺少 IPv6 国内分流
问题现象
所有 IPv6 流量都被转发到代理,包括国内 CDN 节点。以 B 站为例:
# IPv4 — 正常(返回国内 IP)
curl -4 https://api.live.bilibili.com/ip_service/v1/ip_service/get_ip_addr
# → 180.139.x.x 中国
# IPv6 — 错误(返回代理 IP)
curl -6 https://api.live.bilibili.com/ip_service/v1/ip_service/get_ip_addr
# → 173.242.x.x 美国 洛杉矶 ← 国内流量泄露到代理
B 站域名 api.live.bilibili.com → CNAME → a.w.bilicdn1.com,DNS 同时返回 IPv4 和 IPv6 地址:
IPv4: 117.21.179.18 ~ 20, 222.210.39.57 ~ 59
IPv6: 240e:974:ecff:25::28 ~ 30, 240e:cf:9000:306::2 ~ 4, ...
均为中国 CDN 节点。但用户设备支持 IPv6 时浏览器优先走 IPv6,导致本应直连的国内流量走了代理。
根因分析
/usr/bin/ssr-rules 在创建 nftables 链时只生成 IPv4 规则,完全缺少 IPv6 国内分流的代码。
当前 ss_spec_wan_ac 链的创建逻辑(ssr-rules nft 部分,约 line 393-434):
$NFT flush chain inet ss_spec ss_spec_wan_ac
$NFT add rule ... tcp dport 53 ip daddr 127.0.0.0/8 return
$NFT add rule ... tcp dport != 53 ip daddr $server return
$NFT add rule ... ip daddr @blacklist jump ss_spec_wan_fw ← IPv4 only
$NFT add rule ... ip daddr @whitelist return ← IPv4 only
$NFT add rule ... ip saddr @fplan jump ss_spec_wan_fw ← IPv4 only
$NFT add rule ... ip saddr @bplan return ← IPv4 only
$NFT add rule ... ip daddr @china return ← IPv4 only
$NFT add rule ... jump ss_spec_wan_fw ← 兜底
注意所有规则都是 ip 开头(IPv4),没有任何 ip6 规则。IPv6 流量进入该链后,逐条不命中。
三种运行模式的差异
| 模式 |
链末尾 |
IPv6 行为 |
影响 |
| router(绕过大陆IP) |
jump ss_spec_wan_fw |
IPv6 所有规则不命中,落入兜底 jump → 代理 |
有问题 ✗ |
| gfw(GFW列表) |
无兜底 jump |
IPv6 所有规则不命中,链结束 → accept 直连 |
没问题 ✓ |
| all(全局) |
jump ss_spec_wan_fw |
预期全走代理 |
预期行为 ✓ |
因此该问题仅在 router 模式下出现。GFW 模式因为链末尾没有兜底 jump ss_spec_wan_fw,IPv6 流量"意外正确"地直连了。
注:GFW 模式下虽不影响 IPv6 直连,但 GFW 列表中的域名如果解析出 IPv6 地址,会因为 @gfwlist 只有 IPv4 条目而直连出去,这属于另一个潜在问题。
router 模式数据流对比:
| 协议 |
nftables 层 |
结果 |
| IPv4 |
ip daddr @china return → 命中,直连 |
国内 IP ✓ |
| IPv6 |
所有 ip 规则不命中 → jump ss_spec_wan_fw → 代理 |
代理 IP ✗ |
建议修复方案
新增 @china6 set(类型 ipv6_addr),在 ss_spec_wan_ac 链中添加 IPv6 国内直连规则:
1. 新增 nft set
nft add set inet ss_spec china6 { type ipv6_addr; flags interval; auto-merge; }
2. 加载中国 IPv6 CIDR 列表
数据来源:https://raw.githubusercontent.com/gaoyifan/china-operator-ip/ip-lists/china6.txt(约 1500 条,定期更新)
可参考现有的 chinaipset.sh 对 IPv4 @china 的加载方式。
3. 在链创建代码中添加 IPv6 规则
在 ip daddr @china return 之后、jump ss_spec_wan_fw 之前,加入:
$NFT add rule inet ss_spec ss_spec_wan_ac ip6 daddr @china6 return
该修改仅需在 router 模式的链生成代码中添加(gfw 模式可一并添加以显式声明 IPv6 直连,非必须)。
4. (可选)新增 CLI 参数
类似 -i 指定 IPv4 列表文件,可增加 -6 参数指定 IPv6 列表文件,如:
ssr-rules -6 /etc/ssrplus/china6_ssr.txt
当前临时方案
我们目前的 workaround 是通过 chinaipset6.sh 在 ssr-rules 执行完后手动插入规则并挂到 /etc/init.d/shadowsocksr 的 start() 钩子中。该方案存在问题:
- 固件升级会覆盖
/etc/init.d/shadowsocksr,钩子丢失
- 作为外部补丁,维护成本高,不适合普通用户
验证方法
修复后可通过以下命令验证:
# nft 链中应包含 ip6 规则
nft list chain inet ss_spec ss_spec_wan_ac | grep china6
# IPv6 请求应返回国内 IP
curl -6 https://api.live.bilibili.com/ip_service/v1/ip_service/get_ip_addr
# 应返回 240e:* 开头、归属中国
Issue: nftables 规则缺少 IPv6 国内分流
问题现象
所有 IPv6 流量都被转发到代理,包括国内 CDN 节点。以 B 站为例:
B 站域名
api.live.bilibili.com→ CNAME →a.w.bilicdn1.com,DNS 同时返回 IPv4 和 IPv6 地址:均为中国 CDN 节点。但用户设备支持 IPv6 时浏览器优先走 IPv6,导致本应直连的国内流量走了代理。
根因分析
/usr/bin/ssr-rules在创建 nftables 链时只生成 IPv4 规则,完全缺少 IPv6 国内分流的代码。当前
ss_spec_wan_ac链的创建逻辑(ssr-rulesnft 部分,约 line 393-434):注意所有规则都是
ip开头(IPv4),没有任何ip6规则。IPv6 流量进入该链后,逐条不命中。三种运行模式的差异
jump ss_spec_wan_fwjump ss_spec_wan_fw因此该问题仅在 router 模式下出现。GFW 模式因为链末尾没有兜底
jump ss_spec_wan_fw,IPv6 流量"意外正确"地直连了。router 模式数据流对比:
ip daddr @china return→ 命中,直连ip规则不命中 →jump ss_spec_wan_fw→ 代理建议修复方案
新增
@china6set(类型ipv6_addr),在ss_spec_wan_ac链中添加 IPv6 国内直连规则:1. 新增 nft set
2. 加载中国 IPv6 CIDR 列表
数据来源:
https://raw.githubusercontent.com/gaoyifan/china-operator-ip/ip-lists/china6.txt(约 1500 条,定期更新)可参考现有的
chinaipset.sh对 IPv4@china的加载方式。3. 在链创建代码中添加 IPv6 规则
在
ip daddr @china return之后、jump ss_spec_wan_fw之前,加入:该修改仅需在 router 模式的链生成代码中添加(gfw 模式可一并添加以显式声明 IPv6 直连,非必须)。
4. (可选)新增 CLI 参数
类似
-i指定 IPv4 列表文件,可增加-6参数指定 IPv6 列表文件,如:当前临时方案
我们目前的 workaround 是通过
chinaipset6.sh在ssr-rules执行完后手动插入规则并挂到/etc/init.d/shadowsocksr的start()钩子中。该方案存在问题:/etc/init.d/shadowsocksr,钩子丢失验证方法
修复后可通过以下命令验证: