得益于vyos内置的容器(Podman)支持, 给vyos扩展功能还是挺简单的. 再加上它是一个基于Debian的标准Linux发行版, 很多功能都可以用apt
补充, 综合起来这个路由器系统还是挺好用的 (毕竟免费嘛).
自建docker仓库
本来我是想用podman load
命令加载一个离线构建的tinc镜像的, 然而使用podman load
导入的镜像并不能被vyos的配置系统识别到. 就是说, 使用podman load tinc.tar.gz
, 然后用标准的配置指令set container name tinc image my-namespace/tinc:latest
, 它会提示找不到镜像. 而如果是按照它的报错提示运行add container image my-namespace/tinc:latest
的话, 则会直接去docker.io
拉取镜像 (但是怎么能拉取得到呢?)
所以就需要自建一个docker仓库了. 自建仓库很简单, 用sonatype/nexus3就可以了. 不过这里有一个"坑"点, 新版的nexus3建议你给仓库的路由方式选用Path-Based Routing方式, 然而我试了N多错误路径之后发现, vyos上的podman
在请求manifest
信息时死活不会按照它支持的路径请求, 所以这边建议为了节约生命, 统一使用Port Connectors.
推荐的最佳实践是做一个hosted, N个proxy (例如一个docker.io和一个ghcr.io), 最后用一个group给统一起来. 这个方案挺不错的, 新版的nexus3还支持在webui中设置HTTP代理, 这样就可以很方便的给docker-cli挂代理了.
给vyos设置镜像的配置命令:
set container registry 192.168.1.2:8082 authentication password 'password'
set container registry 192.168.1.2:8082 authentication username 'username'
set container registry 192.168.1.2:8082 insecure
set container registry 192.168.1.2:8082 mirror address '192.168.1.2'
set container registry 192.168.1.2:8082 mirror port '8082'
这里需要注意一个事情, vyos的podman在拉取镜像时默认会向registry name
发起请求. 这个操作就很不理解, 比如你如果命名的registry name
是example.com
, 那么它就会向example.com
发起请求拉取镜像, 而不是向mirror address
. 说实话这一点我没理解为何要这样设计. 但是为了适应这个规则, 在registry name
里就要把仓库的IP和端口都写上了.
容器配置
这块没啥好说的, 直接上配置代码:
set container name vpn-tinc allow-host-networks
set container name vpn-tinc arguments '-D -n tinc_net'
set container name vpn-tinc capability 'net-admin'
set container name vpn-tinc device tun destination '/dev/net/tun'
set container name vpn-tinc device tun source '/dev/net/tun'
set container name vpn-tinc image '192.168.1.2:8082/my-namespace/tinc'
set container name vpn-tinc volume tinc-etc destination '/etc/tinc'
set container name vpn-tinc volume tinc-etc source '/config/user-data/container/vpn/tinc/data/etc/tinc'
set container name vpn-tinc volume tinc-localtime destination '/etc/localtime'
set container name vpn-tinc volume tinc-localtime source '/etc/localtime'
set container name vpn-zerotier allow-host-networks
set container name vpn-zerotier capability 'net-admin'
set container name vpn-zerotier command '<这里是zerotier的网络id>'
set container name vpn-zerotier device tun destination '/dev/net/tun'
set container name vpn-zerotier device tun source '/dev/net/tun'
set container name vpn-zerotier image 'zerotier/zerotier:latest'
set container name vpn-zerotier volume zerotier-lib destination '/var/lib/zerotier-one'
set container name vpn-zerotier volume zerotier-lib source '/config/user-data/container/vpn/zerotier/data/var/lib/zerotier-one'
tinc配置
在tinc的配置方面, 一切按照标准的Linux上的操作即可. 不过这里有几个地方需要注意
- 我给的上述配置里并没有生成公私钥对的操作, 所以需要自己生成公私钥对. 可以通过
podman exec
命令生成 - [重要] 在tinc.conf中必须指定
Interface = ethN
, 这里的N
是一个数字, 并且不能和主机原有的eth口冲突 - 可以不在tinc-up.sh中给接口配置IP, 因为在vyos的配置界面我们还要给接口添加ip
zerotier配置
zerotier的配置也比较简单, 和标准的Linux上操作不同的也是要给接口改个名字叫ethN. 这一点也是参考了社区的一篇文章: Level Zero Networking: Dynamic Multipoint VPN with ZeroTier and VyOS
# 容器中的文件路径是: /var/lib/zerotier-one/devicemap
cat >devicemap
# Example: <networkID>=<interface>
xxxxxxxxxxxxxxxx=eth10
接口配置
按照上面操作拉起两个容器之后, 使用show interface
就能看到两个VPN生成的eth接口了. 之后进入配置界面配置一下接口就可以了:
set interfaces ethernet eth10 description 'Zerotier VPN'
# 为了能和其它主机上的zerotier接口通信, 这里需要设置成2800
set interfaces ethernet eth10 mtu '2800'
# tinc接口的IP也可以在tinc-up.sh中设置
set interfaces ethernet eth11 address '172.16.1.2/25'
set interfaces ethernet eth11 description 'Tinc VPN'
这里要注意一个, 使用vyos接管了zerotier的接口之后, 它把接口的MTU设置成5000了, 然而对端的MTU都是2800, 这里我指定了一下MTU的值就能通信了.
防火墙配置
防火墙的配置就不多说了, 我是为VPN接口新建了一个VPN域, 基本策略是: 放行LAN到VPN的双向流量/拒绝VPN到WAN的双向流量/限制VPN到LOCAL的流量.
# 此处省略部分配置, 只显示VPN相关配置
set firewall ipv4 name LAN-VPN default-action 'accept'
set firewall ipv4 name LOCAL-VPN default-action 'accept'
set firewall ipv4 name VPN-LAN default-action 'accept'
set firewall ipv4 name VPN-LOCAL default-action 'drop'
set firewall ipv4 name VPN-LOCAL rule 10 action 'accept'
set firewall ipv4 name VPN-LOCAL rule 10 protocol 'ospf'
set firewall ipv4 name VPN-LOCAL rule 20 action 'accept'
set firewall ipv4 name VPN-LOCAL rule 20 state 'established'
set firewall ipv4 name VPN-LOCAL rule 20 state 'related'
set firewall ipv4 name VPN-LOCAL rule 30 action 'accept'
set firewall ipv4 name VPN-LOCAL rule 30 protocol 'icmp'
set firewall ipv4 name VPN-LOCAL rule 40 action 'accept'
set firewall ipv4 name VPN-LOCAL rule 40 destination port '22'
set firewall ipv4 name VPN-LOCAL rule 40 protocol 'tcp_udp'
set firewall ipv4 name VPN-LOCAL rule 50 action 'drop'
set firewall ipv4 name VPN-LOCAL rule 50 description '不允许mesh-vpn之间互相走'
set firewall ipv4 name VPN-LOCAL rule 50 destination port '9993,655'
set firewall ipv4 name VPN-LOCAL rule 50 disable
set firewall ipv4 name VPN-LOCAL rule 50 protocol 'tcp_udp'
set firewall ipv4 name VPN-WAN default-action 'drop'
set firewall ipv4 name WAN-VPN default-action 'drop'
set firewall ipv4 name WAN-VPN rule 20 action 'accept'
set firewall ipv4 name WAN-VPN rule 20 state 'established'
set firewall ipv4 name WAN-VPN rule 20 state 'related'
# ipv6是一样的就不写了
set firewall zone LAN from VPN firewall name 'VPN-LAN'
set firewall zone LAN member interface 'br0'
set firewall zone LOCAL from VPN firewall name 'VPN-LOCAL'
set firewall zone LOCAL local-zone
set firewall zone VPN description 'mesh vpn'
set firewall zone VPN from LAN firewall name 'LAN-VPN'
set firewall zone VPN from LOCAL firewall name 'LOCAL-VPN'
set firewall zone VPN from WAN firewall name 'WAN-VPN'
set firewall zone VPN member interface 'eth11'
set firewall zone VPN member interface 'eth10'
这里我默认禁止了从VPN域进入主机的流量, 尤其是单独禁止了到主机9993和655端口的流量. 主要是防止zerotier和tinc的流量走VPN接口过来, 避免这俩mesh vpn误认为两台设备处于同一个局域网中出现套娃的情况. 当然我也不知道这样子操作对不对就是了
补充Linux上的操作
因为我的对端提供OSPF的设备是一台Ubuntu Server服务器, 所以也需要配置防火墙. 我用的ufw
配置防火墙, 需要放行OSPF流量. 然而ufw
防火墙的cli接口功能有限, 放行OSPF流量需要在配置文件里加规则:
# 在/etc/ufw/before.rules的filter配置表中添加规则
# Allow OSPFv2 only on specific interfaces
-A ufw-before-input -i enp1s0 -p 89 -j ACCEPT
-A ufw-before-input -i <这里是zerotier的接口> -p 89 -j ACCEPT
-A ufw-before-input -i tinc -p 89 -j ACCEPT
# 同理, 在/etc/ufw/before6.rules的filter配置表中添加规则
# Allow OSPFv3 only on specific interfaces
-A ufw6-before-input -i enp1s0 -p 89 -j ACCEPT
-A ufw6-before-input -i <这里是zerotier的接口> -p 89 -j ACCEPT
-A ufw6-before-input -i candy -p 89 -j ACCEPT
OSPF配置
vyos的ospf配置很简单
set protocols ospf area 0 network '192.168.1.0/24'
set protocols ospf area 0 network '172.16.1.1/25'
set protocols ospf area 0 network '172.17.1.1/24'
set protocols ospf interface br0
set protocols ospf interface eth10 cost '10'
set protocols ospf interface eth11 cost '20'
set protocols ospf parameters router-id '192.168.1.1'
补充Linux上的操作
Linux上用bird
提供OSPF, 我用的是1.6版本的, 把ip a
的运行结果发给gpt, 让gpt给写一个配置就可以了:
protocol kernel {
scan time 60;
import all;
export all; # Actually insert routes into the kernel routing table
}
protocol device {
scan time 60;
}
router id 192.168.0.2;
protocol direct {
interface "enp1s0";
interface "<这里是zerotier的接口名称>";
interface "tinc";
}
protocol ospf {
import all;
area 0.0.0.0 {
interface "enp1s0" {
type broadcast;
cost 10;
};
interface "zerotier的接口名称" {
type broadcast;
cost 20;
};
interface "tinc" {
type pointopoint;
cost 30;
};
};
}
之后在vyos上使用show ip route ospf
和show ip ospf neighbor
检查路由表和邻居是否正常.
在Linux上则使用sudo birdc show ospf neighbors
和sudo birdc show route
查看.
另外, 在Linux上我遇到一个问题. 当我查看bird学习到的路由表时, 对端路由器网段192.168.1.0/24已经学到了; 然而使用命令ip route
查看本机路由表却没有这条项目. 这就导致两边的LAN是不互通的. 问题的原因就是没有在bird.conf
中把bird的路由表导出到系统路由表里, 所以就需要在protocol kernel里面加一个export all