OpenWrt 透明代理部署文档

概述

在 OpenWrt 25.12.2 上基于 sing-box TUN 模式部署透明代理,实现:

  • 大陆 IP 和大陆域名直连,不走代理
  • 海外流量通过 Shadowsocks 加密转发
  • DNS 分流:国内域名用国内 DNS 解析,海外域名通过代理用 Google DNS 解析
  • 其他主机将网关和 DNS 指向本机即可透明上网

已部署验证的机型:

  • MIPS ramips/mt7621 小米路由器 3G(172.20.6.217)

架构原理

1
2
3
4
5
6
7
8
9
10
11
12
13
客户端 (网关/DNS → 本机)

├─ DNS 查询 (UDP 53)
│ └─ dnsmasq → 10.10.10.2 (sing-box DNS 模块)
│ ├─ geosite-cn 命中 → 223.5.5.5 直连解析
│ └─ 其他域名 → tcp://8.8.8.8 通过 SS 解析

└─ TCP/UDP 数据
└─ 路由 → tun0 (sing-box TUN)
├─ geoip-cn 命中 → direct 直连
├─ geosite-cn 命中 → direct 直连
├─ 私有 IP → direct 直连
└─ 其余 → Shadowsocks 出站

sing-box TUN 模式通过 auto_route 自动创建策略路由,将非本地流量导入 tun0 虚拟网卡。strict_route 确保 sing-box 自身的出站连接(到 SS 服务器等)不会被 TUN 再次拦截,避免路由回路。

环境要求

  • OpenWrt 25.12.2(理论支持 25.x 全系列)
  • 内核模块:kmod-tun、kmod-nf-tproxy、kmod-nft-tproxy
  • 已开启 IP 转发
  • 至少 40MB 可用磁盘空间
  • x86_64 或 mipsel_24kc 架构(其他架构需替换 sing-box 二进制和 kmod 源)

文件传输

OpenWrt 25 没有 SFTP/SCP, 可以使用SSH PIPE 传输文件

1
2
3
4
5
传输单个文件
ssh root@172.20.6.217 'cat /root/deploy.sh' > deploy.sh

多文件打包传输
ssh root@172.20.6.217 'tar czf - -C /root deploy.sh troubleshoot.sh transparent-proxy-deploy.md' > r3g-backup.tar.gz

部署步骤

第一步:确认系统信息

登陆目标机,记录以下信息:

1
2
3
4
5
cat /etc/openwrt_release    # DISTRIB_ARCH 决定用什么架构的 sing-box
uname -r # 内核版本
ip addr show # 网络接口名和 IP
ip route show # 默认网关
df -h / # 可用空间

第二步:安装内核模块

x86_64 平台(如果目标机能访问外网):

1
2
apk add kmod-tun kmod-nf-tproxy kmod-nft-tproxy
modprobe tun nf_tproxy nft_tproxy

如果 apk 下载失败(国内网络问题),从 OpenWrt 官方仓库手动下载 apk 文件,传到目标机后用 apk add --allow-untrusted 安装。

MIPS 平台(ramips/mt7621)通常能直连 OpenWrt 仓库,直接 apk add 即可。

第三步:获取 sing-box 二进制

从 GitHub 下载对应架构的版本:

1
2
3
4
5
# x86_64
https://github.com/SagerNet/sing-box/releases/download/v1.11.9/sing-box-1.11.9-linux-amd64.tar.gz

# MIPS (mipsel_24kc / 1004Kc 软浮点)
https://github.com/SagerNet/sing-box/releases/download/v1.11.9/sing-box-1.11.9-linux-mipsle-softfloat.tar.gz

注意:MIPS 必须用 softfloat 版本。sing-box 提供的 linux-mipsle(硬浮点)在 1004Kc 核心上会报 Illegal instruction

解压后将 sing-box 放到 /usr/local/bin/sing-box,赋予执行权限:

1
2
chmod 755 /usr/local/bin/sing-box
/usr/local/bin/sing-box version # 确认可执行

第四步:下载 geo 规则集文件

1
2
3
4
5
6
7
8
9
mkdir -p /etc/sing-box

# geoip-cn.srs(大陆 IP 段,约 34KB)
wget -O /etc/sing-box/geoip-cn.srs \
https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs

# geosite-cn.srs(大陆域名列表,约 49KB)
wget -O /etc/sing-box/geosite-cn.srs \
https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs

这两个文件非常小(合计不到 100KB),不要用 v2ray 格式的 geoip.dat(22MB),sing-box 1.11.x 已不支持。

如果目标机无法访问 GitHub,在本机下载后通过 SSH 传过去。

第五步:写入 sing-box 配置

文件位置:/etc/sing-box/config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
{
"log": {"level": "info", "timestamp": true},
"dns": {
"servers": [
{"tag": "dns-direct", "address": "223.5.5.5", "detour": "direct"},
{"tag": "dns-remote", "address": "tcp://8.8.8.8", "detour": "ss-out"}
],
"rules": [
{"rule_set": "geosite-cn", "server": "dns-direct"}
],
"final": "dns-remote",
"strategy": "ipv4_only"
},
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"interface_name": "tun0",
"mtu": 1500,
"address": ["10.10.10.1/30"],
"auto_route": true,
"strict_route": true,
"sniff": true,
"sniff_override_destination": false
}
],
"outbounds": [
{"type": "dns", "tag": "dns-out"},
{
"type": "shadowsocks",
"tag": "ss-out",
"server": "XXX.XXX.XXX.XX",
"server_port": XXXX,
"password": "XXXXXX",
"method": "XXXXXXXX"
},
{"type": "direct", "tag": "direct"}
],
"route": {
"rule_set": [
{"tag": "geoip-cn", "type": "local", "format": "binary", "path": "/etc/sing-box/geoip-cn.srs"},
{"tag": "geosite-cn", "type": "local", "format": "binary", "path": "/etc/sing-box/geosite-cn.srs"}
],
"rules": [
{"protocol": "dns", "outbound": "dns-out"},
{"ip_is_private": true, "outbound": "direct"},
{"rule_set": "geoip-cn", "outbound": "direct"},
{"rule_set": "geosite-cn", "outbound": "direct"}
],
"final": "ss-out",
"auto_detect_interface": true
},
"experimental": {
"cache_file": {"enabled": false}
}
}

关键配置说明:

  • dns.servers[1].address 使用 tcp://8.8.8.8 而非 UDP。因为 UDP DNS 查询通过 TUN 代理时回程路径有问题,TCP 正常工作。
  • dns.strategy 设为 ipv4_only,过滤 AAAA 记录避免 IPv6。
  • inbounds[0].mtu 设为 1500。默认的 9000 会导致分片问题。
  • inbounds[0].auto_routestrict_route 组合是核心,sing-box 自动管理策略路由。
  • experimental.cache_file.enabled 设为 false。因为重启后 /var/cache 会被清空,默认为 true 会导致 sing-box 启动失败。

第六步:配置 DNS(dnsmasq)

dnsmasq 配置由 init 脚本在每次启动时动态生成,自动检测当前网口的 IP。手动运行时的参考配置:

1
2
3
4
5
6
7
8
listen-address=127.0.0.1
listen-address=172.20.6.217
bind-interfaces
server=10.10.10.2
server=/yview.cn/172.20.2.113
cache-size=2048
no-resolv
filter-AAAA

说明:

  • server=10.10.10.2 — 所有 DNS 查询转发到 sing-box DNS 模块(TUN 网关地址)
  • server=/yview.cn/172.20.2.113 — 如果需要访问内网域名(如公司内部系统),添加这条将特定域名后缀直接转发到内网 DNS 服务器。多个后缀可以写多行。
  • bind-interfaces — 必须启用,否则 dnsmasq 只在 lo 上监听
  • filter-AAAA — 过滤 IPv6 DNS 记录
  • no-resolv — 不从 /etc/resolv.conf 读取上游 DNS

如果完全没有内网域名需求,去掉 server=/... 那行即可。

第七步:写入 init 启动脚本

文件位置:/etc/init.d/sing-box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/bin/sh /etc/rc.common

START=95
USE_PROCD=1
PROG=/usr/local/bin/sing-box
CONFIG=/etc/sing-box/config.json

boot() { start; }

start_service() {
mkdir -p /var/cache/sing-box
ip link del tun0 2>/dev/null
sleep 1

# 自动检测网口 IP
WAN_IP=$(ip addr show eth0 | grep "inet " | awk '{print $2}' | cut -d/ -f1)

cat > /etc/dnsmasq-proxy.conf << EOF
listen-address=127.0.0.1
listen-address=${WAN_IP}
bind-interfaces
server=10.10.10.2
cache-size=2048
no-resolv
filter-AAAA
EOF

# SS 服务器和 DNS 服务器绕过 TUN 直连
for ip in XX.XX.XX.XX 223.5.5.5 172.20.2.113; do
ip rule add to ${ip}/32 table main priority 8999 2>/dev/null || true
done

killall dnsmasq 2>/dev/null
sleep 1
dnsmasq -C /etc/dnsmasq-proxy.conf

procd_open_instance
procd_set_param command /bin/sh -c "$PROG run -c $CONFIG > /tmp/sing-box.log 2>&1"
procd_set_param respawn
procd_close_instance
}

stop_service() {
killall sing-box dnsmasq
ip link del tun0 2>/dev/null
}

关键设计:

  • ip link del tun0 — 防止重启后残留 tun0 导致新启动报 device or resource busy
  • WAN_IP=$(ip addr show eth0 ...) — 自动检测当前 IP,换 IP 后重启服务即可适配
  • bypass 路由:SS 服务器和 DNS 服务器的流量通过 ip rule 绕过 TUN 直连,防止路由回路
  • dnsmasq 配置每次启动动态生成,IP 变化时自动适配
  • 日志写入 /tmp/sing-box.log 而非 procd stdout,便于查看
  • 网口名根据实际情况修改:x86_64 通常是 eth0,有些设备是 br-wanbr-lan

赋予执行权限并启用:

1
2
chmod 755 /etc/init.d/sing-box
/etc/init.d/sing-box enable

如果系统自带 dnsmasq init,需要禁用它避免冲突:

1
2
/etc/init.d/dnsmasq disable
rm -f /etc/rc.d/S*dnsmasq*

第八步:开启 IP 转发并持久化

1
2
echo 1 > /proc/sys/net/ipv4/ip_forward
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

第九步:配置 geo 规则集自动更新

文件位置:/usr/local/bin/update-geo.sh

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
GEOIP_URL="https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
GEOSITE_URL="https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs"
DST="/etc/sing-box"

wget -q --timeout=15 -O "$DST/geoip-cn.srs.new" "$GEOIP_URL" && \
[ -s "$DST/geoip-cn.srs.new" ] && mv "$DST/geoip-cn.srs.new" "$DST/geoip-cn.srs"

wget -q --timeout=15 -O "$DST/geosite-cn.srs.new" "$GEOSITE_URL" && \
[ -s "$DST/geosite-cn.srs.new" ] && mv "$DST/geosite-cn.srs.new" "$DST/geosite-cn.srs"

/etc/init.d/sing-box restart 2>/dev/null

添加 cron 任务:

1
2
chmod 755 /usr/local/bin/update-geo.sh
echo "17 03 * * * /usr/local/bin/update-geo.sh" >> /etc/crontabs/root

第十步:启动服务

1
/etc/init.d/sing-box start

验证

检查进程:

1
ps | grep -E "sing-box|dnsmasq"

应该看到两个进程都在运行。

检查 TUN 接口:

1
ip addr show tun0

应该显示 inet 10.10.10.1/30。

检查 bypass 路由:

1
ip rule show | grep 8999

应该显示 SS 服务器和 DNS 服务器的 bypass 规则。

检查 DNS:

1
2
nslookup www.baidu.com 127.0.0.1    # 应返回大陆 IP,如 180.101.x.x
nslookup www.google.com 127.0.0.1 # 应返回海外真实 IP,如 142.251.x.x

检查日志:

1
cat /tmp/sing-box.log

在客户端执行:

1
2
curl -vvv https://www.baidu.com/    # 应正常返回
curl -vvv https://www.google.com/ # 应正常返回

客户端设置

将需要使用透明代理的机器配置为:

  • 默认网关:指向本机 IP
  • DNS 服务器:指向本机 IP

Windows 在网络适配器属性中设置。Linux 使用 ip route/etc/resolv.conf

日常维护

启动、停止、重启:

1
2
3
/etc/init.d/sing-box start
/etc/init.d/sing-box stop
/etc/init.d/sing-box restart

查看日志:

1
cat /tmp/sing-box.log

更换 SS 节点:

  1. 编辑 /etc/sing-box/config.json,修改 shadowsocks 段落中的 server、server_port、password、method
  2. 编辑 /etc/init.d/sing-box,找到 bypass 路由中的旧 SS IP,替换为新 IP
  3. 执行 /etc/init.d/sing-box restart

更换本机 IP 后:

执行 /etc/init.d/sing-box restart 即可。init 脚本会自动检测新 IP 并更新 dnsmasq 配置。

遇到过的坑

sing-box 二进制兼容性

MIPS 必须用 softfloat 版本。sing-box 发布的 linux-mipsle 是硬浮点编译,在 1004Kc 核心上会 Illegal instruction。正确选择是 linux-mipsle-softfloat

TUN MTU

TUN 接口默认 MTU 9000,但底层物理网卡通常只有 MTU 1500。这会导致大包分片失败,TCP 握手成功但后续数据传不动。在 config.json 中显式设为 1500。

config.json 中 cache_file 路径

sing-box 默认启用 cache_file,路径是 /var/cache/sing-box/cache.db。OpenWrt 的 /var/cache 在重启后会被清空,目录不存在。如果 experimental.cache_file.enabled 为 true 且目录不存在,sing-box 启动时报 FATAL 错误。设为 false 或者 init 脚本中 mkdir -p 创建目录。

TUN 接口残留

重启 OpenWrt 后,如果 sing-box 异常退出(没有执行 stop 清理),tun0 接口可能残留。下一次启动时创建 tun0 会报 device or resource busy。所以在 init 脚本的 start_service 开头执行 ip link del tun0 2>/dev/null

auto_route 与 strict_route

auto_route 创建策略路由将所有流量导入 TUN。strict_route 确保 sing-box 自身的出站连接不被 TUN 捕获。两者必须同时启用,否则 SS 服务器连接会形成路由回路。

SS 服务器和 DNS 的 bypass 路由

即使有 strict_route,某些情况下 sing-box 的出站连接仍可能被 TUN 路由表匹配。最稳妥的做法是在 init 脚本中手动添加 bypass 路由规则:

1
2
ip rule add to SS服务器IP/32 table main priority 8999
ip rule add to DNS服务器IP/32 table main priority 8999

DNS 的 UDP 问题

UDP DNS 查询通过 TUN/SS 代理后,回程响应无法正确送达。解决方案是使用 TCP DNS:在 sing-box DNS 配置中将远程 DNS 地址写成 tcp://8.8.8.8

内网域名解析

公司的内网域名(如 oa.yview.cn)只能由内网 DNS 服务器解析。如果 sing-box DNS 模块(10.10.10.2)查不到,需要在 dnsmasq 中配置域名后缀转发:

1
server=/yview.cn/内网DNS的IP

如果内网 DNS 本身也被 GFW 污染(比如解析 google.com 返回假 IP),这种方案恰好正确——内网域名走内网 DNS,公网域名走 sing-box DNS 分流。

dnsmasq 的 rebind 保护

OpenWrt 的 dnsmasq 默认开启 stop-dns-rebind,会丢弃上游 DNS 返回的私有 IP 地址(RFC1918:10.x、172.16-31.x、192.168.x)。如果内网 DNS 服务器返回的恰好是私有 IP,会被过滤掉。表现为 nslookup 直连上游 DNS 能解析,但通过 dnsmasq 不行。在 /etc/config/dhcp 中设置 option rebind_protection '0' 可关闭。

dnsmasq 不自启

OpenWrt 的 dnsmasq init 脚本需要在 /etc/config/dhcp 有 UCI 配置才会启动。我们的方案是接管 dnsmasq,在 sing-box init 脚本中直接启动,不依赖 UCI。所以需要禁用系统的 dnsmasq init,避免冲突。

sing-box v2ray 格式不支持

sing-box 1.8.0 起逐步废弃 v2ray 格式的 geoip.dat/geosite.dat,1.11.x 中必须使用 .srs 格式的规则集。不要用 v2fly 的 22MB geoip.dat,用 SagerNet 的 34KB .srs 文件即可。

MIPS 内存

sing-box 在 MIPS 上运行时占用约 55MB 内存。小米路由器 3G(256MB RAM)完全够用。如果是 128MB 以下的设备需要留意。

旁路由模式的网口配置

如果 OpenWrt 作为旁路由(只有一个网口收发所有流量),确保 init 脚本中的网口名正确。常见的网口名:eth0(x86 物理机)、br-lan(桥接 LAN)、br-wan(桥接 WAN)。查看方式:ip addr show 找到配置了 IP 的接口名。

文件清单

部署完成后,目标机上关键文件:

  • /usr/local/bin/sing-box — 主程序
  • /etc/sing-box/config.json — sing-box 配置(含 SS 节点信息)
  • /etc/sing-box/geoip-cn.srs — 大陆 IP 规则集(每日自动更新)
  • /etc/sing-box/geosite-cn.srs — 大陆域名规则集(每日自动更新)
  • /etc/dnsmasq-proxy.conf — DNS 配置(init 脚本动态生成)
  • /etc/init.d/sing-box — 启动脚本(含 bypass 路由和 dnsmasq 启动)
  • /usr/local/bin/update-geo.sh — geo 规则集更新脚本
  • /tmp/sing-box.log — 运行日志

诊断

如果出问题,在目标机上执行一键诊断:

1
sh /root/troubleshoot.sh

手动检查要点:

  • 进程:ps | grep -E "sing-box|dnsmasq"
  • TUN:ip addr show tun0
  • 路由:ip rule show | grep 8999
  • DNS:nslookup www.baidu.com 127.0.0.1
  • 日志:cat /tmp/sing-box.log

常见修复命令:

1
2
3
4
5
6
7
8
# TUN 残留导致启动失败
ip link del tun0 && /etc/init.d/sing-box start

# bypass 路由丢失
ip rule add to SS的IP/32 table main priority 8999

# 完全重装
sh /root/deploy.sh

========================================
补充:速查配置

========================================
透明代理 速查手册 (MIPS / 旁路由 br-wan)

【程序与配置】

sing-box /usr/local/bin/sing-box (MIPS softfloat)
→ 配置文件 /etc/sing-box/config.json
→ geo规则 /etc/sing-box/geoip-cn.srs (每日更新)
/etc/sing-box/geosite-cn.srs

dnsmasq 由 init 脚本启动
→ 配置文件 /etc/dnsmasq-proxy.conf (每次启动自动生成)

nftables sing-box TUN auto_route 自动管理

【当前 dnsmasq 配置 (由 init 动态生成)】

listen-address=127.0.0.1
listen-address=172.20.6.217
bind-interfaces
server=10.10.10.2
server=/yview.cn/172.20.2.113
cache-size=2048
no-resolv
filter-AAAA

【服务控制】 /etc/init.d/sing-box {start|stop|restart|status}

换IP后: /etc/init.d/sing-box restart (自动检测新IP)

【查看配置】

cat /etc/sing-box/config.json
cat /etc/dnsmasq-proxy.conf
cat /etc/init.d/sing-box

【查看日志】

cat /tmp/sing-box.log

【更换 SS 节点】

  1. vi /etc/sing-box/config.json 改 server/port/password/method
  2. vi /etc/init.d/sing-box 改旧 SS IP → 新 IP
  3. /etc/init.d/sing-box restart

【原理:一次完整请求的数据流】

以客户端访问 www.google.com 为例:

  1. DNS 解析
    客户端 → dnsmasq (172.20.6.217:53)
    → 转发到 10.10.10.2 (sing-box DNS模块)
    → geosite-cn 不命中 “google.com
    → 走 dns-remote: tcp://8.8.8.8 通过 SS 隧道查询
    → 返回真实海外 IP (142.251.x.x)

  2. TCP 连接
    客户端 → br-wan → 路由决策:
    geoip-cn 不命中, 非私有IP
    → 路由到 tun0 (10.10.10.1)
    → sing-box 处理: sniff域名, 查规则, final=ss-out
    → 加密后发往 SS 服务器

  3. SS 服务器出站
    sing-box 自身到 SS 的连接通过 bypass 路由(ip rule 8999)
    直连 eth0, 不再走 TUN, 避免路由回路

  4. 回程
    Google → SS服务器 → 加密隧道 → br-wan → tun0 → 客户端

如果访问 www.baidu.com:
DNS → geosite-cn 命中 → dns-direct: 223.5.5.5 直连解析 → 国内IP
TCP → geoip-cn 命中 → direct 直连出站, 不走 SS

【geosite-cn 的局限性】

geosite-cn 不是 100% 完备的, 存在三类误差:

  1. 漏网: 新上线的大陆网站未被收录
    后果: DNS 走 8.8.8.8 解析 → 可能拿到海外 CDN IP
    TCP 走 SS 出口绕一圈回国内 → 变慢(200ms 替代 10ms)

  2. 误判: 国外域名被标为 cn
    后果: DNS 走 223.5.5.5 直连 → 被 GFW 污染返回假 IP
    TCP 连到假 IP → 打不开

  3. CDN 混淆: 同一个域名国内外都有节点(如百度)
    收录在 geosite-cn 中, 走直连 → 正确

实际影响:

  • 主流网站(百度/淘宝/微博): 100% 正常
  • Google/YouTube 等已知被墙: 100% 正常(不会被误收录)
  • 冷门国内网站: 大概率正常, 小概率绕路变慢
  • 冷门国外网站: 大概率正常, 极小概率被误判走直连打不开

【server=/域名/DNS 配置指南】

当某个域名行为不正常时(太慢或打不开), 用 server=/域名/DNS 手动指定
它的解析路径。这个配置写在 /etc/init.d/sing-box 的 dnsmasq 配置段。

永久生效: vi /etc/init.d/sing-box, 找到 server=10.10.10.2 附近, 加一行
临时测试: vi /etc/dnsmasq-proxy.conf, 改完执行 killall dnsmasq && dnsmasq -C /etc/dnsmasq-proxy.conf

三种场景的写法:

场景1: 内网域名 (只有内网 DNS 认识)
原因: 公网 DNS 不认识, 必须问内网 DNS
写法: server=/yview.cn/172.20.2.113
server=/corp.local/172.20.2.113

场景2: 国内新网站/冷网站 (geosite-cn 漏了, 默认走代理变慢)
原因: sing-box DNS 用 8.8.8.8 解析, 拿到海外 IP, 绕路
修复: 指定走国内 DNS 直连解析, 拿到国内 IP 就近访问
写法: server=/新网站.cn/223.5.5.5

场景3: 国外新网站/冷网站 (geosite-cn 误判为 cn, 被污染)
原因: sing-box DNS 用 223.5.5.5 直连解析, 被 GFW 污染回假 IP
修复: 指定走 10.10.10.2 (sing-box DNS 重新判断),
由于不在 geosite-cn 列表里, 会 fallback 到 8.8.8.8 via SS
写法: server=/新网站.io/10.10.10.2

场景3备选: 如果 10.10.10.2 仍然返回错误(geosite-cn 仍在误判),
直接指定 8.8.8.8, 该 IP 不在 bypass 列表里所以会走 SS 隧道
写法: server=/新网站.io/8.8.8.8

【客户端设置】
网关: 172.20.6.217
DNS: 172.20.6.217

【文件清单】 /root/
deploy.sh 部署脚本
troubleshoot.sh 诊断脚本
transparent-proxy-deploy.md 详细文档
(已合并入本文档)

部署脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/bin/sh
# MIPS (ramips/mt7621) 透明代理一键部署
# 旁路由模式, 流量进出 br-wan

SS_SERVER="XX.XX.XX.XX"
SS_PORT="XXX"
SS_PASSWORD="XXX"
SS_METHOD="XXXX"
SING_BOX_VER="1.11.9"
SING_ARCH="linux-mipsle-softfloat"
TUN_ADDR="10.10.10.1/30"
WAN_IF="br-wan"

set -e
echo "========================================"
echo " MIPS 透明代理部署"
echo " sing-box ${SING_BOX_VER} ${SING_ARCH}"
echo "========================================"

echo "[1/8] 内核模块..."
apk add kmod-tun kmod-nf-tproxy kmod-nft-tproxy 2>&1 | tail -1
modprobe tun nf_tproxy nft_tproxy 2>/dev/null

echo "[2/8] sing-box..."
mkdir -p /usr/local/bin /var/cache/sing-box /etc/sing-box
if [ -x /usr/local/bin/sing-box ]; then
echo " 已安装"
else
URL="https://github.com/SagerNet/sing-box/releases/download/v${SING_BOX_VER}/sing-box-${SING_BOX_VER}-${SING_ARCH}.tar.gz"
wget -q --timeout=30 -O /tmp/sb.tar.gz "$URL" || {
echo "下载失败! 手动下载 $URL"
exit 1
}
cd /tmp && tar xzf sb.tar.gz && cp sing-box-*/sing-box /usr/local/bin/
chmod 755 /usr/local/bin/sing-box
rm -rf /tmp/sb.tar.gz /tmp/sing-box-*
fi

echo "[3/8] geo 规则..."
for f in geoip-cn.srs geosite-cn.srs; do
repo="sing-geoip"; [ "$f" = "geosite-cn.srs" ] && repo="sing-geosite"
wget -q --timeout=15 -O /etc/sing-box/$f \
"https://raw.githubusercontent.com/SagerNet/${repo}/rule-set/${f}" 2>/dev/null
done

echo "[4/8] 配置..."
WAN_IP=$(ip addr show $WAN_IF | grep "inet " | awk '{print $2}' | cut -d/ -f1)
cat > /etc/sing-box/config.json << EOF
{"log":{"level":"info","timestamp":true},"dns":{"servers":[{"tag":"dns-direct","address":"223.5.5.5","detour":"direct"},{"tag":"dns-remote","address":"tcp://8.8.8.8","detour":"ss-out"}],"rules":[{"rule_set":"geosite-cn","server":"dns-direct"}],"final":"dns-remote","strategy":"ipv4_only"},"inbounds":[{"type":"tun","tag":"tun-in","interface_name":"tun0","mtu":1500,"address":["${TUN_ADDR}"],"auto_route":true,"strict_route":true,"sniff":true,"sniff_override_destination":false}],"outbounds":[{"type":"dns","tag":"dns-out"},{"type":"shadowsocks","tag":"ss-out","server":"${SS_SERVER}","server_port":${SS_PORT},"password":"${SS_PASSWORD}","method":"${SS_METHOD}"},{"type":"direct","tag":"direct"}],"route":{"rule_set":[{"tag":"geoip-cn","type":"local","format":"binary","path":"/etc/sing-box/geoip-cn.srs"},{"tag":"geosite-cn","type":"local","format":"binary","path":"/etc/sing-box/geosite-cn.srs"}],"rules":[{"protocol":"dns","outbound":"dns-out"},{"ip_is_private":true,"outbound":"direct"},{"rule_set":"geoip-cn","outbound":"direct"},{"rule_set":"geosite-cn","outbound":"direct"}],"final":"ss-out","auto_detect_interface":true},"experimental":{"cache_file":{"enabled":false}}}
EOF

echo "[5/8] init 脚本..."
cat > /etc/init.d/sing-box << 'INITEOF'
#!/bin/sh /etc/rc.common
START=95
USE_PROCD=1
PROG=/usr/local/bin/sing-box
CONFIG=/etc/sing-box/config.json
boot() { start; }
start_service() {
mkdir -p /var/cache/sing-box
ip link del tun0 2>/dev/null
sleep 1
WAN_IP=$(ip addr show br-wan | grep "inet " | awk '{print $2}' | cut -d/ -f1)
cat > /etc/dnsmasq-proxy.conf << EOF
listen-address=127.0.0.1
listen-address=${WAN_IP}
bind-interfaces
server=10.10.10.2
server=/yview.cn/172.20.2.113
cache-size=2048
no-resolv
filter-AAAA
EOF
for ip in XX.XX.XX.XX 223.5.5.5 172.20.2.113; do
ip rule add to ${ip}/32 table main priority 8999 2>/dev/null || true
done
killall dnsmasq 2>/dev/null; sleep 1
dnsmasq -C /etc/dnsmasq-proxy.conf
procd_open_instance
procd_set_param command /bin/sh -c "$PROG run -c $CONFIG > /tmp/sing-box.log 2>&1"
procd_set_param respawn
procd_close_instance
}
stop_service() { killall sing-box dnsmasq; ip link del tun0 2>/dev/null; }
INITEOF
chmod 755 /etc/init.d/sing-box

echo "[6/8] geo 更新..."
cat > /usr/local/bin/update-geo.sh << 'SCRIPT'
#!/bin/sh
for f in geoip-cn.srs geosite-cn.srs; do
repo="sing-geoip"; [ "$f" = "geosite-cn.srs" ] && repo="sing-geosite"
wget -q --timeout=15 -O "/etc/sing-box/$f.new" \
"https://raw.githubusercontent.com/SagerNet/${repo}/rule-set/${f}" && \
[ -s "/etc/sing-box/$f.new" ] && mv "/etc/sing-box/$f.new" "/etc/sing-box/$f"
done
/etc/init.d/sing-box restart 2>/dev/null
SCRIPT
chmod 755 /usr/local/bin/update-geo.sh
grep -q update-geo /etc/crontabs/root 2>/dev/null || \
echo "17 03 * * * /usr/local/bin/update-geo.sh" >> /etc/crontabs/root

echo "[7/8] IP 转发..."
echo 1 > /proc/sys/net/ipv4/ip_forward
grep -q ip_forward /etc/sysctl.conf 2>/dev/null || echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

echo "[8/8] 启动..."
/etc/init.d/dnsmasq disable 2>/dev/null; rm -f /etc/rc.d/S*dnsmasq*
/etc/init.d/sing-box enable 2>/dev/null
killall sing-box dnsmasq 2>/dev/null; ip link del tun0 2>/dev/null; sleep 1
/etc/init.d/sing-box start 2>&1; sleep 3

echo ""
echo "========================================"
echo " 部署完成"
echo " sing-box: $(pgrep sing-box|xargs) dnsmasq: $(pgrep dnsmasq|xargs)"
echo " TUN: $(ip addr show tun0 2>/dev/null|grep 'inet '|awk '{print $2}')"
echo " 客户端: 网关+DNS = $(ip addr show br-wan|grep 'inet '|awk '{print $2}'|cut -d/ -f1)"
echo "========================================"

问题诊断脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/bin/sh
#=============================================================================
# 透明代理故障诊断脚本
# 用法: sh /root/troubleshoot.sh
#=============================================================================

echo "========================================"
echo " 透明代理诊断报告"
echo " 时间: $(date)"
echo "========================================"

ERRORS=0

check() {
local desc="$1"
local cmd="$2"
printf "%-50s" " $desc..."
if eval "$cmd" > /dev/null 2>&1; then
echo "✓"
else
echo "✗ 故障"
ERRORS=$((ERRORS + 1))
fi
}

info() {
local desc="$1"
local cmd="$2"
echo ""
echo "--- $desc ---"
eval "$cmd" 2>&1
}

# ============================================================
# 1. 基础连通性
# ============================================================
echo ""
echo "【1】基础连通性"

check "本机默认路由" "ip route show | grep -q default"
info "路由表" "ip route show | grep -E 'default|eth0|tun0'"

check "网关可达" "ping -c 1 -W 2 172.20.6.217 > /dev/null 2>&1"
check "DNS 223.5.5.5 可达" "ping -c 1 -W 2 223.5.5.5 > /dev/null 2>&1"

# ============================================================
# 2. 进程状态
# ============================================================
echo ""
echo "【2】进程状态"

check "sing-box 运行中" "pgrep sing-box > /dev/null 2>&1"
check "dnsmasq 运行中" "pgrep dnsmasq > /dev/null 2>&1"

info "进程详情" "ps | grep -E 'sing-box|dnsmasq' | grep -v grep"

# ============================================================
# 3. 端口监听
# ============================================================
echo ""
echo "【3】端口监听"

check "DNS 53 端口" "netstat -tlnp 2>/dev/null | grep -q ':53 '"
info "监听端口" "netstat -tlnp 2>/dev/null | grep -E ':53 |:41643'"

# ============================================================
# 4. TUN 接口
# ============================================================
echo ""
echo "【4】TUN 接口"

check "tun0 存在" "ip link show tun0 > /dev/null 2>&1"
info "TUN 详情" "ip addr show tun0 2>&1"

# ============================================================
# 5. 路由规则
# ============================================================
echo ""
echo "【5】路由规则"

check "SS 绕过规则" "ip rule show 2>&1 | grep -q 'XX.XX.XX.XX'"
check "DNS 绕过规则" "ip rule show 2>&1 | grep -q '223.5.5.5'"

info "策略路由" "ip rule show 2>&1 | grep -E '8999|2022|2023|2024'"

# ============================================================
# 6. DNS 解析测试
# ============================================================
echo ""
echo "【6】DNS 解析"

info "百度 (应返回国内IP)" "nslookup www.baidu.com 127.0.0.1 2>&1 | grep -E 'Address: [0-9]+\.'"
info "Google (应返回海外IP, 非CDN)" "nslookup www.google.com 127.0.0.1 2>&1 | grep -E 'Address: [0-9]+\.'"

# ============================================================
# 7. sing-box 日志
# ============================================================
echo ""
echo "【7】sing-box 日志 (最近 20 行)"

# procd 管理的服务日志通过 logread 查看
if which logread > /dev/null 2>&1; then
logread -e sing-box | tail -20
elif [ -f /tmp/sing-box.log ]; then
tail -20 /tmp/sing-box.log
else
echo " 无日志 (检查: logread -e sing-box)"
fi

# 错误统计
echo ""
info "错误/超时统计" "logread -e sing-box 2>/dev/null | grep -iE 'error|fatal|timeout|deadline' | tail -10 || echo '无错误'"

# ============================================================
# 8. 连接测试
# ============================================================
echo ""
echo "【8】TCP 连接测试"

# ============================================================
# 9. IP 转发
# ============================================================
echo ""
echo "【9】IP 转发"

info "转发状态" "cat /proc/sys/net/ipv4/ip_forward"

# ============================================================
# 10. 连接跟踪 (SS 连接)
# ============================================================
echo ""
echo "【10】Shadowsocks 连接"

info "到 SS 服务器的连接" "cat /proc/net/nf_conntrack 2>/dev/null | grep 'XX.XX.XX.XX' | tail -5"

# ============================================================
# 11. 磁盘空间
# ============================================================
echo ""
echo "【11】磁盘空间"

info "空间" "df -h /"

# ============================================================
# 汇总
# ============================================================
echo ""
echo "========================================"
if [ $ERRORS -eq 0 ]; then
echo " 诊断结果: 全部通过 ✓"
else
echo " 诊断结果: $ERRORS 项异常, 请检查上方 ✗ 项"
fi

echo ""
echo " 快速修复命令:"
echo " /etc/init.d/sing-box restart"
echo ""
echo " 日志位置:"
echo " /tmp/sing-box.log sing-box 日志"
echo " /var/log/dnsmasq.log DNS 日志"
echo ""
echo " 配置文件:"
echo " /etc/sing-box/config.json"
echo " /etc/dnsmasq.conf"
echo " /etc/init.d/sing-box"
echo "========================================"