使用 clash 和路由表实现透明代理

本文内容如有错误,请在评论区指出

要进行全局代理,常见的方式是转发到特定端口、使用 tun 虚拟设备和使用 TPROXY。clash 有 redir port 可以直接转发实现代理,但是只支持 TCP,IPv6 的支持也尚未合并。TPROXY 是 v2ray-core 的推荐方式。而另一种方式则是 tun。本文介绍的是使用 tun 的方式

要使用这种方式,首先需要一个使用 tun 转发流量的工具,目前 clash 主线并不支持 tun,clash 作者有一个支持 tun 的闭源版本,另外 comzyh 有一个支持 tun 的分支,性能不错,稳定性一般。然后有一个通用的工具 tun2socks,稳定性较好但性能一般。后两者都不错。

comzyh 的 clash 分支

go-tun2socks

基本的路由表、iptables 操作

首先需要使用 ip tuntap add <tun name> mode tun user <tun user> 创建一个 tun 设备,并使用 ip link set <tun name> up 启用。

然后设置路由 ip address replace <tun address> dev <tun name>,tun address 取一个不常用的 IP 段,比如 kr328 的脚本中使用 172.31.255.253/30,又比如 go-tun2socks 默认的 10.255.0.1/24。

然后设置路由规则

1
2
ip route replace default dev <tun name> table <route table id>
ip rule add fwmark <fwmark id> lookup <route table id>

把有特定 fwmark 标记的流量路由到 tun 其中 fwmark 号和 route 表号可以自行设定(不要和其他的规则重复)。

然后用 iptables 在 mangle 表上,把需要代理的流量打上 fwmark 标记,使用 -j MARK --set-mark <fwmark id> 即可

利用 iptables 支持的丰富规则,我们可以灵活地绕过各种流量,列出几个比较常用的:

  • -m owner --uid-owner <username or uid> 匹配某个用户的流量,类似的还有 --gid-owner
  • -p <'tcp', 'udp' or 'icmp'> 匹配某种类型流量
  • --dport <port num> 匹配目的端口,类似的 --sport 匹配源端口,需要和 -p tcp 等连用
  • -d <network name, hostname, subnet(IP CIDR) or IP> 匹配目的网络/主机名/子网/IP
  • -m set --match-set <ipset name> <'dst' or 'src'> 匹配 ipset,可以用于简化规则,ipset 用法自行搜索
  • -m cgroup --cgroup <cgroup id> 匹配 cgroup

有较多规则时可以创建规则链,并把 OUTPUT 或 PREROUTING 链中的流量转入该链(ip6tables 也一样):

1
2
3
iptables -t mangle -N <chain name>
iptables -t mangle -F <chain name>
iptables -t mangle -I OUTPUT -j <chain name>

然后在规则链上编写规则即可,需要打标记走代理就 -j MARK --set-mark <fwmark id>,需要绕过就 -j RETURN,一般建议先写绕过规则,最后无条件地打标记。注意一定要绕过 clash 的流量(建议 uid/gid 或 cgroup)

cgroup 是 Linux 内核的一个功能,这里需要用到它的 net_cls 子系统。使用起来简单,我们首先运行以下命令创建一个组:

1
2
3
mkdir -p /sys/fs/cgroup/net_cls/<cgroup name>
echo <cgroup id> > /sys/fs/cgroup/net_cls/bypass_proxy/net_cls.classid
chmod 666 /sys/fs/cgroup/net_cls/<cgroup name>/tasks

上述命令可以在开机时运行

这样,如果一个进程的 pid 在 /sys/fs/cgroup/net_cls/<cgroup name>/tasks 中,它就会被 iptables 的 -m cgroup --cgroup <cgroup id> 规则匹配到,并且这些进程的子进程 pid 会自动被添加。如果对安全比较敏感,你可以对该文件进行适当的权限控制。

可以使用一个脚本,以它作为 wrapper 来运行需要的命令

1
2
3
#!/bin/bash
echo $$ > /sys/fs/cgroup/net_cls/<cgroup name>/tasks
exec "$@"

与 v2ray 不同,clash 不能通过其规则把流量转到 clash 的内置 DNS,且 clash 不支持 sni 解析域名,需要内置 DNS 反查域名。

因此,需要使用 nat 表把 DNS 流量转发到 clash 的 DNS 端口,对 UDP 53 端口的流量,设置适当的绕过规则过滤后,-j REDIRECT --to-ports <clash dns port> 即可。

comzyh 的 clash 分支需要在配置文件中设置以下内容

1
2
3
tun:
  enable: true
  device-url: dev://<tun name>

go-tun2socks 用以下命令启动

tun2socks -tunName <tun name> -proxyServer <clash socks server> -tunAddr <tun address> -tunGw <tun gateway> -tunMask <tun mask> -tunPersist -blockOutsideDns

选一个不常用的 subnet,并取该 subnet 内两个不同地址作为 tun gateway 和 tun address。

比如要使用 172.31.255.253/30 子网。则 tun mask 为 255.255.255.252,tun gateway 可以用 172.31.255.253,tun address 可以用 172.31.255.254,默认参数对应的子网是 10.255.0.1/24