背景 链接到标题
手头有一台充当代理的中低功耗服务器,跑了 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" 可查看每条流量的匹配详情。
参考 链接到标题
- sing-box 官方文档:https://sing-box.sagernet.org/
- GeoIP 规则集:https://github.com/SagerNet/sing-geoip
- GeoSite 规则集:https://github.com/SagerNet/sing-geosite
- rule-set 配置文档:https://sing-box.sagernet.org/configuration/rule-set/
- 路由规则文档:https://sing-box.sagernet.org/configuration/route/rule/