sing-box 透明代理配置。本文中的配置是示例,适用于 sing-box 1.12 及更高版本,不适用于 ipv6 网络。如果要使用请根据实际情况自己修改。
环境 #
Debian系统:专门用来做透明代理的网关服务器,或者叫旁路网关,网络需要设置为静态:
## /etc/network/interfaces
iface ens16 inet static
address 10.0.0.20/24
gateway 10.0.0.1
sing-box:运行在网关服务器上做DNS服务、规则分流和代理。
配置 #
注意替换里面的路径。
路径/path/to/sing-box/nftables.sh
#!/bin/bash
if [ $# != 1 ]
then
echo "Use $(basename "$0") <set|clear>"
exit 1;
fi
INTERFACE=$(ip route show default | awk '/default/ {print $5}')
TPROXY_PORT=7895 ## 和 sing-box 中定义的一致
ROUTING_MARK=666 ## 和 sing-box 中定义的一致
PROXY_FWMARK=1
PROXY_ROUTE_TABLE=100
# https://en.wikipedia.org/wiki/Reserved_IP_addresses
ReservedIP4='{ 127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 100.64.0.0/10, 169.254.0.0/16, 172.16.0.0/12, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32 }'
CustomBypassIP='{ 192.168.0.0/16 }' ## 添加其他不想透明代理的地址
nftrule=$(cat <<EOF
table inet sing-box {
chain prerouting_tproxy {
type filter hook prerouting priority mangle; policy accept;
meta l4proto { tcp, udp } th dport 53 tproxy to :$TPROXY_PORT meta mark set $PROXY_FWMARK accept comment "DNS劫持"
ip daddr $CustomBypassIP accept comment "绕过某些地址"
meta l4proto { tcp, udp } th dport { 137, 138, 139, 445} accept comment "绕过smb流量"
meta l4proto { tcp, udp } th dport 3389 accept comment "绕过rdp流量"
udp dport 123 accept comment "绕过ntp流量"
fib daddr type local meta l4proto { tcp, udp } th dport $TPROXY_PORT reject with icmpx type host-unreachable comment "直接访问tproxy端口拒绝, 防止回环"
fib daddr type local accept comment "本机绕过"
ip daddr $ReservedIP4 accept comment "保留地址绕过"
meta l4proto tcp socket transparent 1 meta mark set $PROXY_FWMARK accept comment "绕过已经建立的透明代理"
meta l4proto { tcp, udp } tproxy to :$TPROXY_PORT meta mark set $PROXY_FWMARK comment "其他流量透明代理"
}
chain output_tproxy {
type route hook output priority mangle; policy accept;
oifname != $INTERFACE accept comment "绕过本机内部通信的流量(接口lo)"
meta mark $ROUTING_MARK accept comment "绕过本机sing-box发出的流量"
meta l4proto { tcp, udp } th dport 53 meta mark set $PROXY_FWMARK accept comment "DNS重路由到prerouting"
meta l4proto { tcp, udp } th dport { 137, 138, 139, 445} accept comment "绕过smb流量"
meta l4proto { tcp, udp } th dport 3389 accept comment "绕过rdp流量"
udp dport 123 accept comment "绕过ntp流量"
ip daddr $CustomBypassIP accept comment "绕过某些地址"
fib daddr type local accept comment "本机绕过"
ip daddr $ReservedIP4 accept comment "保留地址绕过"
meta l4proto { tcp, udp } meta mark set $PROXY_FWMARK comment "其他流量重路由到prerouting"
}
}
EOF
)
function ClearRules()
{
IPRULE=$(ip rule show | grep $PROXY_ROUTE_TABLE)
if [ -n "$IPRULE" ]
then
ip -f inet rule del fwmark $PROXY_FWMARK lookup $PROXY_ROUTE_TABLE
ip -f inet route flush table $PROXY_ROUTE_TABLE
fi
nft flush ruleset
echo "clear nftables"
}
function SetRules()
{
ip -f inet rule add fwmark $PROXY_FWMARK lookup $PROXY_ROUTE_TABLE
ip -f inet route add local default dev lo table $PROXY_ROUTE_TABLE
sysctl -w net.ipv4.ip_forward=1 > /dev/null
echo "$nftrule" | nft -f -
echo "set nftables"
}
if [ $1 = 'set' ]
then
ClearRules
SetRules
elif [ $1 = 'clear' ]
then
ClearRules
fi
路径/etc/systemd/system/sing-box.service
[Unit]
Description=sing-box tproxy daemon.
After=network.target nss-lookup.target network-online.target
[Service]
Type=simple
LimitNOFILE=1000000
ExecStartPre=/bin/sleep 2
ExecStart=/path/to/sing-box/sing-box run -c /path/to/sing-box/sing-box-tproxy.json -D /path/to/sing-box/data
ExecStartPost=bash /path/to/sing-box/nftables.sh set
ExecStop=bash /path/to/sing-box/nftables.sh clear
[Install]
WantedBy=multi-user.target
客户端配置文件,路径/path/to/sing-box/sing-box-tproxy.json
{
"log": {
"disabled": false,
"level": "warn",
"output": "box.log",
"timestamp": true
},
"dns": {
"servers": [
{
"tag": "tencentDoH",
"type": "https",
"server": "1.12.12.12"
},
{
"tag": "cloudflareDoH",
"type": "https",
"server": "1.1.1.1",
"detour": "PROXY"
},
{
"tag": "localDNS",
"type": "local"
},
{
"tag": "fakeipDNS",
"type": "fakeip",
"inet4_range": "198.18.0.0/15"
}
],
"rules": [
{
"invert": true,
"protocol": "dns",
"action": "reject",
"method": "drop"
},
{
"query_type": "AAAA",
"action": "predefined",
"rcode": "NOERROR"
},
{
"query_type": "A",
"rule_set": "geosite-private",
"server": "localDNS"
},
{
"rule_set": "geosite-private",
"action": "predefined",
"rcode": "NOERROR"
},
{
"clash_mode": "direct",
"server": "tencentDoH"
},
{
"rule_set": [
"geosite-geolocation-cn",
"geosite-win-update",
"geosite-apple"
],
"server": "tencentDoH"
},
{
"query_type": "A",
"rule_set": [
"geosite-google",
"geosite-github",
"geosite-telegram",
"geosite-openai",
"geosite-claude",
"geosite-gemini",
"geosite-gfw"
],
"server": "fakeipDNS",
"rewrite_ttl": 10
},
{
"query_type": [
"A",
"CNAME",
"SVCB",
"HTTPS",
"SRV",
"PTR"
],
"server": "cloudflareDoH"
}
],
"final": "tencentDoH",
"strategy": "ipv4_only",
"disable_cache": false,
"disable_expire": false,
"independent_cache": true,
"reverse_mapping": false
},
"inbounds": [
{
"tag": "TPROXY-IN",
"type": "tproxy",
"listen": "0.0.0.0",
"listen_port": 7895
},
{
"tag": "MIXED-MAIN-IN",
"type": "mixed",
"listen": "0.0.0.0",
"listen_port": 7900
}
],
"outbounds": [
{
"tag": "PROXY",
"type": "selector",
"outbounds": [
"🇭🇰 IEPL-AUTO",
"🇸🇬 ANYTLS"
],
"default": "🇭🇰 IEPL-AUTO",
"interrupt_exist_connections": true
},
{
"tag": "FINAL",
"type": "selector",
"outbounds": [
"PROXY",
"DIRECT"
],
"default": "PROXY",
"interrupt_exist_connections": true
},
{
"tag": "APPLE",
"type": "selector",
"outbounds": [
"PROXY",
"DIRECT",
"🇭🇰 IEPL-AUTO",
"🇸🇬 ANYTLS"
],
"default": "DIRECT",
"interrupt_exist_connections": true
},
{
"tag": "AI",
"type": "selector",
"outbounds": [
"PROXY",
"DIRECT",
"🇭🇰 IEPL-AUTO",
"🇸🇬 ANYTLS"
],
"default": "🇸🇬 ANYTLS",
"interrupt_exist_connections": true
},
{
"tag": "TELEGRAM",
"type": "selector",
"outbounds": [
"🇭🇰 IEPL-AUTO",
"🇸🇬 ANYTLS"
],
"default": "🇭🇰 IEPL-AUTO",
"interrupt_exist_connections": true
},
{
"tag": "🇭🇰 IEPL-A",
"type": "shadowsocks",
"server": "ss-a.exp.com",
"server_port": 6350,
"method": "2022-blake3-chacha20-poly1305",
"password": "xxxxxxxxxx",
"multiplex": {
"enabled": true,
"protocol": "h2mux",
"max_streams": 6,
"padding": true
}
},
{
"tag": "🇭🇰 IEPL-B",
"type": "shadowsocks",
"server": "ss-b.exp.com",
"server_port": 6351,
"method": "2022-blake3-chacha20-poly1305",
"password": "xxxxxxxxxx",
"multiplex": {
"enabled": true,
"protocol": "h2mux",
"max_streams": 6,
"padding": true
}
},
{
"tag": "🇭🇰 IEPL-AUTO",
"type": "urltest",
"outbounds": [
"🇭🇰 IEPL-B",
"🇭🇰 IEPL-A"
],
"interval": "1m",
"tolerance": 200,
"idle_timeout": "10m",
"url": "https://cp.cloudflare.com/generate_204",
"interrupt_exist_connections": true
},
{
"tag": "🇸🇬 ANYTLS",
"type": "anytls",
"server": "x.x.x.x",
"server_port": 443,
"password": "xxxxxxxxxx",
"min_idle_session": 2,
"tls": {
"enabled": true,
"server_name": "anytls.exp.com",
"utls": {
"enabled": true,
"fingerprint": "chrome"
}
}
},
{
"tag": "DIRECT",
"type": "direct"
}
],
"route": {
"rules": [
{
"action": "sniff"
},
{
"protocol": "dns",
"action": "hijack-dns"
},
{
"port": 53,
"action": "hijack-dns"
},
{
"rule_set": "geosite-private",
"outbound": "DIRECT"
},
{
"clash_mode": "direct",
"outbound": "DIRECT"
},
{
"clash_mode": "global",
"outbound": "PROXY"
},
{
"rule_set": "geosite-apple",
"outbound": "APPLE"
},
{
"rule_set": [
"geosite-openai",
"geosite-claude",
"geosite-gemini"
],
"outbound": "AI"
},
{
"rule_set": [
"geosite-telegram",
"geoip-telegram"
],
"outbound": "TELEGRAM"
},
{
"rule_set": [
"geosite-google",
"geosite-github",
"geosite-gfw"
],
"outbound": "PROXY"
},
{
"rule_set": [
"geoip-private",
"geoip-cn",
"geosite-geolocation-cn",
"geosite-win-update"
],
"outbound": "DIRECT"
}
],
"rule_set": [
{
"tag": "geosite-private",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/private.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-google",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/google.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-geolocation-cn",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/geolocation-cn.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-win-update",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/win-update.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-apple",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/apple.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-github",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/github.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geoip-telegram",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geoip/telegram.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-telegram",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/telegram.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-openai",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/openai.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-gfw",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/gfw.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geoip-private",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geoip/private.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geoip-cn",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geoip/cn.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-claude",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/claude.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
},
{
"tag": "geosite-gemini",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/fdxx/meta-rules-dat/refs/heads/sing/geo/geosite/gemini.json",
"download_detour": "🇸🇬 ANYTLS",
"update_interval": "999d"
}
],
"final": "FINAL",
"default_domain_resolver": "tencentDoH",
"auto_detect_interface": true,
"default_mark": 666
},
"experimental": {
"clash_api": {
"external_controller": "0.0.0.0:9191",
"external_ui": "yacd",
"external_ui_download_url": "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip",
"external_ui_download_detour": "🇸🇬 ANYTLS",
"secret": "xxx",
"default_mode": "rule"
}
}
}
服务端配置示例
{
"log": {
"disabled": false,
"level": "warn",
"output": "box.log",
"timestamp": true
},
"dns": {
"servers": [
{
"tag": "OUTDNS",
"type": "https",
"server": "1.1.1.1"
}
]
},
"inbounds": [
{
"type": "shadowsocks",
"tag": "ss-a",
"listen": "0.0.0.0",
"listen_port": 6350,
"method": "2022-blake3-chacha20-poly1305",
"password": "xxxxxxxxxx",
"multiplex": {
"enabled": true,
"padding": true
}
},
{
"type": "shadowsocks",
"tag": "ss-b",
"listen": "0.0.0.0",
"listen_port": 6351,
"method": "2022-blake3-chacha20-poly1305",
"password": "xxxxxxxxxx",
"multiplex": {
"enabled": true,
"padding": true
}
},
{
"type": "anytls",
"tag": "anytls-in",
"listen": "0.0.0.0",
"listen_port": 443,
"users": [
{
"name": "xxx",
"password": "xxxxxxxxxx"
}
],
"tls": {
"enabled": true,
"server_name": "anytls.exp.com",
"acme": {
"domain": "anytls.exp.com",
"email": "exp@gmail.com",
"provider": "letsencrypt",
"dns01_challenge": {
"provider": "cloudflare",
"api_token": "xxxxxxx"
}
}
}
}
],
"outbounds": [
{
"tag": "DIRECT",
"type": "direct",
"domain_resolver": {
"server": "OUTDNS",
"strategy": "ipv4_only"
}
}
]
}
路由和规则匹配 #
在透明代理环境中,程序在发起连接前首先需要知道目标IP地址,也就是DNS查询,拿到IP后再建立连接。这在sing-box路由中体现为2次入站:
- 第一次:nftables判断目标端口是否为53,透明代理入站sing-box,
route.rules中匹配DNS规则分流到DNS模块处理DNS查询。 - 第二次:程序拿着IP透明代理入站sing-box,路由和规则匹配,走代理或直连出站。
telegram等程序已经有目标IP的情况下,则没有DNS查询步骤,入站就是IP。
如果程序使用socks5或http代理入站,DNS查询通常是由代理服务器负责,此时入站是域名,DNS查询发生在出站的时候(本地直接出站或走代理到服务端出站)。
如果使用fakeIP呢?
如果DNS全返回fakeIP
- 对于需要走代理的连接,本地出站时是传域名到服务器重新进行DNS查询建立连接。
- 对于需要直连的连接,本地出站时也是域名,此时sing-box会使用
outbounds[].domain_resolver.server发起DNS查询,返回正常的IP地址再建立连接。
从以上不难看出,fakeIP对于明确需要走代理的连接很有用,相当于在本地省略了一次DNS查询(sing-box立即返回fakeIP),由服务端进行DNS查询建立连接。
但由于DNS查询都返回fakeIP,匹配IP类规则失去意义,两者在路由时都只能使用sniff匹配域名规则。
如果DNS全返回正常IP
- 对于需要走代理的连接,出站时传IP到服务端建立连接。
- 对于需要直连的连接,就直接IP出站建立连接。
对于此种方案,优点是可以使用sniff同时匹配域名规则和IP规则。但缺点是对于需要走代理的连接,传到服务端的可能是被污染的IP,或者干脆在本地就对污染的IP匹配到错误的规则导致分流不符合预期。如果全使用国外DNS服务器走代理查询,速度上也很慢。
sniff_override_destination
如果在服务端入站启用sniff_override_destination,无论入站是IP还是域名,通通覆盖IP为域名重新进行DNS查询是否可行?这样不就解决服务端拿到IP是污染的问题了吗?但这样有个弊端。
有些连接目标IP地址是确定的,或者说是硬编码的,这种不需要DNS查询直接IP入站。这种情况下,如果强行用sniff到的域名覆盖目标地址重新进行DNS查询建立连接,可能会导致连接出现各种问题,比如sniff到的域名解析出的IP地址和程序的预期不符,或者干脆就是无效的域名。
折中方案
那么有没有一种方案既可以保留fakeIP优点,又可以最大限度的保证DNS查询的速度和准确,并且尽可能同时匹配域名和IP类规则?折中的方案是:
- 仅对明确需要走代理的域名返回
fakeIP,出站走代理。 - 需要直连的域名就用国内的DNS服务器查询,出站走直连。
- 其他连接用国外DNS服务器走代理查询,拿到可信IP后出站走代理。
启动 #
启动systemctl start sing-box
停止systemctl stop sing-box
重启systemctl restart sing-box
日志journalctl -f -u sing-box
如果最后测试没问题将sing-box设为开机自启动systemctl enable sing-box
使用 #
将局域网其他设备的网关和DNS服务器手动指定到该机器上,测试看看是否正常工作。没问题后可以在主路由器的DHCP附加选项(dhcp_option)中将网关和DNS服务器自动通告给客户端,这样就不用在其他设备手动指定了,这里以OpenWrt为例:

关于nftables #

由于 nftables 更现代化、更高效、更灵活,具有很多优势,是时候抛弃使用 iptables 命令了。和 iptables 的逻辑不同,nftables 默认没有表和链,都需要你自己创建。创建链时需要注意:
type filter hook prerouting priority -150; policy accept;
- type:
filter,route,nat,每种类型功能不一样,能在什么hook工作也不一样,比如redirect需要在nat类型进行。在iptables体现为4个预先定义的表(raw,mangle,nat,filter),并且默认定义了优先级不能改。 - hook:
prerouting,input,forward,output,postrouting,取决于网络数据包在路由中的位置。在iptables体现为5个预先定义的链(PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING)。 - priority:在当前hook中,各个自定义链的优先级和执行顺序。
Docker #
Docker 目前仍然操纵 iptables 规则来使 docker 网络工作,建议不要在该网关服务器上使用 docker 相关服务,以免出现各种问题。
参考 #
2025-09-27 编辑:修复 nftables.sh dns劫持失效的问题