sing-box 透明代理配置。本文中的配置是示例,如果要使用请根据实际情况自己修改。
环境
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
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/16, 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 }' ## 添加你的代理IP地址或其他不想透明代理的地址
if [ $# != 1 ]
then
echo "Use $(basename "$0") <set|clear>"
exit 1;
fi
clearFirewallRules()
{
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 del local default dev $INTERFACE table $PROXY_ROUTE_TABLE
echo "clear ip rule"
fi
nft flush ruleset
echo "clear nftables"
}
if [ $1 = 'set' ]
then
clearFirewallRules
ip -f inet rule add fwmark $PROXY_FWMARK lookup $PROXY_ROUTE_TABLE
ip -f inet route add local default dev $INTERFACE table $PROXY_ROUTE_TABLE
sysctl -w net.ipv4.ip_forward=1 > /dev/null
nft -f - <<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 accept comment "DNS透明代理"
ip daddr $CustomBypassIP accept comment "绕过某些地址"
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"
udp dport { netbios-ns, netbios-dgm, netbios-ssn } accept comment "绕过NBNS流量"
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
echo "set nftables"
elif [ $1 = 'clear' ]
then
clearFirewallRules
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
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": "tencentDNS",
"address": "tls://120.53.53.53",
"detour": "DIRECT"
},
{
"tag": "aliDNS",
"address": "https://223.6.6.6/dns-query",
"detour": "DIRECT"
},
{
"tag": "cloudflareDNS",
"address": "https://1.1.1.1/dns-query",
"detour": "PROXY"
},
{
"tag": "localDNS",
"address": "10.0.0.1",
"detour": "DIRECT"
},
{
"tag": "refusedDNS",
"address": "rcode://success"
},
{
"tag": "fakeipDNS",
"address": "fakeip"
}
],
"rules": [
{
"outbound": "any",
"server": "tencentDNS"
},
{
"query_type": [
"AAAA",
],
"server": "refusedDNS",
"disable_cache": true
},
{
"invert": true,
"protocol": "dns",
"server": "refusedDNS",
"disable_cache": true
},
{
"rule_set": "geosite-private",
"server": "localDNS"
},
{
"rule_set": [
"geosite-geolocation-cn",
"geosite-win-update",
"geosite-apple"
],
"server": "tencentDNS"
},
{
"query_type": "A",
"rule_set": [
"geosite-google",
"geosite-github",
"geosite-telegram",
"geosite-openai",
"geosite-steam",
"geosite-gfw"
],
"server": "fakeipDNS",
"rewrite_ttl": 10
},
{
"query_type": [
"A",
"CNAME",
"SVCB",
"HTTPS"
],
"server": "cloudflareDNS"
}
],
"final": "localDNS",
"strategy": "ipv4_only",
"disable_cache": false,
"disable_expire": false,
"independent_cache": true,
"reverse_mapping": false,
"fakeip": {
"enabled": true,
"inet4_range": "198.18.0.0/15"
}
},
"inbounds": [
{
"tag": "TPROXY-IN",
"type": "tproxy",
"listen": "0.0.0.0",
"listen_port": 7895,
"udp_timeout": 180,
"sniff": true
},
{
"tag": "MIXED-MAIN-IN",
"type": "mixed",
"listen": "0.0.0.0",
"listen_port": 7900,
"udp_timeout": 180,
"sniff": true
}
],
"outbounds": [
{
"tag": "PROXY",
"type": "selector",
"outbounds": [
"PROXY_REALITY",
"PROXY_TROJAN"
],
"default": "PROXY_REALITY",
"interrupt_exist_connections": true
},
{
"tag": "FINAL",
"type": "selector",
"outbounds": [
"PROXY",
"DIRECT"
],
"default": "PROXY",
"interrupt_exist_connections": true
},
{
"tag": "APPLE",
"type": "selector",
"outbounds": [
"PROXY",
"DIRECT",
"PROXY_REALITY",
"PROXY_TROJAN"
],
"default": "DIRECT",
"interrupt_exist_connections": true
},
{
"tag": "STEAM",
"type": "selector",
"outbounds": [
"PROXY",
"DIRECT",
"PROXY_REALITY",
"PROXY_TROJAN"
],
"default": "PROXY",
"interrupt_exist_connections": true
},
{
"tag": "OPENAI",
"type": "selector",
"outbounds": [
"PROXY",
"DIRECT",
"PROXY_REALITY",
"PROXY_TROJAN"
],
"default": "PROXY_REALITY",
"interrupt_exist_connections": true
},
{
"tag": "TELEGRAM",
"type": "selector",
"outbounds": [
"PROXY",
"DIRECT",
"PROXY_REALITY",
"PROXY_TROJAN"
],
"default": "PROXY_REALITY",
"interrupt_exist_connections": true
},
{
"tag": "DOWNLOADER",
"type": "selector",
"outbounds": [
"PROXY",
"DIRECT",
"PROXY_REALITY",
"PROXY_TROJAN"
],
"default": "DIRECT",
"interrupt_exist_connections": true
},
{
"tag": "PROXY_TROJAN",
"type": "trojan",
"server": "exp1.com",
"server_port": 443,
"password": "passwd",
"tls": {
"enabled": true,
"server_name": "exp.com",
"insecure": false,
"utls": {
"enabled": true,
"fingerprint": "chrome"
}
},
"multiplex": {
"enabled": true,
"protocol": "h2mux",
"max_streams": 6,
"padding": true
}
},
{
"tag": "PROXY_REALITY",
"type": "vless",
"server": "1.2.3.4",
"server_port": 443,
"uuid": "xx",
"tls": {
"enabled": true,
"server_name": "exp2.com",
"utls": {
"enabled": true,
"fingerprint": "chrome"
},
"reality": {
"enabled": true,
"public_key": "xx",
"short_id": "xx"
}
},
"multiplex": {
"enabled": true,
"protocol": "h2mux",
"max_streams": 6,
"padding": true
}
},
{
"tag": "DIRECT",
"type": "direct"
},
{
"tag": "DNS-OUT",
"type": "dns"
},
{
"tag": "BLOCK",
"type": "block"
}
],
"route": {
"rules": [
{
"protocol": "dns",
"outbound": "DNS-OUT"
},
{
"port": 53,
"outbound": "DNS-OUT"
},
{
"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",
"outbound": "OPENAI"
},
{
"rule_set": [
"geosite-telegram",
"geoip-telegram"
],
"outbound": "TELEGRAM"
},
{
"rule_set": [
"geosite-google",
"geosite-github",
"geosite-debian",
"geosite-gfw"
],
"outbound": "PROXY"
},
{
"rule_set": [
"geoip-private",
"geoip-cn",
"geosite-geolocation-cn",
"geosite-win-update"
],
"outbound": "DIRECT"
},
{
"rule_set": "geosite-steam",
"outbound": "STEAM"
},
{
"source_ip_cidr": "10.0.0.10/32",
"outbound": "DOWNLOADER"
}
],
"rule_set": [
{
"tag": "geosite-private",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/private.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-google",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/google.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-geolocation-cn",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/geolocation-cn.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-win-update",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/win-update.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-apple",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/apple.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-github",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/github.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geoip-telegram",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/telegram.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "30d"
},
{
"tag": "geosite-telegram",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/telegram.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-bing",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/bing.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-openai",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/openai.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-steam",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/steam.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-debian",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/debian.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geosite-gfw",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geosite/gfw.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geoip-private",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/private.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
},
{
"tag": "geoip-cn",
"type": "remote",
"format": "source",
"url": "https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/sing/geo/geoip/cn.json",
"download_detour": "PROXY_TROJAN",
"update_interval": "999d"
}
],
"final": "FINAL",
"auto_detect_interface": true,
"default_mark": 666
},
"experimental": {
"clash_api": {
"external_controller": "0.0.0.0:9090",
"external_ui": "yacd",
"external_ui_download_url": "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip",
"external_ui_download_detour": "PROXY_TROJAN",
"secret": "passwd",
"default_mode": "rule"
}
}
}
服务端配置文件,前置haproxy复用443端口
{
"log": {
"disabled": false,
"level": "warn",
"timestamp": true
},
"inbounds": [
{
"type": "vless",
"tag": "vless-reality",
"listen": "0.0.0.0",
"listen_port": 500,
"users": [
{
"name": "xx",
"uuid": "uuid",
}
],
"tls": {
"enabled": true,
"server_name": "exp2.com",
"reality": {
"enabled": true,
"handshake": {
"server": "xx.com",
"server_port": 443
},
"private_key": "private_key",
"short_id": ["short_id"]
}
},
"multiplex": {
"enabled": true,
"padding": true
}
},
{
"type": "trojan",
"tag": "trojan",
"listen": "0.0.0.0",
"listen_port": 501,
"users": [
{
"password": "password"
}
],
"tls": {
"enabled": true,
"server_name": "exp1.com",
"alpn": ["h2", "http/1.1"],
"acme": {
"domain": "exp1.com",
"data_directory": "/path/to/sing-box/data/cert",
"email": "email@gmail.com",
"provider": "letsencrypt",
"dns01_challenge": {
"provider": "cloudflare",
"api_token": "api_token"
}
}
},
"multiplex": {
"enabled": true,
"padding": true
}
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
}
]
}
路由和规则匹配
在透明代理环境中,程序在发起连接前首先需要知道目标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会发起DNS查询,
dns.rules
时匹配outound:any
返回正常的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 相关服务,以免出现各种问题。