Debian 路由器上使用 Clash 作为透明代理
安装好了 Debian 路由器之后,咱们是不是有一个应用是几乎玩软路由的人都喜欢的?没错,那就是透明代理。今天小玲就来教大家在安装完 Debian 路由器之后,通过 Clash 和 nftables 实现透明代理。本教程的设置均基于 《使用 Debian 作为路由器》的设置。
咱们要使用的 Clash 是 Docker 版的。这样安装起来更方便一些。Clash 模式采用 redir-host,因为小玲习惯了 redir-host 模式了,用 fake-ip 模式的话,查网站的 IP 地址会多多少少有一些不方便。
创建用户组
首先新建一个名为 clash
的用户组,因为之后咱们的 Clash 要不光能代理局域网内其他设备的流量,还要代理自身发出的流量。所以要让 Clash 运行在一个特定的用户组上。
sudo groupadd -r clash
查看 clash
用户组的 gid。
cat /etc/group | grep clash
咱们可以看到,小玲创建的 clash 用户组的 gid 是 997
。这个数值会根据你的环境的不同而不同,使用时请更改为你自己的 gid。
clash:x:997:
编写配置文件
首先先把 Clash 的配置文件 config.yaml
准备好,小玲这里准备了一个。注意这里的 DNS 监听端口是 1053
。待会咱们需要把 Dnsmasq 的上游 DNS 改为本机的 1053
端口。小玲不建议直接让 Clash 监听 53 端口,因为 Clash 的 hosts 功能没办法给一个域名同时指定 IPv4 和 IPv6 地址。如果你用不到 hosts 功能,让 Clash 直接监听 53 端口也是可以的,这样就可以让 Dnsmasq 不提供 DNS 服务,改由 Clash 提供 DNS 服务了。
mkdir -p ~/config/clash
vim ~/config/clash/config.yaml
port: 7890
socks-port: 7891
redir-port: 7892
tproxy-port: 7893
allow-lan: true
mode: rule
log-level: error
external-controller: 0.0.0.0:9090
secret: ""
ipv6: true
bind-address: "*"
hosts:
dns:
enable: true
listen: :1053
ipv6: true
enhanced-mode: redir-host
nameserver:
- udp://[2001:4860:4860::8888]:53
- udp://[2001:4860:4860::8844]:53
fallback:
fallback-filter:
geoip: true
ipcidr:
- 240.0.0.0/4
- 0.0.0.0/32
proxies:
proxy-providers:
proxy-groups:
rules:
编写 docker-compose.yml
文件。
vim ~/docker-compose.yml
version: "3"
services:
clash:
cap_add:
- NET_ADMIN
container_name: clash
image: dreamacro/clash:v1.7.1
# 这里的 997 请改成你的 gid。
user: "0:997"
network_mode: host
restart: always
volumes:
- ./config/clash:/root/.config/clash
- /etc/group:/etc/group:ro
- /etc/passwd:/etc/passwd:ro
# 这里省略已有的 service
这里之所以使用 1.7.1 的 Clash 是因为这个版本之后的 Clash,redir-host 模式不好用了。
修改 dnsmasq.conf
文件。
vim ~/config/dnsmasq/dnsmasq.conf
server=::1#1053
cache-size=0
cache-size=0
是为了禁止 Dnsmasq 缓存 DNS 查询结果,这样每次 DNS 请求都会转发到 Clash 那里。
编辑 /etc/rc.local
文件。
sudo vim /etc/rc.local
默认内容如下。
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
exit 0
将下面的内容添加到 exit 0
的前面。
ip rule add fwmark 1 lookup 100
ip route add local default dev lo table 100
ip -6 rule add fwmark 1 table 101
ip -6 route add local ::/0 dev lo table 101
Debian 11 里默认是没有 rc.local
文件的,而且 rc-local
服务器默认是关闭的1。如果没有文件的话请自己创建 /etc/rc.local
文件,并通过 sudo chmod +x /etc/rc.local
命令赋予执行权限。如果 rc-local
服务没有开启请通过 sudo systemctl enable --now rc-local
开启。
保存后执行一下刚刚添加的这 4 条命令,让这些命令立即生效。因为 /etc/rc.local
文件开机时系统都会执行一次,所以以后不用再手动执行这些命令了。
sudo ip rule add fwmark 1 lookup 100
sudo ip route add local default dev lo table 100
sudo ip -6 rule add fwmark 1 table 101
sudo ip -6 route add local ::/0 dev lo table 101
编辑 /etc/nftables.conf
文件。注意这里小玲只是省略的无关的规则,请对比原来的文件,把新规则添加进去。切勿直接用下面的内容覆盖原来的文件。
vim /etc/nftables.conf
table inet filter
delete table inet filter
table inet filter {
chain input {
type filter hook input priority filter; policy accept;
# 这里请添加下面这 2 条规则,防止有人扫描你的端口,
# 然后通过连接 HTTP 代理或者 Socks5 代理通过你的宽带上网。
iifname "ppp0" tcp dport { 7890, 7891, 7892, 7893, 9090 } drop
iifname "ppp0" udp dport { 7893 } drop
}
chain forward {
type filter hook forward priority filter; policy accept;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
table inet clash
delete table inet clash
table inet clash {
# 定义直连的 IP 段
set ipv4_addr {
type ipv4_addr
flags interval
elements = {
10.0.0.0/8,
127.0.0.0/8,
169.254.0.0/16,
172.16.0.0/12,
192.168.0.0/16,
240.0.0.0/4
}
}
set ipv6_addr {
type ipv6_addr
flags interval
elements = {
::ffff:0.0.0.0/96,
64:ff9b::/96,
100::/64,
2001::/32,
2001:10::/28,
2001:20::/28,
2001:db8::/32,
2002::/16,
fc00::/7,
fe80::/10
}
}
chain clash-tproxy {
# 不代理未指定地址,本地地址,广播地址,组播地址。
fib daddr type { unspec, local, anycast, multicast } return
# 不代理直连 IP 段。
ip daddr @ipv4_addr return
ip6 daddr @ipv6_addr return
# 不代理 NTP 服务器的端口,
# 小玲发现不加上这条规则会导致局域网内的设备无法通过 NTP 服务器同步时间。
udp dport { 123 } return
# 最后不符合上面条件的流量全部送入 Clash。
meta l4proto { tcp, udp } meta mark set 1 tproxy to :7893 accept
}
chain clash-mark {
# 不代理未指定地址,本地地址,广播地址,组播地址。
fib daddr type { unspec, local, anycast, multicast } return
# 不代理保留地址。
ip daddr @ipv4_addr return
ip6 daddr @ipv6_addr return
# 不代理 NTP 服务器的端口,
# 小玲发现不加上这条规则会路由器自身无法通过 NTP 服务器同步时间。
udp dport { 123 } return
# 最后不符合上面条件的流量打上标记
meta mark set 1
}
chain mangle-output {
type route hook output priority mangle; policy accept;
# 代理 TCP 和 UDP 协议,gid 不是 997,从本地发送到外部的流量。
# 这句是路由器自身的流量走透明代理的关键规则。
meta l4proto { tcp, udp } skgid != 997 ct direction original jump clash-mark
}
chain mangle-prerouting {
type filter hook prerouting priority mangle; policy accept;
# 代理从 lo 网卡和 enp1s0 网卡进入的 TCP 和 UDP 协议,从本地发送到外部的流量。
# 这句是局域网的设备的流量走透明代理的关键规则。
iifname { lo, enp1s0 } meta l4proto { tcp, udp } ct direction original jump clash-tproxy
}
}
要代理路由器自身发出的流量时请确保 Clash 发出的流量不经过透明代理,否则会引起回环。这就需要规则里的 gid 设置正确了。
之所以用 iifname { lo, enp1s0 }
限制入口网卡是为了防止 bridge 网络模式下的 Docker 容器发出的流量走透明代理。如果不加上这个限制会导致 bridge 网络模式下的 Docker 容器直接访问不了外网。小玲现在还没有办法让 bridge 网络模式下的 Docker 容器通过 Tproxy 走透明代理。如果你有办法,不妨在评论区告诉小玲。lo
网卡是必须写上去的,如果不写路由器自身发出的流量就不能走代理。enp1s0
请改成你的 LAN 口网卡名称。
启动 Clash
重启 Dnsmasq
sudo docker-compose -f ~/docker-compose.yml restart dnsmasq
启动 Clash
sudo docker-compose -f ~/docker-compose.yml up -d
重载 nftables。
sudo systemctl reload nftables
好了,现在在浏览器打开 http://yacd.haishan.me/?hostname=<你的路由器的 IP 地址>&port=9090&secret=#/
看看有没有流量数据。如果有,那么透明代理就设置成功啦。现在你的路由器已经能代理局域网和路由器自身的流量了。如果想让路由器上面的 Docker 容器也走透明代理,请把这个容器的网络模式设为 host
。