需求

年初,我将原来部署在老家的机柜搬迁到了我现在家里的机房,同时在老家装了一台 N150 的小主机,维持一些基本的服务,为了将老家的设备与我现在的机房接入到同一个三层内网中,我需要异地组网的工具。

调查了一圈,最终我还是选择了我比较熟悉的 OpenVPN。

有朋友就要问了:“哎呀,为什么不用 WireGuard / Tailscale / … 呢?又轻量又安全还方便,通信开销比 OpenVPN 小多了!”

这就不得不提到我做的一个测试了,我简单测试了一下 OpenVPN 与 WireGuard 建立的隧道性能,发现基本上没有差别,并且我现在使用的运营商虽然有公网 IPv4 地址,但是会识别 WireGuard 协议并且封禁。

拓扑规划

首先规划了两个可用区:AZ-HQAZ-DBHYAZ-HQ 是我现在家里的机房, AZ-DBHY 是老家的小主机

AZ-HQ 网段规划

网段用途
10.0.0.0/16虚拟机以及硬件设备,智能家居设备也在这个网段中,在三层交换机中再通过 vlan 划分 24 长度的子网
172.16.1.0/24用于 Kubernetes 集群的 LoadBalancer 地址
192.168.2.0/24用于客户端设备

AZ-DBHY 网段规划

网段用途
10.1.0.0/16虚拟机以及硬件设备,在软路由中再通过 vlan 划分 24 长度的子网
192.168.1.0/24用于客户端设备

完整拓扑

topo

OpenVPN Access

事实上 OpenVPN 有一个可以自托管的 Access 服务,可以实现我的需求,但是这个服务居然是闭源付费的,试用版虽然可以连接一个对等网络,但是闭源这事让人有些不快,我决定还是使用纯开源的 OpenVPN 通过配置来实现。

配置过程

基本思路

基本思路是需要在两端都配置一个 OpenVPN 的对等节点,并开启路由功能,允许将发往对端的包通过路由转发到 OpenVPN 的隧道中。

然后将从隧道中发出的包通过路由转发到网关,再经由网关到达目标主机。

在我的场景中,AZ-HQ 端需要配置一台 Linux VM 作为 OpenVPN 节点,而 AZ-DBHY 端使用 iKuai 路由中的 OpenVPN 服务即可。

加密套件生成

为了保障在公网上安全的通信,需要生成加密套件,包含以下内容:

  • CA
  • 服务端证书
  • 客户端证书
  • 加密 Key

我们使用 OpenVPN 开源的 Easy-RSA 来进行便捷的生成。

注意:为了方便演示,我使用了 nopass,实际生产使用中,建议设置密码。

初始化套件并构建 CA

easyrsa init-pki
easyrsa build-ca nopass

此时会在当前目录下生成一个 pki 目录,我将这个目录放置在 /etc/openvpn/pki

生成服务端证书

easyrsa gen-req server nopass
easyrsa sign-req server server

生成客户端证书

注意此处的客户端名称很重要,由于需要配置对等路由,此处最好明确客户端的名称,示例中我使用 az-dbhy

easyrsa gen-req az-dbhy nopass
easyrsa sign-req client az-dbhy

使用 openvpn 命令行生成 tls key

openvpn --genkey > ta.key

创建服务端配置文件

/etc/openvpn/server/server.conf

port 1194 # 通信端口
proto udp # 通信协议
dev tun   # 使用 tun 设备,便于进行路由转发
server 10.255.255.0 255.255.255.0 # 配置隧道网段,使用的是不与两端网络有冲突的网段

# 禁用压缩
comp-lzo no
allow-compression no

# 推送路由,将服务端的网络信息公告给对端
push "route 10.0.0.0 255.255.0.0"
push "route 172.16.1.0 255.255.255.0"
push "route 192.168.2.0 255.255.255.0" # User

# 认证证书
ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/server.crt
key /etc/openvpn/pki/private/server.key

# 加密
tls-crypt /etc/openvpn/ta.key
cipher AES-256-GCM
auth SHA256

# 探活
keepalive 1 5

# 允许通过 SIGUSR1 重启时保持 tun 设备和 key 文件
persist-key
persist-tun

# 日志
verb 3

# 客户端配置
client-config-dir /etc/openvpn/ccd

# 启动后 hook
script-security 2
up /etc/openvpn/scripts/up.sh

这里有三个关键点需要注意:

  1. 需要将服务端所在网络所有的子网都公告给客户端
  2. 通过 client-config-dir 中的配置将客户端的网络告知服务端
  3. 使用 up 启动脚本在服务端启动后自动设置静态路由

/etc/openvpn/ccd/az-dbhy

注意:这里的文件名与客户端证书中的 CN 名称必须一致,以便 OpenVPN 服务端在客户端连接上来时找到对应的客户端配置文件

ifconfig-push 10.255.255.2 10.255.255.1 # 设置隧道设备的 ip 地址

# 设置对端的网段
iroute 10.1.0.0 255.255.0.0
iroute 192.168.1.0 255.255.255.0

/etc/openvpn/scripts/up.sh

由于以上的配置文件都只是针对 OpenVPN 隧道本身的,不会改变操作系统上的静态路由,所以需要通过 up hook 来配置转发到对端的静态路由。

#!/usr/bin/env bash

/usr/sbin/ip route add 10.1.0.0/16 via 10.255.255.2 dev tun0
/usr/sbin/ip route add 192.168.1.0/24 via 10.255.255.2 dev tun0

exit 0

启动服务端

使用 systemctl start --enable openvpn-server@server 来启动 OpenVPN 服务端。

同时需要编辑 /etc/sysctl.conf 添加 net.ipv4.ip_forward=1,来开启 Linux 的 IPv4 转发功能,使用 sysctl -w net.ipv4.ip_forward=1 临时生效。

配置客户端

在 iKuai 路由的 网络设置 > VPN 客户端 > OpenVPN 路径下点击 添加,即可开始添加一个客户端。

ikuai-openvpn-conf1

ikuai-openvpn-conf2

点击 保存 完成配置之后,即可点击 启用 来开启连接了。

爱快软路由的问题

上面的配置已经可以使两侧建立通信,但是我在实测中发现,可以从 AZ-HQAZ-DBHY 建立通信,而反过来则不行。

在 OpenVPN 的节点上通过 ping 双方的 tun 设备地址,都可以正常通信。

我尝试在服务端的 OpenVPN 服务端的 tun 设备上抓包,从 AZ-DBHY 子网尝试 ping AZ-HQ 的子网,发现收到的源地址都是 10.255.255.2,此时我就注意到可能是在 iKuai 中有默认 NAT 策略,导致 iKuai 侧传入 OpenVPN 隧道的包源地址都被替换为了 tun 设备的地址。

经过一番搜索,找到了这篇 博客,其中提到,需要创建一条特殊的 NAT 放通策略,让进入 iKuai 侧 tun 设备的包不再进行 NAT,而是直接带着原始的源地址进行路由转发。

ikuai-nat

参考文献