OpenWrt 透明代理部署文档

概述

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

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

已部署验证的机型:

  • MIPS ramips/mt7621 小米路由器 3G(旁路由模式,br-wan)

架构原理

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 自身的出站连接不会被 TUN 再次拦截,避免路由回路。

环境要求

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

文件传输

OpenWrt 25 没有 SFTP/SCP,使用 SSH PIPE 传输:

1
2
3
4
5
# 传输单个文件
ssh root@<router-ip> 'cat /root/deploy.sh' > deploy.sh

# 多文件打包传输
ssh root@<router-ip> 'tar czf - -C /root deploy.sh troubleshoot.sh' > backup.tar.gz

部署步骤

第一步:确认系统信息

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

第二步:安装内核模块

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

MIPS 平台通常能直连 OpenWrt 仓库,直接 apk add 即可。

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

从 GitHub 下载 MIPS 版本:

1
2
# 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 版本。linux-mipsle(硬浮点)在 1004Kc 核心上会报 Illegal instruction

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

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

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

wget -O /etc/sing-box/geoip-cn.srs \
https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs

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 已不支持。

第五步:写入 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": "<SS-SERVER-IP>",
"server_port": <SS-PORT>,
"password": "<SS-PASSWORD>",
"method": "aes-256-gcm"
},
{"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 代理回程有问题)
  • 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 重启后会被清空,启用会导致启动失败)

第六步:配置 DNS(dnsmasq)

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

1
2
3
4
5
6
7
8
listen-address=127.0.0.1
listen-address=<LAN-IP>
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 模块
  • server=/yview.cn/172.20.2.113 — 内网域名后缀转发(如公司内部系统)
  • bind-interfaces — 必须启用,否则 dnsmasq 只在 lo 上监听
  • filter-AAAA — 过滤 IPv6 DNS 记录
  • no-resolv — 不从 /etc/resolv.conf 读取上游 DNS

第七步:写入 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
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
#!/bin/sh /etc/rc.common

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

# ====== SS 配置 (改这里, restart 生效) ======
SS_SERVER="<your-ss-domain-or-ip>"
SS_PORT="<port>"
SS_PASSWORD="<password>"
SS_METHOD="aes-256-gcm"
# ==========================================

boot() { start; }

start_service() {
# 优先解析 SS 域名,失败则干净退出,等下回 start
SS_IP="$SS_SERVER"
case "$SS_SERVER" in
[0-9]*.[0-9]*.[0-9]*.[0-9]*) ;;
*) SS_IP=$(nslookup "$SS_SERVER" 223.5.5.5 2>/dev/null | grep "Address: [0-9]" | awk "{print \$NF}" | head -1) ;;
esac
case "$SS_IP" in
[0-9]*.[0-9]*.[0-9]*.[0-9]*) ;;
*) echo "$(date) start aborted: nslookup $SS_SERVER failed" >> /tmp/sing-box.log; return 1 ;;
esac

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)
DNS_IPS=$(grep "server=/" $DNSMASQ_CONF | awk -F/ "{print \$NF}" | sort -u)

sed -i "/\"server\":/{ /dns/! s|\"server\": *\"[^\"]*\"|\"server\": \"$SS_IP\"| }" $CONFIG
sed -i "s|\"server_port\": [0-9]*|\"server_port\": $SS_PORT|" $CONFIG
sed -i "s|\"password\": *\"[^\"]*\"|\"password\": \"$SS_PASSWORD\"|" $CONFIG
sed -i "s|\"method\": *\"[^\"]*\"|\"method\": \"$SS_METHOD\"|" $CONFIG

sed -i "/^listen-address=/d" $DNSMASQ_CONF
sed -i "1i listen-address=127.0.0.1\nlisten-address=${WAN_IP}" $DNSMASQ_CONF
sed -i "s/^server=[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}$/server=10.10.10.2/" $DNSMASQ_CONF

while ip rule del priority 8999 2>/dev/null; do :; done
for ip in 223.5.5.5 ${SS_IP} ${DNS_IPS}; do
[ -n "$ip" ] && ip rule add to ${ip}/32 table main priority 8999
done

killall dnsmasq 2>/dev/null
sleep 1
dnsmasq -C $DNSMASQ_CONF

rm -f /etc/resolv.conf
echo "nameserver 127.0.0.1" > /etc/resolv.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() {
ip link del tun0 2>/dev/null
while ip rule del priority 8999 2>/dev/null; do :; done

sed -i "s/^server=10\.10\.10\.2$/server=223.5.5.5/" $DNSMASQ_CONF
killall dnsmasq 2>/dev/null
sleep 1
dnsmasq -C $DNSMASQ_CONF

rm -f /etc/resolv.conf
echo "nameserver 127.0.0.1" > /etc/resolv.conf
}

赋予执行权限并启用:

1
2
3
4
chmod 755 /etc/init.d/sing-box
/etc/init.d/sing-box enable
/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" 2>/dev/null && \
[ -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" 2>/dev/null && \
[ -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
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

客户端设置

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

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

Windows 在网络适配器属性中设置。注意 Chrome/Edge 默认开启 Secure DNS (DoH),需关掉:chrome://settings/security → 使用安全 DNS → 关闭。

日常维护

1
2
3
4
/etc/init.d/sing-box start
/etc/init.d/sing-box stop
/etc/init.d/sing-box restart
cat /tmp/sing-box.log # 查看日志

更换 SS 节点:编辑 /etc/init.d/sing-box 顶部的 SS_SERVER/SS_PORT/SS_PASSWORD/SS_METHOD,然后 restart。init 脚本会自动解析域名并注入 config.json、更新 bypass 路由。

更换本机 IP 后:直接 restart 即可,init 脚本自动检测新 IP。

部署脚本(一键)

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
#!/bin/sh
# MIPS (ramips/mt7621) 透明代理一键部署
# 旁路由模式, 流量进出 br-wan

SS_SERVER="<your-ss-server>"
SS_PORT="<port>"
SS_PASSWORD="<password>"
SS_METHOD="aes-256-gcm"
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 脚本..."
# 使用第七步中的完整 init 脚本,不再内嵌重复

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
#!/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
}

echo ""
echo "【1】基础连通性"
check "本机默认路由" "ip route show | grep -q default"
info "路由表" "ip route show | grep -E 'default|br-wan|tun0'"

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"

echo ""
echo "【3】端口监听"
check "DNS 53 端口" "netstat -tlnp 2>/dev/null | grep -q ':53 '"

echo ""
echo "【4】TUN 接口"
check "tun0 存在" "ip link show tun0 > /dev/null 2>&1"
info "TUN 详情" "ip addr show tun0 2>&1"

echo ""
echo "【5】路由规则"
check "bypass 规则存在" "ip rule show 2>&1 | grep -q '8999'"
info "策略路由" "ip rule show 2>&1 | grep -E '8999|2022|2023|2024'"

echo ""
echo "【6】DNS 解析"
info "百度 (应返回国内IP)" "nslookup www.baidu.com 127.0.0.1 2>&1 | grep -E 'Address: [0-9]+\.'"
info "Google (应返回海外IP)" "nslookup www.google.com 127.0.0.1 2>&1 | grep -E 'Address: [0-9]+\.'"

echo ""
echo "【7】sing-box 日志 (最近 20 行)"
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
fi

echo ""
echo "【8】IP 转发"
info "转发状态" "cat /proc/sys/net/ipv4/ip_forward"

echo ""
echo "【9】磁盘空间"
info "空间" "df -h /"

echo ""
echo "========================================"
if [ $ERRORS -eq 0 ]; then
echo " 诊断结果: 全部通过 ✓"
else
echo " 诊断结果: $ERRORS 项异常, 请检查上方 ✗ 项"
fi
echo ""
echo " 快速修复: /etc/init.d/sing-box restart"
echo "========================================"

遇到的坑

sing-box 二进制兼容性

MIPS 必须用 softfloat 版本。linux-mipsle(硬浮点)在 1004Kc 核心上会 Illegal instruction

TUN MTU

TUN 接口默认 MTU 9000,底层物理网卡只有 MTU 1500,大包分片失败。config.json 中显式设为 1500。

cache_file 路径

sing-box 默认启用 cache_file。OpenWrt 的 /var/cache 重启后被清空,目录不存在会导致启动 FATAL。设为 false 或在 init 中 mkdir -p。

TUN 接口残留

sing-box 异常退出后 tun0 可能残留,下次启动报 device or resource busy。start_service 开头执行 ip link del tun0 2>/dev/null

auto_route 与 strict_route

两者必须同时启用,否则 SS 服务器连接会形成路由回路。

bypass 路由

即使有 strict_route,手动添加 bypass 路由(priority 8999)最稳妥。

DNS 的 UDP 问题

UDP DNS 通过 TUN/SS 代理后回程响应无法正确送达。用 tcp://8.8.8.8

内网域名解析

内网域名(如公司 OA)在 dnsmasq 中配置 server=/<domain>/<internal-dns-ip>

dnsmasq 的 rebind 保护

OpenWrt dnsmasq 默认 stop-dns-rebind,会丢弃私有 IP。内网 DNS 返回私有 IP 时需关闭:option rebind_protection '0'

dnsmasq 不自启

系统 dnsmasq 需 UCI 配置才启动。我们的方案是接管 dnsmasq,需禁用系统 init 避免冲突。

sing-box v2ray 格式不支持

1.11.x 必须用 .srs 格式规则集,不要用 v2fly 的 22MB geoip.dat。

MIPS 内存

sing-box 在 MIPS 上占用约 55MB。小米路由器 3G(256MB)完全够用。128MB 以下设备需留意。

旁路由网口配置

MIPS 旁路由网口名是 br-wan(x86 物理机 eth0)。ip addr show 查看。

Windows Chrome/Edge 无法上网

Chrome/Edge 默认 Secure DNS (DoH) 绕过系统 DNS。sing-box stop 后 DoH 直连被墙 → 浏览器废掉。关闭:chrome://settings/security → 使用安全 DNS → 关闭。

init 脚本中不要用 killall sing-box

procd 管理的 shell 进程名也是 sing-box,killall 会误杀导致 stop_service 被中断。procd 负责进程管理,stop_service 只做清理。

init 脚本设计要点

stop_service 原则:保留 DNS

旧版直接 killall sing-box dnsmasq,所有客户端 DNS 全断。新版:procd 杀 sing-box,dnsmasq 保留并切上游到 223.5.5.5

stop 后效果:

  • 国内 DNS:dnsmasq → 223.5.5.5 直连 ✓
  • 海外 DNS:被 GFW 污染(无 SS 隧道,预期行为)
  • 内网 DNS:server=/<domain>/<ip> 不受影响 ✓

start_service DNS 恢复

start 时 sed 把裸 IP 上游(无论之前被 stop 改成什么)恢复为 server=10.10.10.2。用 ^server=[0-9.]\+$ 匹配裸 IP,不会误碰 server=/<domain>/<ip>

SS 域名解析前置

SS_SERVER 填域名时,nslookup 在任何 setup 之前完成。失败则 return 1 干净退出,不搞半拉子状态。日志写入 /tmp/sing-box.log。

路由器自身 DNS

/etc/resolv.conf 设为 nameserver 127.0.0.1,路由器自身也走 dnsmasq。WAN 是静态 IP(option proto ‘static’),netifd 不会中途 DHCP 覆写。无环路风险:sing-box 出站只用 IP。

速查手册

程序与配置

1
2
3
4
5
6
7
8
sing-box           /usr/local/bin/sing-box  (MIPS softfloat)
→ 配置文件 /etc/sing-box/config.json
→ geo规则 /etc/sing-box/geoip-cn.srs / geosite-cn.srs

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

nftables sing-box TUN auto_route 自动管理

dnsmasq 参考配置

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

服务控制

1
2
/etc/init.d/sing-box {start|stop|restart|status}
换IP后: /etc/init.d/sing-box restart (自动检测新IP)

查看配置

1
2
3
4
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/init.d/sing-box — 改顶部 SS_SERVER/SS_PORT/SS_PASSWORD/SS_METHOD
  2. /etc/init.d/sing-box restart — init 脚本自动解析域名、注入 config.json、更新 bypass 路由

一次完整请求的数据流(以 Google 为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. DNS 解析
客户端 → dnsmasq (LAN_IP:53)
→ 转发到 10.10.10.2 (sing-box DNS模块)
→ geosite-cn 不命中 "google.com"
→ 走 dns-remote: tcp://8.8.8.8 通过 SS 隧道查询
→ 返回真实海外 IP

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

3. SS 出站
sing-box 自身到 SS 的连接通过 bypass 路由(priority 8999) 直连

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

访问百度:DNS geosite-cn 命中 → 223.5.5.5 直连;TCP geoip-cn 命中 → direct 直连。

geosite-cn 的局限性

  1. 漏网:新上线大陆网站未被收录 → 走代理绕路变慢
  2. 误判:国外域名被标为 cn → 走直连被污染
  3. CDN 混淆:同一域名国内外都有节点 → 收录在 cn 中走直连,正确

实际影响:主流网站 100% 正常,冷门网站小概率异常。

server=/域名/DNS 配置指南

写在 /etc/init.d/sing-box 的 dnsmasq 配置段。

场景 1:内网域名(只有内网 DNS 认识)

公网 DNS 不认识内网域名,必须问内网 DNS:

1
2
server=/yview.cn/172.20.2.113
server=/oa.internal/172.20.2.113

场景 2:国内新网站变慢(geosite-cn 漏了,默认走了代理)

sing-box DNS 用 8.8.8.8 解析,拿到海外 CDN IP,绕路。指定走国内 DNS 直连:

1
server=/xinwangzhan.cn/223.5.5.5

场景 3:国外新网站打不开(geosite-cn 误判为 cn,被污染)

sing-box DNS 用 223.5.5.5 直连解析,被 GFW 污染。指定走 10.10.10.2 重新判断(不命中 geosite-cn,fallback 到 8.8.8.8 via SS):

1
server=/newsite.io/10.10.10.2

如果 10.10.10.2 仍返回错误(geosite-cn 继续误判),直接指定 8.8.8.8(其不在 bypass 列表,走 SS 隧道):

1
server=/newsite.io/8.8.8.8

文件清单(路由器 /root/)

1
troubleshoot.sh              诊断脚本

文件清单(项目目录)

1
2
3
4
5
6
7
config.json                  sing-box 配置
sing-box init 启动脚本
dnsmasq-proxy.conf DNS 配置
deploy.sh 部署脚本
troubleshoot.sh 诊断脚本
update-geo.sh geo 更新脚本
transparent-proxy-deploy.md 详细文档