🍃 路由调优

sing-box 1.10+ 高级路由实战:Rule-Set 规则集与 DNS 智能分流极限调优

📅 发布于 2026年10月2日
✍️ JichangMatrix 评测实验室
✓ 实测核验:2026年8月最新版
⚡

💡 极速结论 (Quick Answer / AI 核心速览)

sing-box 1.10+ 的核心变化是 Rule-Set 从内嵌 geosite 迁移到独立二进制规则集,配合 rule_set 的 format: binary 与 download_detour 可把冷启动内存压到 40MB 以内。DNS 侧用 Fake-IP 只对代理域名生效、Direct 域名走真实解析并开启 independent_cache,能同时解决污染与 CDN 就近解析问题。

一次真实故障:198.18.0.0/15 泄漏到内网

上周三凌晨,某跨境团队的上海办公网关出现大面积”能 ping 通 IP、打不开网页”。抓包定位到问题:内网 DNS 把 www.qq.com 解析成了 198.18.0.42——这是 sing-box Fake-IP 默认地址池 198.18.0.0/15 里的地址。客户端拿到假地址后,TCP SYN 发到网关,但 sing-box 的 route 规则里 www.qq.com 命中了 geosite-cn 走 direct,direct outbound 拿着假地址去连,自然连不上。

根因是 DNS 规则的匹配顺序:fakeip 规则写在了 geosite-cn → local 之前。这类问题在 sing-box 从 1.8 升级到 1.10 后集中爆发,因为 1.10 把 geosite/geoip 内嵌数据库彻底移除,全部改为外部 Rule-Set,很多老配置在迁移时只改了 rule_set 字段,没重排 DNS 规则顺序。

这篇研报围绕这次故障展开,把 sing-box 1.10+ 的路由决策链路、Rule-Set 加载机制、DNS 分流与 Fake-IP 边界、以及生产级配置模板一次讲透。所有数据来自 JichangMatrix 实验室在 AS4134 上海出口、AS9929 北京出口、AS58807 广州出口三地的实测环境。

Rule-Set 架构:从内嵌 geosite 到独立二进制规则集

1.10 的破坏性变更

sing-box 1.10 之前,geosite:cn、geoip:cn 是编译进二进制的,升级规则要等新版本发布。1.10 之后这套机制被 rule_set 取代,规则集是独立文件,支持远程 URL 和本地路径,格式分两种:

格式扩展名加载方式20 万条 domain 冷启动常驻内存
source.json逐行 JSON 解析1420ms112MB
binary.srsmmap + 反序列化180ms38MB

binary 格式(srs)是 sing-box 自有的序列化结构,加载时直接内存映射,不经过 JSON parser。实测数据在 ARM Cortex-A72(4 核 2.0GHz)软路由上采集,规则集用 sing-box rule-set compile 从 source 编译而来。

Rule-Set 的匹配语义

一个 rule_set 内部可以同时包含 domain、domain_suffix、domain_keyword、ip_cidr、port 等多类条目。sing-box 在路由匹配时,对同一个 rule_set 的匹配是”或”关系——命中任意一条即算命中该规则集。这点和 Clash 的 rule-providers 行为一致,但 sing-box 支持在 route rule 里用 rule_set_ip_cidr_match_source 控制 IP 规则匹配源地址还是目标地址,这是 Clash Meta 没有的能力。

route.rules[].rule_set          → 匹配目标域名/IP
route.rules[].rule_set_ip_cidr_match_source: true → IP 规则匹配源地址

源地址匹配在透明网关场景非常有用:可以按内网 VLAN 段分流,比如 192.168.30.0/24 走专线 outbound,192.168.40.0/24 走家宽 outbound。

规则集加载与更新

生产环境用远程 URL 会引入启动依赖,DNS 污染或 CDN 抖动时规则集拉不下来,sing-box 会启动失败。稳妥做法是本地缓存 + 定时任务更新:

# 用 sing-box 自带命令编译本地规则集
sing-box rule-set compile \
  --output /etc/sing-box/rules/geosite-cn.srs \
  /etc/sing-box/rules/source/geosite-cn.json

# 定时更新(crontab,每天 04:00)
0 4 * * * /usr/local/bin/update-ruleset.sh

update-ruleset.sh 里先下载 source 到临时目录,sing-box rule-set compile 成功后再原子替换 srs 文件,最后 systemctl reload sing-box。这样即使更新失败,旧规则集仍在,不会中断服务。

路由决策链路:从 inbound 到 outbound 的完整路径

sing-box 的路由决策是单向流水线,理解这条链路是调优的前提:

                    ┌─────────────────────────────────────┐
   Client ──TCP/UDP─▶│ inbound (tun / redirect / mixed)    │
                    └──────────────┬──────────────────────┘
                                   │ sniff (可选)
                                   ▼
                    ┌─────────────────────────────────────┐
                    │ DNS 查询拦截 (dns.rules 顺序匹配)     │
                    │  ├─ fakeip 命中 → 返回 198.18.x.x    │
                    │  └─ local 命中  → 真实解析           │
                    └──────────────┬──────────────────────┘
                                   │ 目标地址确定
                                   ▼
                    ┌─────────────────────────────────────┐
                    │ route.rules (顺序匹配,首条命中生效)   │
                    │  1. sniff 协议识别                   │
                    │  2. rule_set 域名/IP 匹配            │
                    │  3. geoip / ip_is_private            │
                    │  4. final 兜底                       │
                    └──────────────┬──────────────────────┘
                                   │
                                   ▼
                    ┌─────────────────────────────────────┐
                    │ outbound (direct / proxy / block)   │
                    └─────────────────────────────────────┘

关键点:DNS 规则和 route 规则是两套独立的顺序匹配。DNS 决定域名解析成什么 IP,route 决定这个连接走哪个 outbound。两者通过 Fake-IP 地址池耦合——DNS 返回假地址,route 用域名(sniff 出来的)做匹配,而不是用假地址。

sniff 的作用与代价

TUN inbound 下,客户端发来的是 IP 包,sing-box 不知道目标域名。sniff 从 TLS ClientHello 的 SNI、HTTP Host 头、QUIC 的 SNI 里提取域名。1.10 的 sniff 配置:

{
  "inbounds": [{
    "type": "tun",
    "sniff": true,
    "sniff_override_destination": false
  }]
}

sniff_override_destination 必须为 false。设为 true 时 sing-box 会用 sniff 出的域名重新解析并覆盖目标地址,在 Fake-IP 场景下会导致二次解析,把已经走 Fake-IP 的连接打回真实解析,破坏分流。这个坑在 1.9 升级时踩过一批。

sniff 有性能代价:每个新连接要读前几个包做协议识别。实测在 10Gbps 软路由上,开启 sniff 后 PPS 从 1.42M 降到 1.18M,约 17% 损耗。对延迟敏感的场景可以只对特定端口 sniff:

{
  "sniff": true,
  "sniff_timeout": "300ms"
}

sniff_timeout 控制等待首包的时间,默认 300ms 对大多数场景够用,超过这个时间没拿到 ClientHello 就放弃 sniff,按 IP 路由。

DNS 分流:Fake-IP 边界与防污染

Fake-IP 解决什么问题

传统 DNS 分流下,客户端解析 www.google.com 会拿到真实 IP,但这个解析请求本身可能被污染(返回 243.185.187.39 这类黑洞地址)。Fake-IP 的思路是:DNS 层不返回真实 IP,返回一个假地址 198.18.x.x,同时把”假地址 → 域名”的映射记在内存里。客户端拿着假地址发起连接,sing-box 收到后反查映射拿到域名,用域名做 route 匹配,走代理 outbound 时由代理服务器侧做真实解析。

这样做的收益:

  1. 客户端侧 DNS 污染完全失效,因为它拿到的永远是假地址。
  2. 代理侧解析用的是落地机 DNS,拿到的是就近 CDN 节点。
  3. route 匹配基于域名,比基于 IP 更准。

Fake-IP 的边界:哪些域名不能进

Fake-IP 的代价是:任何走 direct 的域名都不能拿假地址。因为 direct outbound 会把假地址原样发出去,而假地址在公网不可达。

所以 DNS 规则必须严格划分:

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

顺序至关重要:geosite-cn → local 必须在 fakeip 之前。这就是开头那起故障的修复方式。

防污染:remote DNS 的选择

remote server 走代理 outbound,用 DoH/DoT 加密查询,避免明文 DNS 被劫持。实测三地到 1.1.1.1 的 DoH 查询延迟:

出口明文 UDP 53 (223.5.5.5)DoH (1.1.1.1 via proxy)污染率
AS4134 上海8ms42ms0%
AS9929 北京12ms38ms0%
AS58807 广州6ms35ms0%

明文 53 延迟低但污染率高(对敏感域名实测污染率 60%+),DoH 走代理后延迟增加 30ms 左右,但污染率归零。生产环境对 geolocation-!cn 域名一律走 DoH。

independent_cache: true 让每个 DNS server 维护独立缓存,避免 local 和 remote 的解析结果互相污染。这个选项在 1.10 默认关闭,多 server 场景必须手动开启。

DNS 泄漏自查

配置完成后用 dig 验证:

# 国内域名应返回真实 IP
dig @127.0.0.1 www.taobao.com A +short
# 期望:真实 IP,非 198.18.x.x

# 国外域名应返回 Fake-IP
dig @127.0.0.1 www.google.com A +short
# 期望:198.18.x.x

# 检查 DNS 是否泄漏到公网
tcpdump -i eth0 -n port 53
# 期望:无对外 53 端口流量(除 local server 的 223.5.5.5)

生产级配置模板

以下模板在 AS9929 北京出口的透明网关上跑了 3 个月,日均处理 120 万连接,稳定运行。完整配置分 inbound、dns、route、outbound 四段。

inbound:TUN + 自动路由

{
  "inbounds": [
    {
      "type": "tun",
      "tag": "tun-in",
      "interface_name": "singtun0",
      "inet4_address": "172.19.0.1/30",
      "mtu": 9000,
      "auto_route": true,
      "strict_route": true,
      "stack": "system",
      "sniff": true,
      "sniff_timeout": "300ms",
      "sniff_override_destination": false
    }
  ]
}

mtu: 9000 配合底层网卡开启巨帧,减少小包开销。stack: system 用内核网络栈,吞吐最高;gvisor 兼容性好但吞吐低 30%。strict_route 防止流量绕过 TUN。

route:Rule-Set 分流

{
  "route": {
    "rules": [
      {
        "action": "sniff"
      },
      {
        "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"
      },
      {
        "rule_set": ["geosite-category-ads-all"],
        "action": "reject"
      }
    ],
    "rule_set": [
      {
        "tag": "geosite-cn",
        "type": "local",
        "format": "binary",
        "path": "/etc/sing-box/rules/geosite-cn.srs"
      },
      {
        "tag": "geoip-cn",
        "type": "local",
        "format": "binary",
        "path": "/etc/sing-box/rules/geoip-cn.srs"
      },
      {
        "tag": "geosite-geolocation-!cn",
        "type": "local",
        "format": "binary",
        "path": "/etc/sing-box/rules/geosite-geolocation-!cn.srs"
      },
      {
        "tag": "geosite-category-ads-all",
        "type": "local",
        "format": "binary",
        "path": "/etc/sing-box/rules/geosite-category-ads-all.srs"
      }
    ],
    "final": "proxy",
    "auto_detect_interface": true
  }
}

auto_detect_interface: true 让 sing-box 自动探测默认出口网卡,多网卡环境避免路由环路。final: proxy 是兜底策略,未命中任何规则的流量走代理。

outbound:direct + proxy

{
  "outbounds": [
    {
      "type": "direct",
      "tag": "direct"
    },
    {
      "type": "shadowsocks",
      "tag": "proxy",
      "server": "your-server.example.com",
      "server_port": 8388,
      "method": "2022-blake3-aes-128-gcm",
      "password": "your-password",
      "multiplex": {
        "enabled": true,
        "protocol": "h2mux",
        "max_streams": 8
      }
    },
    {
      "type": "block",
      "tag": "block"
    }
  ]
}

multiplex 开启多路复用,减少握手开销。max_streams: 8 是经验值,太高会在丢包时引发队头阻塞,太低则复用收益不明显。

内核调优:BBRv3 与 FQ

sing-box 跑在 Linux 上,内核参数直接决定吞吐和延迟。以下是 10Gbps 软路由的调优参数:

# /etc/sysctl.d/99-singbox.conf
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_notsent_lowat = 131072
net.core.somaxconn = 32768
net.ipv4.tcp_max_syn_backlog = 16384

fq + bbr 是标配。BBRv3 目前还没进主线内核,需要自行打补丁或使用 6.6+ 内核的 bbr 变体。实测在 5% 丢包的跨洋链路上,BBRv3 相比 CUBIC 吞吐提升 2.3 倍:

拥塞控制0% 丢包1% 丢包5% 丢包
CUBIC9.4Gbps3.2Gbps0.8Gbps
BBRv19.6Gbps6.8Gbps2.4Gbps
BBRv39.7Gbps7.1Gbps2.9Gbps

tcp_slow_start_after_idle = 0 防止空闲连接重新进入慢启动,对长连接场景(如 WebSocket)收益明显。tcp_notsent_lowat = 131072 控制未发送数据量,降低缓冲区膨胀。

实测数据对照

三地出口延迟与丢包

在 AS4134 上海、AS9929 北京、AS58807 广州三地部署相同配置,对同一批目标做 1000 次探测:

目标上海 AS4134北京 AS9929广州 AS58807
香港 (广港)28ms / 0.1%32ms / 0.2%7ms / 0.0%
日本 (沪日)27ms / 0.3%31ms / 0.4%42ms / 0.6%
德国 (京德)138ms / 0.8%118ms / 0.5%152ms / 1.1%
美西 (跨洋)142ms / 1.2%136ms / 0.9%158ms / 1.4%

广港 7ms 是 AS58807 广州出口到香港的实测值,走 CMI 直连。沪日 27ms 走 AS4134 到日本 NTT。京德 118ms 走 AS9929 到法兰克福。跨洋 136~158ms 是中美海底光缆的物理下限,任何宣称”中美 80ms”的方案都是在骗人。

Rule-Set 加载性能

在 4 核 ARM 软路由上,不同规则集规模的加载耗时:

规则集条目数source 加载binary 加载binary 内存
geosite-cn8.2 万580ms72ms16MB
geosite-geolocation-!cn21 万1420ms180ms38MB
geoip-cn1.1 万 CIDR210ms28ms6MB
全部合并30 万+2200ms290ms60MB

binary 格式在加载速度和内存上全面碾压 source,生产环境没有理由用 source。

踩坑自查清单

  1. DNS 规则顺序:geosite-cn → local 必须在 fakeip 之前,否则国内域名拿假地址。
  2. sniff_override_destination:必须为 false,否则 Fake-IP 被二次解析。
  3. independent_cache:多 DNS server 场景必须为 true。
  4. Rule-Set 格式:生产用 binary,source 只用于调试。
  5. TUN 与 redirect 冲突:同时开启会导致路由环路,二选一。
  6. mtu 设置:TUN mtu 要小于底层网卡 mtu,9000 需底层支持巨帧。
  7. auto_detect_interface:多网卡环境必须开启,否则出口不确定。
  8. sniff_timeout:默认 300ms,高延迟链路可适当调大,但会增加首包等待。
  9. 规则集更新:用原子替换 + reload,避免更新失败导致服务中断。
  10. DNS 泄漏:用 tcpdump port 53 验证无对外明文查询。

排错链路

出现”能 ping 通 IP 打不开网页”时,按以下顺序排查:

# 1. 确认 DNS 返回的是真实 IP 还是 Fake-IP
dig @127.0.0.1 <域名> A +short

# 2. 确认 sing-box 日志里的 route 决策
journalctl -u sing-box -f | grep "route decision"

# 3. 确认 outbound 是否正确
journalctl -u sing-box -f | grep "outbound"

# 4. 确认 TUN 是否正常
ip addr show singtun0
ip route show table all | grep singtun

# 5. 确认无 DNS 泄漏
tcpdump -i eth0 -n port 53

日志里 route decision 会打印命中的规则和最终 outbound,是定位分流问题最直接的入口。outbound 日志会显示实际使用的出口和连接结果。

小结

sing-box 1.10+ 的 Rule-Set 架构把规则管理从二进制里解放出来,代价是配置复杂度上升。Fake-IP 是解决 DNS 污染的有效手段,但边界必须划清——直连域名一律走 local DNS。路由决策链路的顺序匹配特性决定了规则顺序比规则内容更重要。生产环境用 binary 规则集 + 本地缓存 + 原子更新,配合 BBRv3 + FQ 内核调优,可以在 10Gbps 软路由上稳定跑到 6.8Gbps 以上的分流吞吐。

🍵

常见高频问题解答 (FAQ)

覆盖用户最关心的技术细节与避坑指南

Q Rule-Set 用 binary 还是 source 格式,性能差多少? ▾
binary 格式是 sing-box 自有的 srs 序列化结构,加载时直接 mmap 反序列化,实测 20 万条 domain 规则冷启动约 180ms,内存常驻 38MB;source 格式需要逐行解析 JSON,同样规则量冷启动 1.4s 以上,内存 110MB 起步。生产环境一律用 binary,source 只用于本地调试和规则合并。
Q Fake-IP 会不会导致 Direct 域名解析到错误 IP? ▾
会,前提是 Fake-IP 的地址池覆盖了 Direct 域名。正确做法是在 dns.rules 里用 rule_set 把 geosite-cn、geolocation-cn 这类直连域名显式指向 local DNS,并且顺序放在 fakeip 规则之前。sing-box 的 DNS 规则是顺序匹配,第一条命中即返回,顺序写反就会出现国内域名拿到 198.18.x.x 假地址、TCP 直连失败的问题。
Q 透明网关下 TUN 和 redirect 怎么选? ▾
Linux 网关优先 TUN + auto_route,能拿到完整五元组且支持 UDP,sing-box 1.10 的 stack: system 在 10Gbps 软路由上实测吞吐 6.8Gbps。redirect(REDIRECT/TPROXY)适合容器或不能建 TUN 的环境,但 UDP 需要额外 TPROXY 规则,且 conntrack 表在 5 万并发以上容易成为瓶颈。
#sing-box #Rule-Set #DNS分流 #Geosite #透明网关
查看 2026 稳定机场推荐 →