背景 链接到标题

手头有一台充当代理的中低功耗服务器,跑了 sing-box + Shadowsocks,内网多节点统一通过它出网。日常用它拉 Docker 镜像、下载模型文件、跑构建任务。

以前默认行为是:只要流量出了本地局域网,全部走 Shadowsocks 代理到国外中转节点。用了一段时间后发现国内服务的请求也绕了一大圈,带宽消耗不小,速度也不快。

本文记录改造过程:如何把国内域名和 IP 段从代理链路里摘出来,走直连。


环境 链接到标题

项目
sing-box 版本 1.13.12
出站协议 Shadowsocks(chacha20-ietf-poly1305)
入站 HTTP 代理 :7890
路由规则类型 rule_set.srs 二进制格式)
GeoIP/GeoSite 来源 SagerNet 官方 GitHub 仓库

基础配置 链接到标题

目录结构 链接到标题

/opt/sing-box/
├── .env                      # 节点凭据(敏感)
├── docker-compose.yaml       # Compose 编排
└── sing-box/
    ├── Dockerfile            # 基于 ghcr.io/sagernet/sing-box
    ├── entrypoint.sh         # 配置渲染入口
    └── config.json           # 运行时配置(自动生成)

docker-compose.yaml 链接到标题

services:
  sing-box:
    build:
      context: ./sing-box
      dockerfile: Dockerfile
    container_name: sing-box-clash
    env_file:
      - ./.env
    ports:
      - "7890:7890"
    restart: unless-stopped
    volumes:
      - ./sing-box:/etc/sing-box

.env 里存 Shadowsocks 节点信息,通过 entrypoint.sh 读取后渲染进 config.json,不把明文密码提交到代码库。

entrypoint.sh 核心逻辑 链接到标题

PROXY_SERVER="${PROXY_SERVER:-}"
PROXY_PORT="${PROXY_PORT:-}"
PROXY_CIPHER="${PROXY_CIPHER:-}"
PROXY_PASSWORD="${PROXY_PASSWORD:-}"

# 校验环境变量
if [ -z "$PROXY_SERVER" ] || [ -z "$PROXY_PORT" ] \
   || [ -z "$PROXY_CIPHER" ] || [ -z "$PROXY_PASSWORD" ]; then
  echo "Error: PROXY_* env vars are not set"
  exit 1
fi

# 渲染 config.json
cat > /etc/sing-box/config.json <<JSON
{ ... }
JSON

# JSON 合法性校验
jq empty /etc/sing-box/config.json

# 启动
exec sing-box run -c /etc/sing-box/config.json

最小可用配置 链接到标题

{
  "log": { "level": "info" },
  "inbounds": [
    { "type": "http", "tag": "http-in", "listen": "0.0.0.0", "listen_port": 7890 }
  ],
  "outbounds": [
    { "type": "shadowsocks", "tag": "ss-out", "method": "chacha20-ietf-poly1305",
      "password": "***", "server": "***", "server_port": 996, "udp_over_tcp": true },
    { "type": "direct", "tag": "direct" },
    { "type": "block", "tag": "block" }
  ],
  "route": {
    "rules": [
      { "ip_cidr": ["192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12"],
        "outbound": "direct" }
    ],
    "final": "ss-out"
  }
}

最小配置只有一条规则:私网 IP 走直连,其余全部走代理。能用,但效率不够——国内公共服务也被送去了 Shadowsocks。


路由规则演进 链接到标题

阶段一:仅私网直连 链接到标题

{ "route": {
    "rules": [
      { "ip_cidr": ["192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12"],
        "outbound": "direct" }
    ],
    "final": "ss-out"
}}

问题:访问国内服务(Docker Hub、ModelScope、PyPI)时,请求走出局域网 IP → 命中 final → 绕到 Shadowsocks 中转节点再回来,延迟高、带宽浪费。

阶段二:加入 GeoIP + GeoSite 规则集 链接到标题

{ "route": {
    "rules": [
      { "ip_cidr": ["192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12"],
        "outbound": "direct" },
      { "rule_set": ["geoip-cn"], "outbound": "direct" },
      { "rule_set": ["geosite-cn"], "outbound": "direct" }
    ],
    "rule_set": [
      {
        "tag": "geoip-cn",
        "type": "remote",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs",
        "download_detour": "ss-out"
      },
      {
        "tag": "geosite-cn",
        "type": "remote",
        "format": "binary",
        "url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs",
        "download_detour": "ss-out"
      }
    ],
    "final": "ss-out"
}}

优先级从高到低:ip_cidr(私网)→ geoip-cn(中国 IP 段)→ geosite-cn(中国域名)→ final(代理兜底)。


GeoIP/GeoSite 规则集 链接到标题

为什么不用旧版 geoip/geosite 字段 链接到标题

sing-box 1.8.0 引入了 rule_set 机制替代旧的 geoip/geosite 字段,旧字段在 1.12.0 已完全移除。当前 1.13.12 版本若使用旧字段会启动失败。

旧版(已废弃):

{ "route": { "geoip": { "path": "geoip.db" }, "geosite": { "path": "geosite.db" } }}

新版(当前标准):

{ "route": { "rule_set": [ ... ], "rules": [{ "rule_set": ["geoip-cn"] }] }}

规则集文件来源 链接到标题

两个文件都由 SagerNet 官方维护,自动更新:

规则集 仓库 文件大小 更新频率
geoip-cn.srs sing-geoip ~33KB 每周一次
geosite-cn.srs sing-geosite ~51KB 每天多次

下载路径说明 链接到标题

{
  "tag": "geoip-cn",
  "type": "remote",
  "format": "binary",
  "url": "https://raw.githubusercontent.com/.../geoip-cn.srs",
  "download_detour": "ss-out"
}
  • type: "remote":从远程 URL 拉取,启动时自动下载
  • format: "binary".srs 二进制格式,比文本规则解析更快、内存占用更低
  • download_detour: "ss-out":首次下载走 Shadowsocks 代理(GitHub 国内直连不可达),下载后本地缓存,之后不再重复拉取

分流逻辑 链接到标题

流量进入 sing-box
      │
      ▼
匹配 ip_cidr 私网段(192.168/10/172.16)
      │ 命中 → direct(直连)
      │ 未命中
      ▼
匹配 rule_set geoip-cn(中国 IP 段)
      │ 命中 → direct
      │ 未命中
      ▼
匹配 rule_set geosite-cn(中国域名)
      │ 命中 → direct
      │ 未命中
      ▼
final → ss-out(Shadowsocks 代理)

国内域名和 IP 的覆盖范围 链接到标题

geosite-cn 包含但不限于:.cn 域名、阿里云全系服务、Docker Hub 国内镜像域名、华为云、腾讯云等国内 CDN 域名。geoip-cn 包含所有分配给中国大陆的 IP 地址段。两者配合,既能按域名匹配,也能在域名不在列表但 IP 在国内时兜底命中。


DNS 链路与 DNS 污染 链接到标题

sing-box 是 HTTP 代理,只转发 TCP 流量,不处理 DNS 查询。DNS 解析由各节点本地 DNS 完成,与 sing-box 路由决策互不干涉。

当前 DNS 链路 链接到标题

节点
  ├── /etc/hosts  →  短域名优先命中(如 192.168.*.*)
  └── /etc/resolv.conf: nameserver 192.168.*.*(内网 DNS 服务器)
        └── 上游公网 DNS
              └── 正确解析 modelscope.cn / docker.io / github.com

DNS 污染场景 链接到标题

如果运营商对某些域名返回被污染的 IP(例如 google.com 指向国内某节点),DNS 解析本身不会走 sing-box 代理。此时:

  • 被污染的 IP 若不在中国 GeoIP 范围内 → 走 ss-out 代理绕过去(结果正确,但绕了弯路)
  • 被污染的 IP 恰好在中国 GeoIP 范围内 → 走直连,结果错误

路由层无法补偿 DNS 污染。真正防污染需要 sing-box 接管 DNS 查询,用指定 DNS 服务器解析后,再用 GeoIP/GeoSite 做路由判断。当前版本(纯 HTTP 代理模式)做不到这一点。当前环境实测内网 DNS 解析正常,暂无此问题,但如果遇到明确的污染行为,需要升级到完整的 DNS + 路由一体化方案。


实测结果 链接到标题

改造前国内服务流量全部走 Shadowsocks,改造后验证分流效果:

域名 出站 延迟 备注
modelscope.cn direct ~60ms 国内直连
docker.m.daocloud.io direct ~100ms 国内镜像
pypi.tuna.tsinghua.edu.cn direct ~3.8s 国内 PyPI 源
www.baidu.com direct ~2s 国内域名
github.com ss-out ~840ms 国外走代理

modelscope.cn 下载大文件时从之前的 500KB/s 提升到 5MB/s+,差异明显。


注意事项 链接到标题

短域名 链接到标题

内网短域名走 /etc/hosts 或内网 DNS 解析为私网 IP,天然命中 ip_cidr 规则直连,不需要单独配置域名规则。

Docker daemon 的 no-proxy 链接到标题

Docker 的 no-proxy 配置和 sing-box 分流是两个独立机制。Docker daemon 的 no-proxy 控制 Docker 引擎自身是否直连目标,sing-box 控制代理后的路由决策,两者互不干涉。

规则集更新 链接到标题

SagerNet 仓库每周自动更新规则集,sing-box 启动时自动拉取最新版本,无需手动操作。首次启动时 download_detour: "ss-out" 走代理下载约 85KB,之后持久化到本地缓存。

日志级别 链接到标题

生产环境保持 "level": "info"。排障时改 "level": "debug" 可查看每条流量的匹配详情。


参考 链接到标题