基于PVE的新网络架构 - 踩坑回顾
![Featured Image](/assets/images/size/w1000/2021/03/image.png.webp)
我美好的寒假的第一天,毁于那多灾多难的 OpenWRT:当我第七七四十九次折腾 OpenWRT 上的隧道而他又莫名其妙的开始爆炸时,我怒了。
![](../assets/images/2021/03/image.png.webp)
我实在是搞不明白,在 Windows 下面轻轻松松跑个三四百兆的 L2TP 连接,怎么到了 OpenWRT 上下载就只有 20M 不到,上传一跑直接爆炸。
冥冥之中,有一股神秘的声音(其实是西瓜)在我耳畔低语:为什么不试试上虚拟化的 RouterOS 呢?
尽管西瓜一再抨击 Proxmox VE 而向我推销他的 Xen,但是因为我对 PVE 更加熟悉,最后还是选择了 PVE (事实证明这是好的)
于是我做出了初步的计划:在 PVE 上跑 RouterOS + OpenWRT;RouterOS 负责打隧道(PPPoE、L2TP、PPTP、GRE),OpenWRT 负责做透明代理;用 DHCP 对不同的设备分配不同的网关达到分流和故障控制的效果。
上网下了一个PVE,找了一个 6.44.2 的 L5 授权破解版(和卖盗版电子盘的原理有异曲同工之妙,都是改硬盘序列号),写盘、装系统、部署一气呵成,是 BGP Player 中的豪杰。
后续 AJ 大哥告诉我 CHR 版本也可以无限白嫖,是一个更好的选择,不过这时候我已经配好了,反正都是一样的用,如果没有什么安全问题就不换 CHR 了。
RouterOS 的配置十分简单便捷,J1900 软路由的四个接口分别对应一个 Linux Bridge 再分配到 ROS 的 VM 上就行了。在这个阶段我只踩到了一个坑:ether5 上的 vlan33 里面有一台主机 192.168.6.99,但是我给 vlan33 配了 192.168.6.1/24 之后还是 ping 不通这台机子,torch 能看到这台机子往外发包。排查了十分钟之后我把接口的 loop detect 给关了,问题解决。
接下来是 OpenWRT,本来也打算用 VM 的,但是我思考了半秒,发现:
既然 PVE 宿主机跑的是 Linux,OpenWRT 跑的也是 Linux,我可不可以用什么容器化技术跑它呢?
然后我想起来 PVE 的 LXC 容器,参考了一下张大妈这篇和恩山这篇文章 ,根据以下几步,十分轻易地搭建了起来。
- 选一个喜欢的 x86 固件,官方版也可以
- 下载这个固件的 squashfs 版本,官方的好像可以直接下 rootfs
- unsquashfs 把固件提取出来,再次打包成 rootfs.tar.gz
- 把打包好的或者官方的 rootfs 上传到 PVE
- 用控制台新建 LXC 容器(参考恩山文章)即可
搭建是搭建好了,我也能访问 LuCI,下一个问题接踵而来:居然没有 NAT?!
事出反常必有妖,这个问题被我从多角度全方位进行了排查:
- 重启防火墙、调整防火墙区域:无效
- 检查 OpenWRT 自己可不可以上网:可以
- 关闭 RouterOS 让 OpenWRT 单独做网关:无效
- 手动添加 SNAT MASQUERADE:可以
问题出现了进展,我开始怀疑这个固件的防火墙有 BUG,不会自动加 NAT
- 换其他第三方固件:一样没有 NAT
- 用 VM 模式启动 OpenWRT:可以
到这里我本来已经打算妥协,抛弃 LXC 投奔 VM 了。但是我最后又尝试了一次,把“启用 FullCone-NAT” 给取消了,你猜怎么着,添加成功了。
![](../assets/images/2021/03/image-1.png.webp)
瞬间我又有了折腾的动力,顺着这条线索,我去看了看这个功能所依赖的 LGA1150/fullconenat-fw3-patch
if (zone->fullcone && (access("/usr/lib/iptables/libipt_FULLCONENAT.so", 0) == 0)) {
r = fw3_ipt_rule_new(handle);
fw3_ipt_rule_src_dest(r, msrc, mdest);
fw3_ipt_rule_target(r, "FULLCONENAT");
fw3_ipt_rule_append(r, "zone_%s_postrouting", zone->name);
r = fw3_ipt_rule_new(handle);
fw3_ipt_rule_src_dest(r, msrc, mdest);
fw3_ipt_rule_target(r, "FULLCONENAT");
fw3_ipt_rule_append(r, "zone_%s_prerouting", zone->name);
} else {
r = fw3_ipt_rule_new(handle);
fw3_ipt_rule_src_dest(r, msrc, mdest);
fw3_ipt_rule_target(r, "MASQUERADE");
再手动尝试加载这个内核模块
root@CTOpenWrt:~# modprobe xt_FULLCONENAT
no module folders for kernel version 5.4.73-1-pve found
一切问题都浮出水面了。
让我来讲讲这里面的道理:
- 如果在 LuCI 里面选择启用 FullCone NAT
- 原来的行为是添加一个-j MASQUERADE,则改为添加一个-j FULLCONENAT
- 我选择了使用,他尝试添加一个-j FULLCONENAT
- 但是因为内核模块加载失败,所以没有对应的 target,也就添加失败了
- 这个 patch 没有检测添加失败的情况,不会 fallback 到普通的 MASQUERADE
- 我失去了 NAT
那么解决问题的第一步是让他加载上内核模块,我尝试过寻找对应 PVE 内核版本的编译好的 kmod,但是很显然并不存在这种东西,那么就自己编译吧!
- 找到这个 patch 实际依赖的内核模块是这个 Chion82/netfilter-full-cone-nat
- 尝试编译,编译出错,缺少头文件
- 翻箱倒柜找到了对应版本的 Header(反正 apt 源里没有,好像要钱)
- 再次编译,编译成功,insmod 成功
![](../assets/images/2021/03/image-2.png.webp)
然后,这就是 ROS + OpenWRT 做的完美 Full Cone NAT
![](../assets/images/2021/03/image-3.png.webp)
要达到这样的效果,只需要在 RouterOS 防火墙的 NAT 规则最后加上一条把所有 (没有匹配 ROS 自己做的 NAT 及其他规则的) UDP 都转发到 OpenWRT 上去就可以了。
顺带一提我发现这个 OpenWRT 也没办法直接 PPPoE 拨号,因为 PPP 也依赖内核模块实现,同样自己编译然后加载应该就能解决问题。不过这部分工作我是交给 ROS 做的,所以没有实战解决,欢迎大家自己来踩坑试试(不是)
以上就是我的所有踩坑内容了,目前为止运行了 35 天都非常稳定,彻底摆脱了一周不重启就有各种各样奇奇怪怪毛病的日子。目前来看 J1900 是足够应付 200M 宽带 + 透明网关代理的,如果不够升级一下硬件就行,这套架构是没什么问题的。
![](../assets/images/2021/03/image-4.png.webp)
以上内容,因为行文仓促,难免有谬误,欢迎大家在评论留言指正