前言
使用clash实现透明代理有两种方式,一种是 redirect
,仅支持TCP流量,另一种是 tproxy
,支持TCP和UDP流量。为了能代理UDP流量,所以我们选用 tproxy
的方式来实现。
工作流
nftables
的工作流详见 Netfilter hooks。具体Netfilter的五个链 prerouting
input
forward
output
postrouting
是什么关系,以及流向,请参考该链接来加深理解。全部的文档可以查阅本文下方 参考资料 的1和2。
注意,在旁路由自身中运行的程序都工作在应用层,而 tproxy
只能工作在 prerouting
链,所以如果我们要想透明代理旁路由自己产生的流量,要通过设置IP规则让这部分流量在进入 output
链时,将需要代理的流量转回到 prerouting
链。这就需要靠IP规则来实现。
下载与安装
在 clash 和 clash-meta (现在叫mihomo)中,我选用后者来使用(前者已经删库了)。我默认你已经按照 前文(二) 安装好了必要的软件了。以下为root用户运行的命令。
1
2
3
4
5
6
7
8
9
10
11
12
|
## 检查最新稳定版的版本号,如果获取不到请检查网络
remote_ver=$(curl -sS https://api.github.com/repos/MetaCubeX/mihomo/releases/latest | jq -r .tag_name | sed 's|v||' | grep -v "null"); echo $remote_ver
## 下载最新稳定版(前一句有输出这一句才能正常执行)
cd /tmp
wget -q --progress=bar:dot --show-progress -O "mihomo-linux-amd64-v${remote_ver}.gz" "https://github.com/MetaCubeX/mihomo/releases/download/v${remote_ver}/mihomo-linux-amd64-v${remote_ver}.gz"
## 解压
gzip -d "mihomo-linux-amd64-v${remote_ver}.gz"
## 安装
install -ps mihomo-linux-amd64-v${remote_ver} /usr/local/bin/clash
|
上述 wget
这一行默认下载的是 AMD64 v3
架构的包,你可以输入以下命令查看是否支持。如若不支持,可以下载名称中带 cgo
或者 compatible
的版本,说明 在此。
1
|
/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 --help | grep supported
|
如若支持 AMD64 v3 架构,输出将会是下面这样的。
1
2
3
4
5
|
x86-64-v3 (supported, searched)
x86-64-v2 (supported, searched)
x86_64 (AT_PLATFORM; supported, searched)
tls (supported, searched)
x86_64 (supported, searched)
|
创建服务
clash.service
创建工作目录/var/lib/clash
。
1
|
mkdir -p /var/lib/clash
|
创建文件 /etc/systemd/system/clash.service
,内容如下。请注意将 ens18
修改为你的网卡名。这时 clash
的配置文件为 /var/lib/clash/config.yaml
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
[Unit]
Description = Clash-Meta tproxy daemon.
Wants = network-online.target subconverter.service
After = network-online.target
[Service]
Environment = PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Type = simple
Restart = always
LimitNPROC = 500
LimitNOFILE = 1000000
ExecStartPre = sleep 1s
ExecStart = clash -d /var/lib/clash
ExecStartPost = ip route add local default dev ens18 table 100 ; ip rule add fwmark 1 lookup 100 ; mynftables.nft
ExecStop = nft flush ruleset ; ip route del local default dev ens18 table 100 ; ip rule del fwmark 1 lookup 100
[Install]
WantedBy = multi-user.target
|
如果你比较讲究Linux哲学,可以把 ExecStart
改成下面这样,这时 clash
的配置文件为 /etc/clash/config.yaml
。
1
|
ExecStart = clash -d /var/lib/clash -f /etc/clash/config.yaml
|
如果不想以root用户运行 clash
,可以自行创建用户 clash
和用户组 clash
,然后在 /etc/systemd/system/clash.service
的 [Service]
单元下增加下面几行,同时修改 /var/lib/clash
为该普通用户所有(后续的脚本都是以root用户为运行用户进行的,如果要把 clash
服务调整为普通用户运行,请自行修改脚本)。
1
2
3
4
|
User = clash
Group = clash
CapabilityBoundingSet = CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
AmbientCapabilities = CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE
|
mynftables.nft
注意到我们在 /etc/systemd/system/clash.service
的 ExecStartPost
行有执行 mynftables.nft
。现在,新建 /usr/local/bin/mynftables.nft
,内容如下。请注意按照注释修改,创建好后以命令 chmod +x /usr/local/bin/mynftables.nft
为它增加可执行权限。该文件会引用到另外一个文件 /var/lib/clash/geoip4_cn.nft
,你可以先下载这并非是最新的文件,先保存到该位置:geoip4_cn.nft,我们将在 本系列第(七)篇 用定时任务来自动更新。
如果你的代理不支持代理 UDP
流量,可以选择解除 ip protocol udp accept
行的注释。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
#!/usr/sbin/nft -f
## 清空旧规则
flush ruleset
## 只处理指定网卡的流量,要和ip规则中的接口操持一致
define interface = ens18
## clash的透明代理端口
define tproxy_port = 7895
## clash打的标记(routing-mark)
define clash_mark = 666
## 常规流量标记,ip rule中加的标记,要和ip规则中保持一致,对应 "ip rule add fwmark 1 lookup 100" 中的 "1"
define default_mark = 1
## 本机运行了服务并且需要在公网上访问的tcp端口(本机开放在公网上的端口),仅本地局域网访问的服务端口可不用在此变量中,以半角逗号分隔
define local_tcp_port = {
2222, # ssh,按需设置
8080-8088 # nginx webui,按需设置
}
## 要绕过的局域网内tcp流量经由本机访问的目标端口,也就是允许局域网内其他主机主动设置DNS服务器为其他服务器,而非旁路由
define lan_2_dport_tcp = {
53 # dns查询
}
## 要绕过的局域网内udp流量经由本机访问的目标端口,也就是允许局域网内其他主机主动设置DNS服务器为其他服务器,而非旁路由;另外也允许局域网内其他主机访问远程的NTP服务器
define lan_2_dport_udp = {
53, # dns查询
123 # ntp端口
}
## 保留ip地址
define private_address = {
127.0.0.0/8,
100.64.0.0/10,
169.254.0.0/16,
224.0.0.0/4,
240.0.0.0/4,
10.0.0.0/8,
172.16.0.0/12,
192.168.0.0/16
}
## 大陆ip地址
include "/var/lib/clash/geoip4_cn.nft"
table ip clash {
## 保留ipv4集合
set private_address_set {
type ipv4_addr
flags interval
elements = $private_address
}
## 大陆ipv4集合
set geoip4_cn_set {
type ipv4_addr
flags interval
elements = $geoip4_cn
}
## prerouting链
chain prerouting {
type filter hook prerouting priority filter; policy accept;
ip protocol { tcp, udp } socket transparent 1 meta mark set $default_mark accept # 绕过已经建立的连接
meta mark $default_mark goto clash_tproxy # 已经打上default_mark标记的属于本机流量转过来的,直接进入透明代理
fib daddr type { local, broadcast, anycast, multicast } accept # 绕过本地、单播、组播、多播地址
tcp dport $lan_2_dport_tcp accept # 绕过经由本机到目标端口的tcp流量
udp dport $lan_2_dport_udp accept # 绕过经由本地到目标端口的udp流量
ip daddr @private_address_set accept # 绕过目标地址为保留ip的地址
ip daddr @geoip4_cn_set accept # 绕过目标地址为大陆ip的地址
# ip protocol udp accept # 绕过全部udp流量(udp不进行透明代理)
goto clash_tproxy # 其他流量透明代理到clash
}
## 透明代理
chain clash_tproxy {
ip protocol { tcp, udp } tproxy to :$tproxy_port meta mark set $default_mark
}
## output链
chain output {
type route hook output priority filter; policy accept;
oifname != $interface accept # 绕过本机内部通信的流量(接口lo)
meta mark $clash_mark accept # 绕过本机clash发出的流量
fib daddr type { local, broadcast, anycast, multicast } accept # 绕过本地、单播、组播、多播地址
udp dport { 53, 123 } accept # 绕过本机dns查询、NTP流量
tcp sport $local_tcp_port accept # 绕过本地运行了服务的tcp端口,如果并不需要从公网访问这些端口,可以注释掉本行
ip daddr @private_address_set accept # 绕过目标地址为保留ip的地址
ip daddr @geoip4_cn_set accept # 绕过目标地址为大陆ip的地址
ip protocol { tcp, udp } meta mark set $default_mark # 其他流量重路由到prerouting
}
}
|
clash配置文件
暂时先按照下面的形式形成 clash
的配置文件 /var/lib/clash/config.yaml
或者 /etc/clash/config.yaml
,我们将在 本系列第(七)篇 用定时任务来自动更新订阅。我只对关键信息进行了注释,未注释的内容你都可以自行按照 官方WIKI 进行设置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
port: 7890
socks-port: 7891
mixed-port: 7893
tproxy-port: 7895 # 和mynftables.nft中的tproxy_port保持一致
routing-mark: 666 # 和mynftables.nft中的clash_mark保持一致
allow-lan: true
bind-address: "*"
mode: rule
log-level: info
ipv6: false # 不进行IPv6流量代理
find-process-mode: off
external-controller: 0.0.0.0:9090
secret: # 登陆ui的密码
external-ui: ui # webui的基础路径
external-ui-name: xd # webui的下级路径
external-ui-url: https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
unified-delay: true
tcp-concurrent: true
experimental:
sniff-tls-sni: true
geodata-mode: true
geodata-loader: standard
geox-url:
geoip: https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat
geosite: https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat
mmdb: https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb
profile:
tracing: true
store-selected: true
store-fake-ip: true
sniffer:
enable: true
parse-pure-ip: true
override-destination: true
dns:
enable: true
ipv6: false # 禁止DNS解析IPv6
listen: 0.0.0.0:7874 # DNS监听端口
use-hosts: true
enhanced-mode: redir-host
default-nameserver: # 建议修改为你最近的DNS服务器
- 10.0.0.1
- 101.226.4.6
- 114.114.114.114
- 116.228.111.118
- 223.5.5.5
nameserver: # 建议修改为你最近的DNS服务器
- 10.0.0.1
- 101.226.4.6
- 114.114.114.114
- 116.228.111.118
- 223.5.5.5
fallback:
- https://dns.cloudflare.com/dns-query
- tls://dns.google:853
- https://1.1.1.1/dns-query
- tls://1.1.1.1:853
- tls://8.8.8.8:853
- https://public.dns.iij.jp/dns-query
- https://jp.tiar.app/dns-query
- https://jp.tiarap.org/dns-query
- tls://jp.tiar.app
- tls://dot.tiar.app
fallback-filter:
geoip: true
geoip-code: CN
geosite:
- gfw
ipcidr:
- 0.0.0.0/8
- 10.0.0.0/8
- 100.64.0.0/10
- 127.0.0.0/8
- 169.254.0.0/16
- 172.16.0.0/12
- 192.0.0.0/24
- 192.0.2.0/24
- 192.88.99.0/24
- 192.168.0.0/16
- 198.18.0.0/15
- 198.51.100.0/24
- 203.0.113.0/24
- 224.0.0.0/4
- 240.0.0.0/4
- 255.255.255.255/32
domain:
- +.google.com
- +.facebook.com
fake-ip-filter:
- +.*
proxies: # 以下为你的代理节点、分组及代理规则
proxy-groups:
rules:
|
clash其他相关文件
clash
还需要一些配套文件,在启动前先下载下来。我们将在 本系列第(七)篇 用定时任务来自动更新这些文件。
1
2
3
4
5
6
7
8
9
10
|
cd /var/lib/clash
wget -q --progress=bar:dot --show-progress -O country.mmdb https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country.mmdb
wget -q --progress=bar:dot --show-progress -O geosite.dat https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat
wget -q --progress=bar:dot --show-progress -O geoip.dat https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat
mkdir -p ui
cd ui
wget -q --progress=bar:dot --show-progress -O xd.zip https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
unzip -oqq xd.zip
mv metacubexd-gh-pages xd
|
启用服务
完全配置好以后,我们可以设置 /etc/systemd/system/clash.service
为开启自动启动,并立即启动起来。
1
|
systemctl enable --now clash.service
|
后续如想查看日志,我们直接使用Debian自带的工具来查看:
1
|
journalctl -efu clash.service
|
如果日志报错 ip: RTNETLINK answers: File exists
,请手动将旧的ip路由及ip规则清空,其实就是多执行几次 /etc/systemd/system/clash.service
中 ExecStop
这一行的命令,出现 RTNETLINK answers: No such process
和 RTNETLINK answers: No such file or directory
为止,表示旧的ip路由及ip规则都清空了。
1
|
nft flush ruleset ; ip route del local default dev ens18 table 100 ; ip rule del fwmark 1 lookup 100
|
如果想要重启:
1
|
systemctl restart clash.service
|
查看当前的 nftables
规则,如果为空可以手动输入 mynftables.nft
以查看错误,如有错误,请自行根据输出排故。
访问webui:http://<IP>:9090/ui/xd
,其中 9090
ui
xd
均在配置文件中由你定义,分别对应 external-controller
external-ui
external-ui-name
。
执行逻辑
大致在 mynftables.nft 这个文件的注释也解释过每句的作用了,这里再捊一下。
对照着题图,局域网中的其他客户端的流量在通过旁路由时,进入 nftables
的 prerouting
链,然后判断第69-76行的规则,如果规则匹配就以 accept
直接收受并发往目的地,不会再转到 clash
;如果全都不匹配,会在最后匹配上 goto clash_tproxy
,也就是进入 ip protocol { tcp, udp } tproxy to :$tproxy_port meta mark set $default_mark
这句,这句将会把流量转到 clash
。需要提一句的是第70行的规则是针对从本机 output
链发过来的流量的,没有第70行,这部分流量同样会在最后匹配到第77行的 goto clash_tproxy
。
经过 clash
代理后,流量会被打上标记 clash_mark
,也就是第13行定义的 666
,clash
工作在题图中的应用层,经过 clash
处理过的流量会在 output
链中被第89行直接接收并发往目的地,不会再一次进行代理,这样就防止了流量一直在走环路的问题。
本机产生的流量类似,它们会在 output
链中先匹配第88-94行,如果匹配就直接接收并发往目的地了,而剩余没有匹配到的会在第95行被打上 $default_mark
,也就是在第16行定义的 1
,这个标记和上方创建的 clash.service
中 fwmark 1
的 1
一致。被打上这个标记的流量会按照 clash.service
定义的 ip route
和 ip rule
,重路由进入 prerouting
链,然后根据规则确定是直接接收还是转向 clash
。
至于 forward
转发链,我们并没有设置它,那么转发的默认 policy
就是 accept
,将直接全部接受。我已经在 前文(一) 中提及了我只在主路由爱快上设置防火墙:IPv4、IPv6,旁路由对这类流量直接转发,不再过滤。所以,针对运行在我NAS 10.0.0.11
上的服务以及其他众多运行在 Docker MacVLAN 网络上的容器,我直接从主路由爱快转发端口到它们即可,无需再经过旁路由额外中转。
参考资料
-
nftables wiki
-
nft Man Page
-
clash nftables 透明代理(TPROXY)
-
Project X 透明代理(TProxy)配置教程
系列