Serenader

Learning by sharing

使用 PVE 运行 Clash 旁路由虚拟机实现透明代理

post cover
在没折腾旁路由之前,家里的翻墙都是在路由器上安装 SS 服务,在路由层面实现代理,这样联网的设备都可以享受到透明代理的好处,即不需要安装任何软件就能愉快地访问境外网站。但是这个方案有个致命的缺点就是,路由器的性能太差,远远不能跑满带宽的速度。
以我手头的设备来说,路由器是华硕的 AC86U ,讲道理这个路由器性能是不差的,两年前花了800+ RMB 买的,但是运行 SS 的话,速度连 100 Mbps 都跑不到,而目前家里的宽带下行 500 Mbps 上行 50 Mbps ,路由器的性能成了瓶颈。
后来在买了云轩的迷你主机之后才了解到可以在它上面运行旁路由实现透明代理,体验了一番结果大吃一惊,旁路由的翻墙性能比 AC86U 好太多,直接就跑满到了带宽的极限速度。
只是旁路由的安装过程还是蛮折腾的,绕了不少弯,因此在此记录一下方便后人学习,也方便自己以后查阅。

开始之前

在开始讲过程之前,先交代一下设备要求。其实旁路由本质上就是将某个设备当作网关,在上面安装了翻墙软件,然后在需要翻墙的设备上手动设置网关跟 DNS ,以此来实现透明代理。
这个旁路由设备可以是一个实体硬件,也可以是一个虚拟机,两者没什么太大的差别。你可以使用 ESXi,Proxmox VE,或者群晖的 VMM 来创建这个虚拟机,因为道理都是一样的。
由于我自己是使用 PVE ,因此这里以 PVE 为例。
除此之外再说一下旁路由系统选择以及软件选择方面,目前比较主流的方案有:
  • 使用 LEDE 系统运行 koolss 软件
  • 使用 LEDE 系统运行 koolclash 软件
  • 直接使用 Linux 系统运行 Clash 软件
软件方面,在体验过 koolss 跟 koolclash 之后果断选择了后者,因为后者有更加灵活的节点配置和规则配置,UI 更加友好,以及有实时流量记录和实时的日志可以查询。但是在使用 koolclash 的时候碰到一个很奇怪的问题是,软件都是设置好了,但是设备只能访问一些境外网站。而某些境外网站,比如 Google,Twitter,Facebook 等就不能访问。网上搜索了一番无果,便寻找其他解决方案。最后找到了一篇文章,方案是直接在 Linux 系统上运行 Clash ,经过一番折腾发现这个方案可行,因此最终决定使用这套方案。
所以这篇文章以在 debian 系统下安装 Clash 为例。

软件准备

由于在各大平台上创建虚拟机的步骤不太一样,而且创建虚拟机的方法也很简单,因此创建 debian 虚拟机的过程就略过。
装好 debian 系统之后需要设置一下系统的 IP 地址为静态地址,以我自己的为例,我将 debian 旁路由的 IP 地址设为 192.168.1.80 ,主路由器地址为 192.168.1.1 ,具体设置方法是直接编辑 /etc/network/interfaces
# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug ens18
iface ens18 inet static
	address 192.168.1.80/24
	gateway 192.168.1.1
	# dns-* options are implemented by the resolvconf package, if installed
	dns-nameservers 192.168.1.1
其中 ens18 是我 debian 虚拟机的网卡名称,每个人的网卡名称可能不一样。如果想查看自己的网卡名称可以通过执行 ip link show 查看。
编辑完之后需要重启一下网络:
sudo /etc/init.d/networking restart
这时候如无意外 debian 的 IP 就变成了我们指定的 192.168.1.80 了。
网络准备好之后,需要先下载 Clash 软件,并且把它设成可执行的权限:
# 下载最新版 clash,注意根据自己的系统下载对应的版本,我的是 64 位的,所以下载的是 linux-amd64 这个版本
wget https://github.com/Dreamacro/clash/releases/download/v0.17.1/clash-linux-amd64-v0.17.1.gz
# 解压并且把二进制文件放到 /usr/bin ,并且加上可执行权限
gzip -d clash-linux-amd64-v0.17.1.gz
sudo mv clash-linux-amd64-v0.17.1 /usr/bin/clash
sudo chmod +x /usr/bin/clash
# 为 clash 添加绑定低位端口的权限,这样运行 clash 的时候无需 root 权限
sudo setcap cap_net_bind_service=+ep /usr/bin/clash

创建 clash 配置文件

运行 clash 之前需要先创建配置文件,否则 clash 无法启动:
# 创建文件夹
mkdir -p ~/.config/clash
cd ~/.config/clash
# 创建配置文件
touch config.yaml
vim config.yaml
config.yaml 文件里面填入下面的配置:
# 以下部分不要修改!
port: 7890
socks-port: 7891
redir-port: 7892
allow-lan: true

mode: Rule

log-level: info
# external-controller 主要是用于 web 端管理页面,必须监听在 0.0.0.0
external-controller: 0.0.0.0:9090

# secret 是进入管理面板所需要的密码,可填可不填,建议填上
secret: "secret-password"

# external-ui 表示管理面板的路径
external-ui: dashboard

dns:
  enable: true
  ipv6: false
  listen: 0.0.0.0:53
  enhanced-mode: fake-ip
  fake-ip-range: 198.18.0.1/16
  nameserver:
    - '8.8.8.8'

# 下面部分则是代理的设置跟规则的设置,这里忽略不写。
Proxy:
...
这里需要注意的是, dns 部分的 nameserver 填的是主路由器的 IP ,对我来说是 192.168.1.1 ,可能每个人的 IP 不一样,请根据实际情况填写。至于为什么需要填路由器的 IP,后面会讲。
创建好配置文件之后还需要下载管理面板的前端代码到本地上,这样运行 clash 之后就可以通过 http://192.168.1.80:9090/ui/#/ 路径访问到了。
目前管理面板使用的比较多的有两个,一个是官方的管理面板,另外一个是第三方开发者开发的面板,基于好看的原则,最后我选择了 haishanh/yacd 的面板。具体安装方式是:
# 先进入到配置文件的目录
cd ~/.config/clash
# 下载前端代码压缩包,如果要使用官方的管理面板则把链接替换成 https://github.com/Dreamacro/clash-dashboard/archive/gh-pages.zip
wget https://github.com/haishanh/yacd/archive/gh-pages.zip
# 解压缩并且把目录名改成 dashboard
unzip gh-pages.zip
mv yacd-gh-pages/ dashboard/
至此配置文件跟管理面板都配置好了,接着需要设置对应的转发和路由。

设置转发和路由

编辑 /etc/sysctl.conf 文件开启转发功能:
sudo vim /etc/sysctl.conf
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/f018c061-b9d8-4293-92e2-dfc587cd14fc_WX20200104-1223492x.png
找到 net.ipv4.ip_forward=1 这一行,取消掉注释。或者最简单办法直接在文件顶部或者尾部加多这么一行配置进去,然后保存就行了。
为了让刚刚的修改生效,需要执行:
sudo sysctl -p
这样就开启了转发了。接着还需要开启 iptables 的路由,依次执行:
iptables -t nat -N clash
iptables -t nat -N clash_dns

iptables -t nat -A PREROUTING -p tcp --dport 53 -d 198.19.0.0/24 -j clash_dns
iptables -t nat -A PREROUTING -p udp --dport 53 -d 198.19.0.0/24 -j clash_dns
iptables -t nat -A PREROUTING -p tcp -j clash

# 这里需要注意的是,下面两行最后的 192.168.1.80 是当前旁路由的 IP 地址,请根据你自己的实际情况修改
# 如果你自己的旁路由 IP 跟下面的 IP 地址不对的话会造成无法翻墙
iptables -t nat -A clash_dns -p udp --dport 53 -d 198.19.0.0/24 -j DNAT --to-destination 192.168.1.80:53
iptables -t nat -A clash_dns -p tcp --dport 53 -d 198.19.0.0/24 -j DNAT --to-destination 192.168.1.80:53

iptables -t nat -A clash -d 0.0.0.0/8 -j RETURN
iptables -t nat -A clash -d 10.0.0.0/8 -j RETURN
iptables -t nat -A clash -d 127.0.0.0/8 -j RETURN
iptables -t nat -A clash -d 169.254.0.0/16 -j RETURN
iptables -t nat -A clash -d 172.16.0.0/12 -j RETURN
iptables -t nat -A clash -d 192.168.0.0/16 -j RETURN
iptables -t nat -A clash -d 224.0.0.0/4 -j RETURN
iptables -t nat -A clash -d 240.0.0.0/4 -j RETURN

iptables -t nat -A clash -p tcp -j REDIRECT --to-ports 7892
执行完上面的 iptables 命令之后,就完成了旁路由的路由功能了,但是此时 iptables 并没有永久保存,下次开机上面的配置就会丢失。为了使得重启之后 iptables 命令仍然存在,我们需要安装软件来实现:
sudo apt install iptables-persistent
安装的过程中会提示你是否需要保存 iptables 配置,直接选是就行。这时候即使电脑重启了也会应用这些路由规则。
如果后面你有需要重新修改 iptables 的配置,那么只需要在执行完 iptables 之后再执行:
sudo iptables-save > /etc/iptables/rules.v4
即可将最新的 iptables 规则保存下来。

启动 clash 并且设置开机自启

由于我的旁路由系统是 debian,因此这里我使用 systemctl 来管理 clash 服务。首先创建 clash 服务文件:
sudo touch /etc/systemd/system/clash.service
sudo vim /etc/systemd/system/clash.service
填入下面的内容:
[Unit]
Description=clash daemon

[Service]
Type=simple
User=YOUR USER NAME
ExecStart=/usr/bin/clash -d /home/YOUR USER NAME/.config/clash/
Restart=on-failure

[Install]
WantedBy=multi-user.target
需要注意的是,上面的 User 以及 ExecStart 需要根据你自身实际情况填写。User 则是表示需要以什么用户启动 clash,通常为非 root 用户。然后 ExecStart 里面 -d 参数后面的路径则是 clash 的配置文件路径。
编辑玩之后保存,然后启动 clash 并且开启开机自启:
# 启动 clash
sudo systemctl start clash.service
# 启动开机自启
sudo systemctl enable clash.service
如无意外的话这时候 clash 已经运行起来了,通过浏览器访问 http://192.168.1.80:9090/ui 就能看到 clash 的管理面板了。
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/96b121ba-20cf-42ee-ba7e-65c9d3005d89_Untitled.png

为手机等设备设置网关跟 DNS

只要把旁路由配置完,剩下的就是挨个修改联网设备的网关跟 DNS 了。在设备的网络设置里面手动指定 IP 以及网关和 DNS ,其中网关一定得设为旁路由的 IP,在我这里是 192.168.1.80 ,而且 DNS 分别设置为 198.19.0.1 和 198.19.0.2 ,如下图:
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/fb0bf1f5-7e98-423a-ad6f-55a9180ee1d3_E437EFF1-BC75-4B4F-9CC1-4F4F5444665F.jpeg
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/d5218066-6a19-48c1-9ede-dac48ece866f_64DF70C8-5AD0-4C6B-A0C5-C1376D2074EB.jpeg
上面的图片是 iPhone 的设置方法,其他设备的同理。配置完网络之后就可以访问一下 Google 或者 YouTube 看看是否能翻墙了。如果没意外的话设备就能够正常访问境外网站了,在 clash 的管理面板就能够看到实时流量,以及日志和连接了。
自己的 MacBook Pro 设置完网关之后,打开 YouTube 看视频的速度如下:
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/4f1186bf-ac36-419c-9828-f37bc4098a34_Untitled.png
最后跑个 Speedtest ,速度妥妥的达到了带宽的极限。
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/52bc0c27-1a32-4c4d-8215-89a441140063_73B07746-6D46-4002-914F-4F57BA905A66.jpeg
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/6caa0f1f-5398-492a-a28e-0a60c085423b_Untitled.png

Bonus:设置主路由下发旁路由的 IP 为默认网关

如果家里设备比较多的话,那么需要一个一个设备去修改网关跟 DNS 。好在,现在的路由器一般都可以设置默认的网关 IP,以梅林为例,直接在 DHCP 服务器上就可以设置默认网关:
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/e0cae3f0-a984-48a3-a86d-043f6c5bab08_Untitled.png
保存之后,所有通过 DHCP 联网的设备都会直接分配到旁路由的 IP 作为网关,实现联网就能翻墙。
但是对于 DNS 配置来说不能用这种方式,还是得通过手动指定 DNS 的办法来解决。其实如果不配置 DNS 的话设备也能够正常上网跟翻墙,只是设备就不能享受到 clash 的根据域名来动态分流的能力了,少了 DNS 配置的话 clash 接收到的都是 IP 地址,因此只能通过 IP 的策略去命中代理,如下图:
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/c180ef01-e866-4f9b-960b-fe620bbffd6e_Untitled.png

Bonus:增加自定义 DNS 解析能力

纵使 Clash 非常好用,但是目前它缺少一个很重要的功能,就是自定义 DNS 解析的能力。在 koolss 里面有一个非常好用的功能是自定义 dnsmasq ,你可以给某些域名解析到特定的 IP,比如我在自己的 Homelab 上搭建了 GitLab 服务,并且开放了外网访问的能力,这样在外网就可以通过域名访问到我的 GitLab 。但是当在内网的时候,我希望对于 GitLab 的域名来说能够解析到对应的内网 IP ,这样的话就不用走一遍外网网络了,速度会快很多。
/image/66ca2c10-54b0-4238-95fb-cccbd0c63dea/75410c61-e4a3-4362-80ac-8d0b441d37c1_Untitled.png
@02/28/2020 Update: 后来无意间发现 Clash 已经是支持自定义 hosts 功能的,之前没有仔细确认。因此在这里更新一下使用 Clash 实现自定义 hosts 功能的方法。
在 Clash 的 Github 的示例配置文件里面有这么一段代码:
# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com)
# hosts:
#   '*.clash.dev': 127.0.0.1
#   'alpha.clash.dev': '::1'
这个配置就是自定义 hosts 的功能。可能是还处于试验性的功能,文档并没有怎么提到这个功能,但是我自己试了一下确实是可以用的。因此就可以在 clash 的配置里面添加自己想要的自定义 hosts 配置了。
在 Clash 的 GitHub 仓库逛了一圈,发现之前有人提过这个 issue ,但是作者说因为功能复杂,会给程序增加复杂度所以就没做。看完之后简直想放弃 clash 了,因为我的手机装了群晖的套件,它会在后台帮我备份照片等。而我又是通过域名登录到群晖 NAS 的,如果不能将域名映射到内网 IP 的话,那么会导致备份速度非常慢。
后来我灵机一动,能不能让 clash 的 DNS 解析指向主路由,然后在主路由添加自定义的 dnsmasq 规则呢?实践证明这个方案是可行的!
首先,如果想要让 clash 的 DNS 解析指向主路由的话,只需要将 clash 配置文件里面的 dns nameserver 设置为主路由的 IP 就行:
dns:
  enable: true
  ...
  nameserver:
    - '192.168.1.1'
然后在主路由增加自定义 dnsmasq 配置。对于梅林固件的路由器来说,只需要在 /jffs/configs 目录下新增 dnsmasq.conf.add 配置文件即可:
user@RT-AC86U:/jffs/configs# cat dnsmasq.conf.add
address=/example.com/192.168.1.101
user@RT-AC86U:/jffs/configs#
至此,即使使用 clash 的透明代理也能够享受到自定义的 DNS 解析了,这样对于内网服务来说,在内网就是解析到内网 IP,在外网就是解析到外网 IP,实现智能切换。

Reference