Clash 旁路由 & 透明代理
需求
完成代理配置后,局域网设备不需要任何配置,即可实现科学上网。
最简单的方案是在主路由上安装 OpenClash,但是这样会有一些问题:
- 主路由的性能可能不够,运行 OpenClash 会导致网络卡顿。
- OpenClash 基于 Clash,又进行了一层便于小白使用的封装。我对于 OpenClash 的感觉就是需要安装一堆奇怪的依赖,启动后会向 iptables 等地方添加一堆奇怪的规则,然后就成功实现透明代理了。但是出问题后因为 OpenClash 有点类似于黑箱,排查问题比较困难。
- OpenClash 的 LuCI 界面卡卡的,不流畅。
所以我选择了在旁路由 (x86 Linux 服务器) 上安装 Clash,实现透明代理。
方案
假设局域网 IP 段为 192.168.1.0/24
;主路由 IP 为 192.168.1.1
, 运行 OpenWrt;旁路由 IP 为 192.168.1.2
,x86 服务器,运行 ArchLinux。手机作为接入网络的设备,不需要修改任何设置、安装任何软件,即可实现科学上网。
- 主路由将旁路由
192.168.1.2:53
设置为上级 DNS 服务器。 - 手机访问
www.baidu.com
orwww.google.com
,DNS 请求被转发到旁路由。 - 旁路由运行 Clash,Clash 在
192.168.1.2:53
提供 DNS 服务。基于 dnsmasq-china-list 提供的国内域名列表进行分流,www.baidu.com
是国内域名,查询上层 DNS 服务器https://dns.alidns.com/dns-query
,返回www.baidu.com
的 IP 地址39.156.66.10
;www.google.com
是国外域名,查询上层 DNS 服务器https://dns.cloudflare.com/dns-query
,返回www.google.com
的 Fake IP198.18.0.4
, 同时 Clash 保存198.18.0.4
到www.google.com
的映射关系。 - 主路由设置了路由规则,将 Fake IP 网段
198.18.0.0/16
指向了旁路由192.168.1.2
. 手机访问www.baidu.com
时,请求不经过旁路由,直接访问;访问www.google.com
时,请求被转发到旁路由。 - 旁路由收到
198.18.0.4
的 TProxy 请求,Clash 会根据映射规则得知手机想要访问www.google.com
,然后进行请求代理。
尽管我非常喜欢使用 Docker,但是 Docker 也会对 iptables 进行一些修改,导致一些额外复杂度的引入。所以我选择二进制 + Systemd 服务的方式运行 Clash,比较简单。
步骤
旁路由允许转发
cat << EOF > /etc/sysctl.d/99-sysctl.conf
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --system
旁路由禁止 systemd-resolved 占用 53 端口
我的 ArchLinux 使用 systemd-resolved,它会占用 53 端口,会和 Clash 冲突。所以需要禁止 systemd-resolved 占用 53 端口。
mkdir -p /etc/systemd/resolved.conf.d
cat << EOF > /etc/systemd/resolved.conf.d/disable.conf
[Resolve]
DNSStubListener=no
EOF
systemctl restart systemd-resolved
开启 DNSStubListener=no
后,systemd-resolved 会停止监听 53 端口,并仍能正常进行解析工作。
ref Getting rid of systemd-resolved consuming port 53, Systemd-resolved DNSStubListener
旁路由安装 Clash
安装 Clash
mkdir -p /root/clash
cd /root/clash
wget https://downloads.clash.wiki/ClashPremium/clash-linux-amd64-2023.08.17.gz && gzip -d clash-linux-amd64-2023.08.17.gz && mv ./clash-linux-amd64-2023.08.17 clash && chmod +x clash
准备 Clash 配置文件,记得将 proxies 设置为自己的代理服务器,并修改 secret。
cat << EOF > /root/clash/config.yaml.source
# restful api
external-controller: 0.0.0.0:9090
# 保护 restful api 的口令,记得修改
secret: "SET YOUR SECRET"
# 日志级别,info / warning / error / debug / silent,调试时设为 debug。平时可以设置更高的日志级别,减少日志量。
log-level: debug
# HTTP(S) 和 SOCKS4(A)/SOCKS5 代理服务共用端口
mixed-port: 1080
# Linux 的透明代理服务端口 (TProxy TCP 和 TProxy UDP)
tproxy-port: 1081
# Clash 路由工作模式
# rule: 基于规则的数据包路由
# global: 所有数据包将被转发到单个节点
# direct: 直接将数据包转发到互联网
mode: rule
# 设置为 true 以允许来自其他 LAN IP 地址的连接
allow-lan: true
# allow-lan == true 时,绑定所有 IP 地址
bind-address: '*'
profile:
# 持久化 Fake IP 映射关系
store-fake-ip: true
dns:
enable: true
listen: 0.0.0.0:53
ipv6: true
# 用于解析下列 DNS 服务器的域名,比如 dns.alidns.com
default-nameserver:
- 223.5.5.5
- 223.6.6.6
enhanced-mode: fake-ip
use-hosts: true
# Fake IP 地址池 CIDR
fake-ip-range: 198.18.0.1/16
# 如果域名匹配 fallback-filter(国外域名),DNS 服务器列表为 fallback
# 如果域名不匹配 fallback-filter(国内域名),DNS 服务器列表为 nameserver + fallback
# Clash 会向 DNS 服务器列表的所有服务器并发发送请求
# 并使用第一个收到的响应作为 DNS 查询的结果.
# nameserver 为仅用于国内域名的 DNS 服务器列表
nameserver:
- https://doh.pub/dns-query
- https://dns.alidns.com/dns-query
# fallback 国内域名、国外域名都会使用的 DNS 服务器列表
fallback:
- https://doh.dns.sb/dns-query
- https://dns.cloudflare.com/dns-query
- https://dns.twnic.tw/dns-query
- tls://8.8.4.4:853
# fallback-filter 用于判断域名是否为国外域名
fallback-filter:
geoip: true
ipcidr:
- 240.0.0.0/4
- 0.0.0.0/32
# proxies 设置为自己的代理服务器
EOF
ref Clash 参考配置
Clash 将占用以下端口:
- 53/udp: DNS 服务
- 9090/tcp: RESTful API
- 1080/tcp: HTTP(S) / SOCKS5 代理服务,局域网设备可以手动设置代理服务器为
socks5://192.168.1.2:1080
或http://192.168.1.2:1080
实现科学上网 - 1081/tcp, 1081/udp: TProxy 透明代理服务
旁路由设置中国域名更新
cat << EOF > /root/clash/china_domain_update.py
#!/usr/bin/env python
import os
import urllib.request
import subprocess
from pathlib import Path
from yaml import load, dump
try:
from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
from yaml import Loader, Dumper
def main():
file_dnsmasq_china = Path("./accelerated-domains.china.conf")
# url = "https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf"
url = "https://gitee.com/felixonmars/dnsmasq-china-list/raw/master/accelerated-domains.china.conf"
subprocess.run(["wget", url, "-O", str(file_dnsmasq_china)], check=True)
fakeip_direct_rules = '''*.lan
*.localdomain
*.example
*.invalid
*.localhost
*.test
*.local
*.home.arpa
time.*.com
time.*.gov
time.*.edu.cn
time.*.apple.com
ntp.*.com
*.time.edu.cn
*.ntp.org.cn
+.pool.ntp.org
time1.cloud.tencent.com
*.cn'''.splitlines()
lines = file_dnsmasq_china.read_text().splitlines()
for line in lines:
# server=/00058.com/114.114.114.114 -> 00058.com
domain = line.split("/")[-2].strip()
fakeip_direct_rules.append(f"+.{domain}")
file_clash_config_source = Path("/root/clash/config.yaml.source")
file_clash_config = Path("/root/clash/config.yaml")
config = load(file_clash_config_source.read_text(), Loader=Loader)
config['dns']['fake-ip-filter'] = fakeip_direct_rules
file_clash_config.write_text(dump(config, Dumper=Dumper, allow_unicode=True))
if __name__ == '__main__':
main()
EOF
chmod +x /root/clash/china_domain_update.py
运行脚本,下载最新的中国域名列表,并更新 Clash 配置文件。
也可以编写 Systemd 服务,每天 4:00 更新一次。
cat << EOF > /etc/systemd/system/clash_china_domain_update.service
[Unit]
Description=Clash China Domain Update
After=network.target
[Service]
Type=oneshot
ExecStart=/root/clash/china_domain_update.py
[Install]
WantedBy=multi-user.target
EOF
cat << EOF > /etc/systemd/system/clash_china_domain_update.timer
[Unit]
Description=Clash China Domain Update Timer
[Timer]
OnCalendar=*-*-* 04:00:00
Unit=clash_china_domain_update.service
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now clash_china_domain_update.timer
旁路由设置 Clash 服务
新建 Systemd 服务
cat << EOF > /etc/systemd/system/clash.service
[Unit]
Description="Clash is a cross-platform rule-based proxy utility that runs on the network and application layer."
After=network.target
[Service]
Type=simple
Restart=always
RestartSec=1
ExecStart=/root/clash/clash -d /root/clash
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now clash
旁路由设置 TProxy 的相关 iptables 规则
cat << EOF > /root/clash/clash_tproxy_iptables_setup.sh
#!/usr/bin/env bash
# ROUTE RULES
ip rule add fwmark 1 table 100
ip route add local 0.0.0.0/0 dev lo table 100
# CREATE TABLE
iptables -t mangle -N clash
# RETURN LOCAL AND LANS
iptables -t mangle -A clash -d 0.0.0.0/8 -j RETURN
iptables -t mangle -A clash -d 10.0.0.0/8 -j RETURN
iptables -t mangle -A clash -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A clash -d 169.254.0.0/16 -j RETURN
iptables -t mangle -A clash -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A clash -d 192.168.50.0/16 -j RETURN
iptables -t mangle -A clash -d 192.168.9.0/16 -j RETURN
iptables -t mangle -A clash -d 224.0.0.0/4 -j RETURN
iptables -t mangle -A clash -d 240.0.0.0/4 -j RETURN
# FORWARD ALL
iptables -t mangle -A clash -p udp -j TPROXY --on-port 1081 --tproxy-mark 1
iptables -t mangle -A clash -p tcp -j TPROXY --on-port 1081 --tproxy-mark 1
# HIJACK ICMP (untested)
# iptables -t mangle -A clash -p icmp -j DNAT --to-destination 127.0.0.1
# REDIRECT
iptables -t mangle -A PREROUTING -j clash
EOF
chmod +x /root/clash/clash_tproxy_iptables_setup.sh
编写 Systemd 服务,每次启动时设置 TProxy 规则。
cat << EOF > /etc/systemd/system/clash_tproxy_iptables_setup.service
[Unit]
Description=Clash TProxy IPTables Setup
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/bash /root/clash/clash_tproxy_iptables_setup.sh
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now clash_tproxy_iptables_setup.service
OpenWrt 主路由设置路由规则
将 Fake IP 网段 198.18.0.0/16
指向旁路由
网络 - 路由 - 添加: LAN, unicast, 198.18.0.0/16, 192.168.1.2
OpenWrt 主路由设置旁路由为上级 DNS
网络 - DHCP/DNS - 常规设置 - DNS 转发 - 添加 192.168.1.2
网络 - DHCP/DNS - HOSTS 和解析文件 - 忽略解析文件
运维
检查 DNS 解析是否正确
# baidu.com 应解析为直连 ip,如 39.156.66.10
dig baidu.com
# google.com 应解析为 Fake IP,如 198.18.0.4
dig google.com
/etc/init.d/dnsmasq restart # 重启 dnsmasq
/etc/init.d/uhttpd restart # 重启 luci
缺点
- 仅根据域名白名单进行分流,可能不准确