9 分钟
Linux 网络虚拟化技术(六) Wireguard VPN
声明:在中文语境下,VPN 被特化为突破互联网管控手段的代名词。本文介绍的 VPN 并非这个含义,而是其原意,面向组织的虚拟私有网络。
概述
本系列上一篇文章,我们介绍了使用 tun/tap 实现一个简单的 vpn 的示例。在工业界,有很多成熟的商业或开源的 VPN 协议和实现。
传统的 VPN 协议有 OpenVPN、IPSec/L2TP、PPTP,但是这些 VPN 协议相对笨重复杂。
而本文介绍的 Wireguard 是极简、快速、现代的 VPN。据其官网称,其比 IPsec 更快、更简单、更精简和更有用,比 OpenVPN 具有更高的性能。可以运行在嵌入式设备和超级计算机、跨平台支持 Linux、Windows、macOS、BSD、iOS、Android。
选择 Wireguard 作为 VPN 的代表来介绍的另外一个重要原因是,Wireguard 已于 Linux 5.6 (2020) 进入 Linux 内核。
Wireguard 一些关键技术点如下:
- 极简,专注于 VPN 的安全和路由,C 代码据称只有 4000 行 (2020 年)。配置分发管理(秘钥、IP) Wireguard 不关注。
- 工作在 IP 层,IP 数据包加密后通过 UDP 隧道在节点间传输。
- 安全上 Wireguard 在这个流程上每个环节有且只有一种算法,这极大的简化了代码量,符合 Unix 极简哲学,使用者无需关心复杂的安全算法的选择。加密流程类似于 SSH。
模型
Wireguard 搭建了一个是由 interface 组成的虚拟网络。在这个网络中所有 interface 的都是对等,对数据包采用相同的处理逻辑。
interface 通过一个 ini
格式的配置文件定义,包含:
- 一个
[interface]
配置,即当前 interface 自身的属性。 - 多个
[peer]
配置,即当前 interface 可以直接感知到的其他 interface。
可以将 Wireguard 网络可视化为一个有向图,则一个 interface
配置的 [interface]
定义了这个有向图的 node,[peer]
定义了这个有向图的边。
配置
在 Linux 中,Wireguard 配置文件位于 /etc/wireguard
目录。配置文件为 <interface-name>.conf
,如 /etc/wireguard/wg0.conf
。
正如上文所述,一个 interface 的配置包含两类,[interface]
和 [peer]
。
[interface]
核心字段如下:
Address
,格式为带有子网后缀的 IP 地址,如192.168.96.1/24
,表示当前 interface 绑定的 IP 以及子网。这个字段是wg-quick
识别的字段,用于生成 Linux interface,并配置一个默认路由。PrivateKey
,当前 interface 的私钥,格式为一个 base64 字符串,由wg genkey
命令生成。ListenPort
,默认为51820
,监听的 UDP 端口,用于其他的 interface 和当前 interface 建立用于数据交换的 Tunnel。- 其他字段参见: wg-quick(8) — Linux manual page | WireGuard 教程:WireGuard 的搭建使用与配置详解 - 2、配置详解 - interface
[peer]
核心字段如下:
PublicKey
,指向 interface 的公钥,格式为一个 base64 字符串,由wg pubkey < /path/to/pri-key
命令生成。AllowedIPs
,指向 interface 允许的 IP,网段格式,多个用逗号分隔,如192.168.96.2/32,192.168.31.0/24
。用来实现路由和流量过滤。即:- 出流量:经过本 interface 的出数据包的目标 ip 是否在该 IP 列表中,如果在,则出数据包将发到到该 peer 对应的远端 interface 中。
- 入流量:来自该 peer 对应的 interface 的入数据包 的源 ip 是否在 IP 列表中,如果不在,将丢弃。
Endpoint
可选,指向 interface 的隧道地址,格式为ip:port
或domain:port
,域名解析发生在该接口启动的时刻。port
为指向 interface 的ListenPort
字段值。PersistentKeepalive
配置了Endpoint
时,可选配置,表示和Endpoint
心跳间隔。- 其他字段参见: wg(8) — Linux manual page | WireGuard 教程:WireGuard 的搭建使用与配置详解 - 2、配置详解 - peer
流程
将上方模型图,扩充成一个目标为在网络的笔记本和手机可以访问家庭内网任意 ip 的场景:
- 家庭内网
192.168.31.0/32
,网关openwrt
,没有公网 IP。 - 部署一个 VPN 网段
192.168.96.0/24
。 - 具有公网 ip 的云服务器
huawei
- 两台移动设备,分别为笔记本电脑
mac
,手机mi11
。
此时模型图变为:
说明:
- 方块代表 interface, 红色字为 interface 的 Address 字段。
- 从方块出发的线表示该 interface 的 peer 配置,线上的字为 peer 的
AllowedIPs
,即路由。 - 实线表示配置该 peer 配置了
Endpoint
,换句话说,实线代表一个 tunnel,这些 interface 一旦 up,这些 tunnel 将根据双方的公私钥自动建立起安全 tunnel。 - 两个 interface 之间可以连通的充分必要条件是:
- 存在两个相互指向的线(即,两个 interface 需要分别配置指向对方的 peer,目的是双方都配置好公私钥)。
- 这两个线至少有一个实线(即,网络至少单向可通,需要建立一个 tunnel,用来传输数据)。
- 因为 huawei 设备具有公网 ip,所以 mi11、mac、openwrt 才能有一个实线指向 huawei。
下面分析如下几个场景的数据流:
- mi11 访问 openwrt,sip 为 192.168.96.3,dip 为 192.168.96.2。
- ip 包从应用到达
mi11
的 wireguard interface,该 interface 查询 peer 列表,发现第一个 peer 的 AllowedIPs 的192.168.96.0/24
能匹配上该数据包的 dip。因此数据通过m11 -> huawei
的 UDP tunnel,发送到huawei
的 wireguard interface。 - ip 包到达
huawei
的 wireguard interface,该 interface 查询 peer 列表,发现第一个 peer 的 AllowedIPs 的192.168.96.2/32
能匹配上该数据包的 dip。因此数据通过openwrt -> huawei
的 UDP tunnel,发送到openwrt
的 wireguard interface。这里值得提一下的是,在 tunnel 视角,这个 tunnel 的方向是openwrt -> huawei
,即 openwrt 侧发起建立的,但是huawei -> openwrt
的 ip 数据包,是可以通过该 tunnel 从huawei
发送到openwrt
的。 - ip 包到达
openwrt
的 wireguard interface,该 interface 发现目标 ip 就是当前 interface ip,说明该数据包的目标就是当前设备。于是,该数据包将传送到应用层。 - 流程结束。
- ip 包从应用到达
- mi11 访问 nas,sip 为 192.168.96.3,dip 为 192.168.31.5。
- 前面的流程和第一个场景的 1、2 步一致,只是参数不同。
- ip 包到达
openwrt
后,会查询当前设备的路由表,发现 dip 在当前 lan 局域网内,于是将 ip 包发送到 nas 设备。这里值得提一下的是,该流程和 wiredguard 没有关系,是 openwrt 自身对路由表的处理逻辑了。
- pc 访问 mac。sip 为 192.168.31.2,dip 为 192.168.96.4。
- ip 包从应用到达
pc
的内核,内核通过默认路由发送到网关openwrt
,这里值得提一下的是,该流程和 wiredguard 没有关系,是局域网的自身配置决定的。 - 后续的流程和第一个场景的 1、2 步一致,只是参数不同。
- ip 包从应用到达
实施细节
本部分将上文流程部分示例进行落地实施,这个过程有很多细节值得关注。
生成 key
建议在 Linux 系统安装 wireguard 工具集(安装参见:下文),通过命令行生成所有设备 wireguard 配置 interface 需要的公私钥。
cd /etc/wireguard/
umask 0077
wg genkey > huawei.key
wg genkey > openwrt.key
wg genkey > mi11.key
wg genkey > mac.key
umask 0022
wg pubkey < huawei.key > huawei.key.pub
wg pubkey < openwrt.key > openwrt.key.pub
wg pubkey < mi11.key > mi11.key.pub
wg pubkey < mac.key > mac.key.pub
配置 huawei
该设备为 Ubuntu 22.04,内核版本为 5.15,因此不需要升级内核(如果内核小于 5.6 需先升级内核),可以直接安装(参考:官方文档)。
sudo apt update
sudo apt install wireguard
配置文件 vim /etc/wireguard/wg0.conf
。
[Interface]
# Name = huawei
Address = 192.168.96.1/24
PrivateKey = xxx # cat huawei.key 获取
ListenPort = 51820
[Peer]
# Name = openwrt
AllowedIPs = 192.168.96.2/32,192.168.31.0/24
PublicKey = xxx # cat openwrt.key.pub 获取
[Peer]
# Name = mi11
AllowedIPs = 192.168.96.3/32
PublicKey = xxx # cat mi11.key.pub 获取
[Peer]
# Name = mac
AllowedIPs = 192.168.96.4/32
PublicKey = xxx # cat mac.key.pub 获取
启动 wg0 接口 wg-quick up wg0
。
内核需开启 forward 等内核参数:
# ipv4 包转发
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
# arp 代理
echo "net.ipv4.conf.all.proxy_arp = 1" >> /etc/sysctl.conf
# ipv6 包转发
echo "net.ipv6.conf.all.forwarding = 1" >> /etc/sysctl.conf
# 应用配置
sysctl -p /etc/sysctl.conf
如果需要通过该节点转发外网访问流程,需配置 iptables 开启 NAT。
iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i wg0 -o wg0 -m conntrack --ctstate NEW -j ACCEPT
# 192.168.96.0/24 为 VPN 网段,eth0 为公网出口网卡设备。
iptables -t nat -A POSTROUTING -s 192.168.96.0/24 -o eth0 -j MASQUERADE
测试路由是否正常:
ip route get 192.168.96.2
ip route get 192.168.31.1
最后,需要到云服务器管理后台开放 UDP 51820 端口入流量。
配置 openwrt
openwrt 各种定制版层出不穷,本文介绍的官方编译版本,版本号为: 22.03 x86_64。
openwrt 提供了 GUI 方式配置 wiredguard 的包,打开 WebUI -> 系统 -> Sofeware。搜索安装: luci-app-wireguard
、wireguard-tools
、luci-i18n-wireguard-zh-cn
,然后重启 Openwrt。
配置文件如下:
[Interface]
# Name = openwrt
Address = 192.168.96.2/32
PrivateKey = xxx # cat openwrt.key 获取
[Peer]
# Name = huawei
Endpoint = <huawei 公网IP>:51820
AllowedIPs = 192.168.96.0/24
PublicKey = xxx # cat huawei.key.pub 获取
PersistentKeepalive = 15
打开 WebUI -> 网络 -> 接口 -> 添加新接口,填写如下信息,点击创建接口。
- 名称: wg0
- 协议:WireGuard VPN
常规设置,导入配置文件,点击加载配置文件,将上面配置文件粘贴进来,点击导入配置。然后进行一些额外的配置
- 对端 -> 第一个条目 -> 编辑, 勾选路由允许的 IP。添加对 AllowedIPs 路由。这样才能实现局域网内的设备通过 VPN IP 访问其他设备。
然后,一直点保存、保存并应用。
目前,还有个问题,即在 huawei
上无法访问 192.168.31.0/24
上的 IP。原因是没有给 OpenWrt wg0 配置防火墙,配置方法如下:
- 打开 OpenWrt WebUI -> 网络 -> 防火墙 -> 常规配置 -> Zone,点击添加,填写如下内容(该部分是试出来的,并不太理解):
- 名称: wg0
- Input、Output、Forward: accept
- Masquerading: 勾选
- Covered network:选择 wg0、lan (如果没有 wg0 可以先创建出来后面再编辑)
- Allow forward to destination zones: lan
- Allow forward from source zones: lan
- 保存并应用后,重新配置 wg0 接口。
- WebUI -> 网络 -> 接口 -> wg0,编辑。
- 防火墙设置,创建分配防火墙区域,选择 wg0。
- 保存并应用即可。
此时可以进行如下测试:
# huawei 上执行
ping 192.168.96.2
ping 192.168.31.1
# openwrt 上执行
ping 192.168.96.1
# pc 上执行
ping 192.168.96.1
如上均可以 ping 通,说明配置正确。
配置 mi
前往 f-droid 下载安装最新版安卓客户端。
配置文件如下:
[Interface]
# Name = mi11
Address = 192.168.96.3/32
PrivateKey = xxx # cat mi11.key 获取
[Peer]
# Name = huawei
Endpoint = <huawei 公网IP>:51820
AllowedIPs = 192.168.96.0/24,192.168.31.0/24
PublicKey = xxx # cat huawei.key.pub 获取
PersistentKeepalive = 15
打开 Android App,点击加号,导入配置,或者手动填写配置,保存后,点击开关开启 VPN。
验证方法为:
- 在
huawei
,ping 192.168.96.3
是否可以 ping 通 - 在
mi11
,关闭 WIFI,使用数据流量,打开 VPN:- 打开浏览器,访问 OpenWrt 的 WebUI 的内网地址,本例中为
192.168.31.254
,是否可以正常打开。 - 打开 ES 文件浏览器,是否可以连接到 NAS 上的 Samba 服务器。
- 打开浏览器,访问 OpenWrt 的 WebUI 的内网地址,本例中为
配置 mac
在中国大陆地区无法从 App Store 下载 GUI 版本 wireguard,因此本部分将介绍 cli 方式。
brew install wireguard-tools
配置文件 vim /etc/wireguard/wg0.conf
。
[Interface]
# Name = mac
Address = 192.168.96.4/32
PrivateKey = xxx # cat mac.key 获取
[Peer]
# Name = huawei
Endpoint = <huawei 公网IP>:51820
AllowedIPs = 192.168.96.0/24,192.168.31.0/24
PublicKey = xxx # cat huawei.key.pub 获取
PersistentKeepalive = 15
启动 wg0 接口 wg-quick up wg0
。
用户态实现
在某些情况下,无法将内核升级到 5.6 之上,此时可以选择官方用户态实现(官方代码仓库 列表):wireguard-go。
# 依赖
apt update
apt install -y git libmnl-dev libelf-dev build-essential pkg-config
# 安装 Go
wget https://go.dev/dl/go1.20.3.linux-amd64.tar.gz
tar -zxvf go1.20.3.linux-amd64.tar.gz
rm -rf go1.20.3.linux-amd64.tar.gz
mv go /usr/local/
echo 'export PATH=/usr/local/go/bin:$PATH' >> /etc/profile
export PATH=$PATH:/usr/local/go/bin
# 编译安装 wireguard-go
git clone https://git.zx2c4.com/wireguard-go
cd wireguard-go
# 最新版似乎有 bug,卡在 [#] wg setconf utun2 /dev/fd/63
# https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=253537
git checkout 0.0.20201118
go build -v -o "wireguard-go"
mv wireguard-go /usr/sbin/wireguard-go
cd ../
rm -rf wireguard-go
# 编译安装 WireGuard
git clone https://git.zx2c4.com/WireGuard
cd WireGuard/src/tools
make && make install
cd ../../../
rm -rf WireGuard
注意:该实现依赖 tun 内核模块,需开启,才能正常工作。
后续步骤和上文配置 huawei 一致。
设置开启自动启动:
systemctl enable wg-quick@wg0
基于 Wireguard VPN 的应用
从上面可以看出 Wireguard VPN 专注于 VPN 核心问题,解决流量加密和 Tunnel 问题。直接使用配置起来非常麻烦。因此,业界有基于 Wireguard 的面相普通用户更友好的商业产品: tailscale 比较适合小型组织。该产品开源届也实现了 tailscale 的开源替代 headscale。当然面向大型组织,可以基于 Wireguard VPN 根据自身需求自研自己的 VPN 服务。
除了面向组织的 VPN 场景,Wireguard VPN 的另一个重要场景就是云原生领域。基于 Wireguard VPN 的 k8s 网络插件,实现搭建跨内网,跨云的 k8s 集群。这个网络插件 Kilo。