WireGuard 原理与使用
前言
WireGuard 是由 Jason Donenfeld 等人用 C 语言编写的一个开源3层网络隧道工具,被视为下一代 VPN 协议,旨在解决许多困扰 IPSec/IKEv2、OpenVPN 或 L2TP 等其他 VPN 协议的问题。它与 Tinc 和 MeshBird 等现代 VPN 产品有一些相似之处,即加密技术先进、配置简单。从 2020 年 1 月开始,它已经并入了 Linux 内核的 5.6 版本,这意味着大多数 Linux 发行版的用户将拥有一个开箱即用的 WireGuard。
前置知识:
- 计算机网络 (IP,UDP)
- Linux 网络框架
本文仅对其原理和使用作技术上的探讨,不对用途担责。
WireGuard 的原理
如前言所述,WireGuard (下称:WG) 完全工作在内核模式上,这是它和其它 VPN 的很大不同之处。这意味着,WG 在效率上要高于运行在用户空间中的其他 VPN 软件(少了来回切换上下文的开销),同时却也限制了其安装,即在无法获得 root / 修改内核的情况下无法正确配置。
所有的 VPN 实现上有两种途径,一是配置系统代理,二是通过虚拟网卡。WG 工作在内核模式下,最方便的就是创建一个虚拟网卡了,在 Linux 中就是一个 TAP 设备。有了虚拟网卡,就可以分配 IP ,然后修正路由表,使得特定的流量通过虚拟网卡发送/接收,从而被 WG 处理。WG 在接收到网卡数据包时,首先进行非对称加密,然后封包通过 UDP 发送。接收到对端 (Peer) 发送的 UDP 数据包则进行解密,然后放到虚拟网卡上。加解密的操作也通过内核模块的 25519 算法完成,开销很低。
不同于 SoftEther VPN 、L2TP 等二层 VPN 协议,WG 工作在三层,即 IP 层,这意味着它无法传输二层包,也就从根本上拒绝了 DHCP。不过,静态 IP 配置也基本够用。而这样做,也进一步减少了 WG 实现上的代码量,路由的工作完全无需在 WG 中实现,完全交给系统,保持了很好的系统兼容性。
WireGuard 的连接
上述,WG 是通过 UDP 进行端点间通信的,(并且明确不支持也不会支持 TCP),这就没有连接状态了,唯一能确定对端是否联通的方法就是可选的握手。这意味着,一旦 WG 接口启动,无论对端是否连接上,在系统中都是活动的连接,这一点在配置 DDNS 等功能的时候需要尤为注意。
WG 虽然借鉴了传统的 C/S 架构,但是没有明确端点的角色,默认情况下,由被链接的一方担任 Server,对方则为 Client。默认端口为 51820,作为 Client 时,如果不显式指定,则使用随机端口连接。典型的连接如图:
UDP
Client (Local port) <-------> Server (Listen port)
与之区分,VXLAN 则是
(Send Ports) <-------------> (Listen Port)
Peer1 UDP Peer2
(Listen Port) <-------------> (Send Ports)
基于这样的类 C/S 结构,WG 的流量能够正常穿过防火墙和 NAT 设备,而不像 VXLAN 那样需要严格的对等。
而当接口上没有流量时,在不启用握手的情况下,两个端点之间不会产生任何的 UDP 通信。考虑到 NAT 和防火墙的一般情况,即端口映射/放行策略一般有时间限制(TTL),处于 NAT/防火墙后的 WG 端点建议配置握手,即使接口上没有流量也每隔一段时间发送一个握手包,保持防火墙/NAT 放行。
WireGuard 的配置
安装 WG
在一般情况下,WG 不随发行版分发,需要安装。以 Ubuntu 为例,使用
apt install wireguard-tools
安装相关内核模块和工具。安装完成后,执行 wg help
,应该能够看到这样的输出:
Usage: wg <cmd> [<args>]
Available subcommands:
show: Shows the current configuration and device information
showconf: Shows the current configuration of a given WireGuard interface, for use with `setconf'
set: Change the current configuration, add peers, remove peers, or change peers
setconf: Applies a configuration file to a WireGuard interface
addconf: Appends a configuration file to a WireGuard interface
syncconf: Synchronizes a configuration file to a WireGuard interface
genkey: Generates a new private key and writes it to stdout
genpsk: Generates a new preshared key and writes it to stdout
pubkey: Reads a private key from stdin and writes a public key to stdout
You may pass `--help' to any of these subcommands to view usage.
生成密钥对
由于 WG 强制使用 25519 加密,所以第一步就是生成节点的接口公私钥。
执行
wg genkey | tee wg-prikey | wg pubkey > wg-pubkey
生成公私钥对,其中 wg-prikey
为私钥文件, wg-pubkey
为公钥文件。
提示
有几个端点就生成几对公私钥!
编写配置文件
这里给出一个示例:
[Interface]
PrivateKey = ... # 接口私钥,即 wg-prikey 的内容,实质是 base64 编码的数据
Address = 192.168.5.100/25 # 接口地址/子网掩码长度
DNS = 223.6.6.6
MTU = 1280 # MTU - 最大传输单元
ListenPort = 25575 # 监听端口,不填就是随机,只能作 Client
[Peer]
PublicKey = ... # 对端公钥
AllowedIPs = 192.168.5.0/25 # 允许 IP,指定特定的流量经过此隧道
Endpoint = ip:port # 对端信息,ip 可以是 FQDN,IPv6 地址需要使用 [] 括住,不填默认允许接受所有连接
PersistentKeepalive = 25 # 保活(握手)间隔,不填就是0,即禁用
将这样的配置稍作修改部署到两台机器上,如配置文件名为 wg.conf
,则使用
wg-quick up wg.conf
创建接口并修改路由表。
对于多个对端,只需重复 [Peer] 段即可。
测试
如果一切正常,两台/多台端点之间应该能相互 ping 通。
Ping 不通怎么办?
- 检查配置的 IP 和端口
- 检查网段信息
- 检查 AllowIPs
- 检查防火墙
- TCPDUMP,启动!