前言
使用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规则来实现。
Netfilter hooks
下载与安装
在 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
Copy 上述 wget
这一行默认下载的是 AMD64 v3
架构的包,你可以输入以下命令查看是否支持。如若不支持,可以下载名称中带 cgo
或者 compatible
的版本,说明 在此 。
1
/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 --help | grep supported
Copy 如若支持 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)
Copy 创建服务
clash.service
创建工作目录/var/lib/clash
。
1
mkdir -p /var/lib/clash
Copy 创建文件 /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
Copy 如果你比较讲究Linux哲学,可以把 ExecStart
改成下面这样,这时 clash
的配置文件为 /etc/clash/config.yaml
。
1
ExecStart = clash -d /var/lib/clash -f /etc/clash/config.yaml
Copy 如果不想以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
Copy 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
}
}
Copy 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 :
Copy 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
Copy 启用服务
完全配置好以后,我们可以设置 /etc/systemd/system/clash.service
为开启自动启动,并立即启动起来。
1
systemctl enable --now clash.service
Copy 后续如想查看日志,我们直接使用Debian自带的工具来查看:
1
journalctl -efu clash.service
Copy 如果日志报错 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
Copy 如果想要重启:
1
systemctl restart clash.service
Copy 查看当前的 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)配置教程
系列