跳过正文

Singbox 双栈配置:两个坑

·494 字·3 分钟·
soloopooo
作者
soloopooo
What about you?

最近在给 sing-box 配置双栈(IPv4 + IPv6),折腾过程中踩了两个坑,记录一下。

坑一:DNS 策略藏着 ipv4_only
#

一开始怎么配 IPv6 都不走,排查了一圈,最后发现是 DNS 配置里有个 "strategy": "ipv4_only" 在作祟。它让 sing-box 直接丢弃所有 AAAA 记录,IPv6 根本无从谈起。删掉或改成 prefer_ipv6 就好了。小坑。

坑二:链式代理下 IPv6 大包莫名卡死
#

真正的噩梦是这个。网络拓扑:

客户端 Mihomo (SOCKS5) → 公网高速中转节点 → 服务器 Nginx (Stream) → sing-box (AnyTLS) → 目标

双栈配好后,访问 test-ipv6.com 出现了非常分裂的现象:

  • IPv6 地址检测 ✅
  • 小包连通性 10/10 ✅
  • 大数据包传输测试 ❌ 进度条卡死,最终超时

具体表现是:网页能打开,但只要涉及大图、视频流、文件上传,就直接挂起。DNS 解析正常、TLS 握手成功,但数据一多就断流。

排障过程
#

我怀疑过中转节点配置不对、Nginx stream 转发有问题、sing-box 参数没调好,甚至换用了 anytls-go 标准实现测试——全部失败。

当时还想过一个方向:是不是链式代理里某段链路的 MTU 有问题?但转念一想,链式代理已经把原始请求包层层封装了,中间代理段传输的是封装后的隧道流量,中间环节根本不需要关心什么 MTU——要管也是管物理链路的 MTU。

所以问题只可能出现在两个端点:客户端出站,或服务器出站。

最后直接在服务器上 curl 那个大数据包测试 URL,发现服务器自己都请求超时。说明问题不在代理软件,而是服务器网络层自身就在丢包。

决定性证据
#

test-ipv6.com 的大数据包测试本质上是这个请求(注意用引号包裹,否则 shell 会错误解析 &?):

curl -6 "https://mtu1280.osaka.test-ipv6.com/ip/?callback=?&size=1600&fill=xxxxxxxx...&testdomain=test-ipv6.com&testname=test_v6mtu"

它会发送 1600 字节的填充数据。如果 MTU 正常,响应是一个 JSONP callback 返回你的 IPv6 地址。如果卡死或超时,说明路径上存在 MTU 黑洞。

根因:机房 MTU 非标配置
#

ping -M do -s 逐级探测,找到了精确的边界:

ping -M do -s 1232 ipv6.google.com  # 通
ping -M do -s 1233 ipv6.google.com  # 断

计算:1232 (payload) + 8 (ICMPv6 头) + 40 (IPv6 头) = 1280

结论:机房链路的 IPv6 MTU 被硬限制在了 1280 字节。 上游设备配置了非标准的 MTU,且 ICMPv6 Type 2 (Packet Too Big) 要么根本没发,要么发了也被中间代理层当垃圾丢了。路径 MTU 发现(PMTUD)在这里完全是个摆设。

既然机房不守规矩——不发 ICMPv6 No.2,那我也没必要跟它搞什么协议协商。不依赖 PMTUD,不依赖客户端配合,直接在服务器物理层把规矩立死:我就是这么发包的,超过 1280 的一个不放

解决方案:路由级 MTU 隔离
#

目标是 IPv4 和 IPv6 在同一张网卡上走不同的 MTU——IPv4 满载 1500,IPv6 限制 1280。

走弯路:改网卡 MTU
#

一开始直接 sudo ip link set dev eth0 mtu 1280,测试通过了,但 IPv4 流量也被迫降到 1280,白白损失性能。

最终方案:ip -6 route change
#

ip -6 route change 修改 RA 下发的默认路由,仅对 IPv6 路径限 MTU:

# 1. 恢复 eth0 到 1500
sudo ip link set dev eth0 mtu 1500

# 2. 查看当前 IPv6 默认路由(RA 自动下发)
ip -6 route show default
# 输出:default via fe80::464c:a8ff:fe08:f27f dev eth0 metric 100 mtu 1500

# 3. 修改默认路由,强制 IPv6 MTU=1280
sudo ip -6 route change default via fe80::464c:a8ff:fe08:f27f dev eth0 mtu 1280 metric 100

# 4. 如有备用路由也一并修改
sudo ip -6 route change default via fe80::464c:a8ff:fe08:f27f dev eth0 metric 1024 mtu 1280

验证:

ip -6 route show default
# default via fe80::... dev eth0 metric 100 mtu 1280  ✅
ip link show eth0 | grep mtu
# mtu 1500  ✅ IPv4 不受影响

原理:Linux 路由表项的 MTU 优先级高于接口 MTU。IPv4 查路由表走接口默认 1500,IPv6 查路由表命中 mtu 1280 条目,内核自动分片——同一张网卡,两种标准,互不干扰。

由于路由通过 RA 获取,重启后会被重置。如需持久化,写入 netplan:

# /etc/netplan/99-ipv6-mtu.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      mtu: 1500
      dhcp4: true
      dhcp6: true
      routes:
        - to: ::/0
          via: "fe80::464c:a8ff:fe08:f27f"  # 替换为你的网关
          on-link: true
          mtu: 1280
sudo netplan apply

再踩一坑:RA 每 1800 秒重置路由
#

配完消停了一会儿,又不好使了。ip -6 route 一看:

default via fe80::464c:a8ff:fe08:f27f dev eth0 proto ra metric 100 mtu 1500
default via fe80::464c:a8ff:fe08:f27f dev eth0 proto static metric 1024 onlink mtu 1280

RA 下发的路由 metric 100 优先级高于我手动加的 metric 1024。 内核永远选 metric 小的那条,流量还是走 1500 MTU,大包继续丢。

RA (Router Advertisement) 默认每 1800 秒刷新一次,每次刷新都会覆盖我的改动。

解法:netplan 指定 metric
#

踩了个坑后发现:netplan apply 本身会触发 RA 刷新,所以直接在 netplan 的 routes 里显式指定 metric: 1 即可,RA 那条是 metric 100,我们的优先级永远更高:

# /etc/netplan/99-ipv6-mtu.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    eth0:
      mtu: 1500
      dhcp4: true
      dhcp6: true
      routes:
        - to: ::/0
          via: "fe80::464c:a8ff:fe08:f27f"  # 替换为你的网关
          on-link: true
          metric: 1
          mtu: 1280
sudo netplan apply

不需要 cron 守护脚本了,netplan 一次性搞定。

总结
#

  1. 双栈配了不走 IPv6 → 先检查 DNS strategy 是不是 ipv4_only
  2. IPv6 小包通、大包卡 → 不要急着调代理软件,先在服务器上直接测。用 ping -M do -s 找到 MTU 边界,再用 ip -6 route change 做路由级隔离,让 IPv4 和 IPv6 走不同的 MTU
  3. 机房网络的非标 MTU 配置很常见,IPv6 的 PMTUD 又脆弱,学会在路由层面做策略控制是实用技能