Arch Linux 下 Clash + cgproxy 实现全局代理(透明代理)
Table of Contents
2022-12-12 更新
DNS 污染,我解不开,没有相关的知识。最后决定不用全局代理了,为几个必要的、常用的应用单独设置即可。
Emacs
Emacs 用到的网络请求主要是 GNUS 和一些自己用 emacs-request 写的函数:
(setq url-proxy-services
'(("http" . "127.0.0.1:7890")
("https" . "127.0.0.1:7890"))
request-curl-options
'("-x" "http://127.0.0.1:7890"))
起因
上次没主意把 v2ray 升到了 v5 版本,然后出了大问题。尝试回滚,把 v2ray 装回 4.4 ,然后报了错,连不上站点,日志看起来是服务器的问题,事实上我手机一直用着,所以估计 v2ray 是报废了。之前一直用 qv2ray + cgproxy 做全局代理[1],很方便,每个应用程序都会走代理,省去很多麻烦 – 其实根本就没管过。现在用不了了就考虑折腾一下 clash ,GLaDOS 跟我说 clash 更简单,所以尝试一番。
网上很多教程是配置 iptables
,还考虑什么旁路路由。我实在是不懂,硬着头皮乱配置一通,结果大失败。正心灰意冷之际,突然看到:
也就是说,cgproxy是通过 Tproxy 把流量转给 qv2ray 的,而 clash 也支持 Tproxy 。我突然心生一计 ——先睡觉。
工具安装
Pyyaml 安装
这个库用来解析 yaml
文件, clash 的配置文件就是 yaml
格式。
注意,因为最终这些流程要写成系统服务,到时候执行 Python
脚本会以 root
权限去执行,所以 Python
库的安装不能装在普通用户家目录,也就是要用 root
权限去安装。
sudo pip pyyaml
安装成功
# 用 root 权限执行 python
$ sudo python
>> import yaml
>> yaml.__version__
'6.0'
Clash 配置
Clash 配置文件
clash 的使用:
clash -d ~/.config/clash
-d
指定了用哪个目录来作为配置文件目录。
如果目录没建的话,它会自动生成,同时会去下载 mmdb
。因为在Clash 安装那里已经装过了,这里就会跳过下载 —— 如果你没装它就自动安装。
配置文件路径就是 ~/.config/clash/config.yaml
。我用的是订阅,下载来文件就是一个配好了的 cofnig.yaml
。
我是写了另一个配置文件,通过 Python
来处理的,在Python 配置这一节我会说到,这里我介绍一下那些配置。
tproxy-port: 7893
dns:
enable: true
enhanced-mode: fake-ip
listen: 0.0.0.0:53
nameserver:
- 119.29.29.29
- 114.114.114.114
fallback:
- https://1.1.1.1/dns-query
- https://8.8.8.8/dns-query
tproxy-port
顾名思义,它指定了tproxy
的监听端口,cgproxy
获取应用流量之后通过这个端口发给clash
,这个要和 cgrpxoy 的配置一致(见cgproxy 配置)dns.enable
开启 dns 嗅探dns.enhanced-mode
有redir-host
和fake-ip
两种模式[3]redir-host
: 传统的DNS转发模式,局域网根据规则查询DNS请求获取真实IP地址。fake-ip
: 伪造 IP 模式,内部DNS查询得到的是伪造 IP 段的某个地址,局域网使用伪 IP 与 Clash 通信, Clash 会根据伪 IP 找到真实 IP 使用 SOCKS 代理进行通信。
dns.listen
dns 监听地址,ip 就填0.0.0.0
,端口填53
会有冲突,下面会调。dns.nameserver
dns 服务器dns.fallback
如果GEOIP非 CN 时使用的 dns 服务器[3]
为了使 clash 能监听 dns 的 53 端口,要写改 /etc/systemd/resolved.conf
文件[4]:
DNSStubListener=no
2022-12-12 更新
在使用期间经历了一次 DNS 污染,去网上查了资料,需要把 dns 服务器的地址改成 TSL 或 TCP 请求 [5] :
tproxy-port: 7893
dns:
enable: true
enhanced-mode: fake-ip
listen: 0.0.0.0:53
nameserver:
- 'tls://dns.rubyfish.cn:853'
fallback:
- 'tls://1.1.1.1:853'
- 'tcp://1.1.1.1:53'
- 'tcp://208.67.222.222:443'
- 'tls://dns.google'
Clash 服务
脚本编写
创立目录 etc/clash
,写一个脚本 clash.sh
,这个脚本用于控制 clash 的启停。要把 CONFIG_DIR
改成你的路径:
#!/bin/bash
# 配置文件目录
CONFIG_DIR=/home/<you user name>/.config/clash
update() {
# 检查更新
python /etc/clash/update.py ${CONFIG_DIR}/env.yaml
}
start() {
# 开启 IP 转发
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf && sysctl -p
echo "net.ipv6.conf.all.forwarding = 1" >> /etc/sysctl.conf && sysctl -p
# 启动 cgproxy
systemctl start cgproxy.service
# 启动 Clash
echo "starting Clash"
echo $$ > /var/run/clash.pid
/usr/bin/clash -d ${CONFIG_DIR}
}
stop() {
# 关停 cgproxy
systemctl stop cgproxy.service
# 关停 Clash
PID=`cat /var/run/clash.pid`
kill -9 ${PID}
rm /var/run/clash.pid
# 关闭 IP 转发
echo "net.ipv4.ip_forward = 0" >> /etc/sysctl.conf && sysctl -p
echo "net.ipv6.conf.all.forwarding = 0" >> /etc/sysctl.conf && sysctl -p
}
status() {
echo "TODO"
}
case $1 in
update)
update
;;
start)
start
;;
stop)
stop
;;
status)
status
;;
*)
echo "$0 update | start | stop | status "
;;
esac
写服务脚本 /etc/systemd/system/clash.service
:
[Unit]
Description=Clash Service
Requires=network-online.target
After=network.target network-online.target
[Service]
ExecStartPre=/etc/clash/clash.sh update
ExecStart=/etc/clash/clash.sh start
ExecStop=/etc/clash/clash.sh stop
Restart=on-failure
RestartPreventExitStatus=23
[Install]
WantedBy=multi-user.target
更新服务:
systemctl daemon-reload # 刷新服务
systemctl start clash.service # 启动服务,目前还不能启动
systemctl enbale clash.service # 服务开机自启动
流程介绍
- 预处理
ExecStartPre
调用控制脚本的update
,在服务正式启动前作处理。控制脚本的update
其实就是执行了一个 Python 脚本,用来更新订阅(详见 Python 配置)。 - 启动
ExecStart
调用控制脚本的start
。- 把
1
写到ip_forward
,表示要开启 IP 转发。 - 启动 cgproxy 服务
- 记录当前 clash 服务的 PID ,用于停止服务
- 开启 clash ,指定目录
- 把
- 停止
ExecStop
调用控制脚本的stop
。- 停止 cgproxy 服务
- 根据启动时记录的 PID 去关闭 clash 进程
- 把
1
写到ip_forward
,停止 IP 转发
cgproxy 配置
配置文件位于 /etc/cgproxy/config.json
{
"comment":"For usage, see https://github.com/springzfx/cgproxy",
"port": 7893,
"program_noproxy": ["clash"],
"program_proxy": [],
"cgroup_noproxy": ["/system.slice/clash.service"],
"cgroup_proxy": ["/"],
"enable_gateway": false,
"enable_dns": true,
"enable_udp": true,
"enable_tcp": true,
"enable_ipv4": true,
"enable_ipv6": true,
"table": 10007,
"fwmark": 39283
}
各个选项[8]:
port
Tproxy 的端口,就是 clash 配置文件里面指定的tproxy_port
program_noproxy
不走代理的程序,把clash
排除,不然会流量循环。像我的音乐软件listen1
就可以加到里面,因所有流量都是国内的,用代理容易出现网络问题program_proxy
走代理的程序,这里留空,在后面程序组那里配置了cgroup_noproxy
不在代理的程序组cgroup_proxy
走代理的程序组,/
表示全部
剩下的就不介绍了,可以参考官网。
到这里,你可以把服务脚本里边的 ExecStartPre=/etc/clash/clash.sh update
先注释掉。然后更新服务,启动 clash
,应该就可以使用了, curl https://google.com
测试一下。
Python 配置
这部分是用来更新订阅的。
先写一个 py 脚本 /etc/clash/update.py
[9], [10], [11]:
import yaml
import http.client
import traceback
from urllib.parse import urlparse
import time
import sys
import os
def check_update(interval: int) -> bool:
update_file = "/etc/clash/.lastupdate"
cur_time = int(time.time())
need_update = False
with open(update_file, "r+" if os.path.exists(update_file) else "w") as f:
s = f.read() if f.readable() else None
if s:
last_update = int(s)
# 非空文件,且超过更新间隔
if cur_time - last_update >= interval * 3600 * 24:
need_update = True
else:
# 空文件,新建
last_update = 0
need_update = True
f.write(str(cur_time))
return need_update
def read_env(env_path: str) -> dict:
with open(env_path) as f:
s = f.read()
data = yaml.safe_load(s)
return data
def download_file(url: str) -> bytes:
purl = urlparse(url)
doamin = purl.netloc
path = purl.path
data = b''
try:
conn = http.client.HTTPSConnection(doamin)
conn.request("GET", path)
r = conn.getresponse()
print('File downloaded.')
data = r.read()
except:
traceback.print_exc()
return data
def write_and_update_to_file(file_data: bytes, env_data: dict) -> None:
print('Parsing file')
try:
data = yaml.safe_load(file_data)
# TPORXY-PORT
data["tproxy-port"] = env_data["tproxy_port"]
# DNS
data["dns"]["enable"] = True
data["dns"]["listen"] = "0.0.0.0:%d" % env_data["dns_port"]
data["dns"]["nameserver"] = env_data["nameserver"]
data["dns"]["fallback"] = env_data["fallback"]
data["dns"]["enhanced-mode"] = "fake-ip"
save_path = env_data["clash_config"]
print("Saving file to file: %s" % save_path)
with open(save_path, "w") as f:
f.write(yaml.safe_dump(data))
print("Update done")
except:
traceback.print_exc()
if __name__ == "__main__":
env_path = sys.argv[1]
env_data = read_env(env_path)
if check_update(env_data["interval"]):
print("Updating")
write_and_update_to_file(
download_file(env_data["clash_url"]),
env_data
)
else:
print("Donnot update")
配置文件 ~/.config/clash/env.yaml
:
# TPROXY 端口
tproxy_port: 7893
# DNS 监听端口
dns_port: 53
# 订阅地址,一个 yaml 文件
clash_url: https://xxx/yyy.yaml
# 订阅文件路径
clash_config: /home/<your user name>/.config/clash/config.yaml
# 更新间隔(单位:天)
interval: 7
# 内 DNS 服务器
nameserver:
- 119.29.29.29
- 114.114.114.114
# 外 DNS 服务器
fallback:
- https://1.1.1.1/dns-query
- https://8.8.8.8/dns-query
测试: sudo python /etc/clash/update.py ~/.config/clash/env.yaml
结语
至此,clash + cgproxy 的透明代理就配置完成了。
如果想用 iptables 的话可以去参考文末给出的文章;想用 qv2ray + cgproxy 的话看参考的第一篇[1]。
希望对你有帮助