我的透明代理方案 1.0
我的全局透明代理已经运行一年多,简单稳定。近期有些优化的想法,试验前先用本文给目前的方案打个版本号。
以下配置都是简化后的片段,仅供参考。
主路由/OpenWRT
我使用一台红米 AX6S 路由器刷 OpenWRT 后作为主路由,上游是客厅的接宽带的路由器。优点是隔离出了自己的专属内网,怎么折腾都不影响合租室友,即使以后搬家也不需要重新配置。代价仅仅是多了一次 NAT,性能损耗大可忽略不计。
基础配置:
- 关闭 IPv6,因为不想多写一份配置
- 关闭 SSH 密码登陆
安装所需依赖:
- luci-i18n-base-zh-cn:中文语言包
- 值守式系统更新,方便把需要的包封进新的系统固件
- luci-app-attendedsysupgrade
- luci-i18n-attendedsysupgrade-zh-cn
- ipset:iptables 规则要用
- 补充一些 iptables 扩展
- iptables-mod-extra
- iptables-mod-ipmark
- iptables-mod-iprange
- iptables-mod-socket
- iptables-mod-tproxy
- 补充 GNU Coreutils 里的工具
- coreutils-nohup
流量重定向
流量重定向由 Linux TProxy 实现,技术细节可以看「深入理解 Linux TProxy」。
入站
Clash :
tproxy-port: 10000
开机自启用于创建 iptables 规则的脚本:
ipset create bypass_clash hash:net
ipset add bypass_clash 0.0.0.0/8
ipset add bypass_clash 10.0.0.0/8
ipset add bypass_clash 100.64.0.0/10
ipset add bypass_clash 127.0.0.0/8
ipset add bypass_clash 169.254.0.0/16
ipset add bypass_clash 172.16.0.0/12
ipset add bypass_clash 192.0.0.0/24
ipset add bypass_clash 192.0.2.0/24
ipset add bypass_clash 192.88.99.0/24
ipset add bypass_clash 192.168.0.0/16
#ipset add bypass_clash 198.18.0.0/15
ipset add bypass_clash 198.51.100.0/24
ipset add bypass_clash 203.0.113.0/24
ipset add bypass_clash 224.0.0.0/3
ip rule add fwmark 0x233 table 100
ip route add local default dev lo table 100
iptables -t mangle -N clash
# 忽略fake-ip之外的保留地址
iptables -t mangle -A clash -m set --match-set bypass_clash dst -j RETURN
iptables -t mangle -A clash -p udp -s 192.168.80.9 -j RETURN
iptables -t mangle -A clash -p tcp -s 192.168.80.9 -j RETURN
iptables -t mangle -A clash -p tcp -j TPROXY --on-ip 127.0.0.1 --on-port 10000 --tproxy-mark 0x233
iptables -t mangle -A clash -p udp -j TPROXY --on-ip 127.0.0.1 --on-port 10000 --tproxy-mark 0x233
iptables -t mangle -A PREROUTING -p tcp -j clash
iptables -t mangle -A PREROUTING -p udp -j clash
# 分流已连接的请求,优化tproxy性能
iptables -t mangle -N tproxy_divert
iptables -t mangle -A tproxy_divert -j MARK --set-mark 0x233
iptables -t mangle -A tproxy_divert -j ACCEPT
iptables -t mangle -I PREROUTING -p tcp -m socket -j tproxy_divert
# 避免直接发往透明代理端口导致死循环
iptables -A INPUT -p tcp --dport 10000 -m mark ! --mark 0x233 -j REJECT
iptables -A INPUT -p udp --dport 10000 -m mark ! --mark 0x233 -j REJECT
出站
早期也重定向了主路由本机的出站流量,目前不在用了,但为了内容完整性还是写一下。
将出站流量打上入站中同样的标记,使其路由进入本地回环:
iptables -t mangle -N clash_out
# 过滤发向保留地址的
iptables -t mangle -A clash_out -m set --match-set bypass_clash dst -j RETURN
# 给出站流量打标记,之后与入站重定向同理
iptables -t mangle -A clash_out -j MARK --set-mark 0x233
iptables -t mangle -A OUTPUT -j clash_out
接下来还需要避免 Clash 的出站流量被重定向入站,造成死循环。
一种方式是根据用户做区分,需要安装 shadow-useradd
和 iptables owner 扩展(包含在 iptables-mod-extra
包中),然后用单独的用户运行 Clash。假设是 clashuser
:
iptables -t mangle -I clash_out -m owner --uid-owner clashuser -j RETURN
后来发现 Clash 提供了配置项 routing-mark
标记出站流量,这更方便。假设 Clash 的标记是 666
:
iptables -t mangle -A OUTPUT -m mark ! --mark 666 -j clash_out
DNS
OpenWRT 配置:
- 「DNS 转发」中设置 Clash 为 Dnsmasq 的上游
- 选中「忽略解析文件」,否则会有原先上游的干扰
Clash 关闭 IPv6、设置端口、使用 fake-IP 模式:
dns:
enable: true
ipv6: false
listen: 0.0.0.0:5353
default-nameserver:
- 8.8.8.8
- 223.5.5.5
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
...
fake-IP
fake-IP 模式下 Clash 为域名分配一个假 IP(DNS TTL 为 1 避免被客户端缓存)。优点是部分情况下可以节省发向上游 DNS 服务的查询,如果域名规则合理的话也可以避免 DNS 泄漏。
为了让 GEO DNS 分配合理的节点、避免客户端网络环境中的 DNS 污染,代理服务器往往会使用自己网络环境下的的解析结果。所以客户端代理工具会做优化,例如 Clash 在遇到基于 IP 的规则(不带 no-resolve
选项的 IP 、SCRIPT 等类型规则)之前不需要解析域名,命中代理规则的话就直接发给代理服务器。
在非 fake-IP 模式下,即使有上述优化,也必须解析域名来获得一个让客户端发起连接的 IP。
但在 fake-IP 模式下是立即返回一个假 IP,并记录域名和假 IP 的映射关系。客户端以此 IP 为目的地址发起请求,Clash 捕获到该 IP 的请求后根据对应关系获取原始域名,然后进行规则匹配。在没有遇到第一个基于 IP 的规则前,都不需要解析。
Clash 文档中以请求 google.com 为例:
$ curl -v http://google.com
<---- cURL asks your system DNS (Clash) about the IP address of google.com
----> Clash decided 198.18.1.70 should be used as google.com and remembers it
* Trying 198.18.1.70:80...
<---- cURL connects to 198.18.1.70 tcp/80
----> Clash will accept the connection immediately, and..
* Connected to google.com (198.18.1.70) port 80 (#0)
----> Clash looks up in its memory and found 198.18.1.70 being google.com
----> Clash looks up in the rules and sends the packet via the matching outbound
推荐阅读浅谈在代理环境中的 DNS 解析行为。
分流
所有流量都经过 Clash,分流规则决定了用网体验。我不喜欢那种堆了大几万条域名、IP 的规则集。规则匹配时的性能消耗还好说,主要是条目太多没法审计,稳定性完全依赖维护者,可能哪天规则自动更新就打乱了习惯。
我更推荐先用关键字、后缀等粗粒度规则筛出自己用网习惯下的高频域名,比如 foreign。让这些高频域名用上 fake-IP 的优势,剩下的用 GEOIP 兜底。
proxy-groups:
- name: Proxy
type: select
proxies:
- xxx
use:
- xxx
rules:
- DOMAIN-SUFFIX,googleapis.cn,Proxy
- DOMAIN-SUFFIX,googleapis.com,Proxy
....
- GEOIP,CN,DIRECT
- MATCH,Proxy
兜底规则非常依赖 GEOIP 库的准确度,但 Clash 内置 MaxMind GeoLite2 的大陆地区 IP 准确度存疑,所以我替换为综合了 ipip.net 和纯真数据的 Hackl0us/GeoIP2-CN。这个项目每三天更新一次数据,可以写个定时任务替换本地的 Country.mmdb
文件,但这个文件不会热重载,需要重启 Clash。
1% 的不适用场景
代理工具转发流量的基本相同:劫持客户端流量后,分别与两端建立连接,然后在中间做转发。这会破坏对网络要求苛刻的场景,例如端口扫描时 Nmap 发出的 TCP SYN 其实是被代理工具响应,不是实际目标,误导 Nmap 认为所有探测端口都开放。
并且代理工具一般工作在第四层,只处理 TCP/UDP。所以默认基于 ICMP 协议的 traceroute 和 ping 等工具也得换成支持 TCP/UDP 的版本。
目前看来更理想的方式是给数据套一层 L3 VPN 再走代理,只是不知道多一层 NAT 和 UDP-in-TCP 会造成多大的损耗。