搭建 WireGuard VPN Server

wireguard
WireGuard 是一个处于实验开发阶段的网络层VPN。它使用非对称加密技术,使用UDP封装IP数据包,创建类型为WireGuard的虚拟网络接口,运行在Linux内核层,资源占用小,相比OpenVPN配置简单,性能更好。

WireGuard的官网有安装教程https://www.wireguard.com/install/。 需要注意的是要提前安装linux-headers,如果你使用的内核是linux-lts,那么需要安装linux-lts-headers。

安装完毕后使用modprobe wireguard && lsmod | grep wireguard检查是否能够加载WireGuard内核模块。

官网的教程比较简单https://www.wireguard.com/quickstart/, 可以看到两台服务器是对等的关系,在192.168.1.1/24的网段上创建了10.0.0.1/24的虚拟网络,两边敲的命令基本一样。这样创建了点对点的虚拟网络,但还不能通过虚拟网络转发本地网络请求到远程服务器,也就是说没有VPN的流量转发功能,需要通过iptables开启NAT转发。

sysctl net.ipv4.ip_forward=1
iptables -A FORWARD -i wg0 -o ens3 -j ACCEPT; iptables -A FORWARD -i ens3 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE;

可以看到创建虚拟网络的过程需要用到两组密钥,一组是服务器A,一组是服务器B。每组密钥都有一个私钥和一个公钥,私钥使用 wg genkey > private.key 生成,公钥使用 wg pubkey < private.key 生成,都是Base64编码的格式,方便复制到配置文件。

私钥用来创建虚拟网络接口,公钥复制到其他服务器用来建立与使用对应私钥的服务器连接。需要注意的是,连接的双方都需要知道对方的公钥才能建立连接,也就是说A的公钥复制到B,B的公钥复制到A。而公钥的交换需要手动复制,WireGuard不关心公钥交换过程,需要使用者自己实现交换。

在对等网络上,可以看到WireGuard并不区分服务器和客户端,两端是等价互联,处于相同的地位,建立连接后任何一端都可以主动发起连接。如果服务器A和B有任意一个位于网关后面,那么就需要区分服务器和客户端了,因为客户端处于NAT设备后面,有公网IP的服务器无法与客户端直接联系。

其实服务器的endpoint参数可以不写,先在服务器使用客户端的公钥建立一个Peer等待客户端的连接,客户端的endpoint参数写上服务器的IP:PORT,并且加上persistent-keepalive 25用来每隔25秒发送一次心跳包保持NAT端口映射。

服务器:wg set wg0 peer [客户端公钥] allowed-ips 10.0.0.2/32
客户端:wg set wg0 peer [服务器公钥] allowed-ips 10.0.0.1/32 endpoint 服务器IP:端口 persistent-keepalive 25

wg set wg0 peer [客户端公钥] remove 可以移除客户端的连接。

服务器端口可以使用wg命令查看interface中listening port的值。同样使用wg命令可以查看建立的Peer连接是否成功,latest handshake要有对应的时间。也可以在客户端ping 10.0.0.1来测试网络是否建立连接。

以上手动建立的WireGuard网络重启后丢失。WireGuard 提供一个根据配置文件建立连接的工具wg-quick,配置文件的位置在/etc/wireguard/,默认为空,可以使用wg showconf > /etc/wireguard/wg0.conf 将当前的配置导入wg0.conf以便使用wg-quick管理接口的创建和销毁。

wg-quick up wg0 启动/etc/wireguard/wg0.conf配置中的接口和连接,wg-quick down wg0关闭接口。

这里有一份WireGuard服务器的配置文件模板:

[Interface]
Address = 10.0.0.1/24
SaveConfig = true
PostUp = iptables -A FORWARD -i wg0 -o ens3 -j ACCEPT; iptables -A FORWARD -i ens3 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE;
PostDown = iptables -D FORWARD -i wg0 -o ens3 -j ACCEPT; iptables -D FORWARD -i ens3 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE;
ListenPort = 51820
FwMark = 0xca6c
PrivateKey = [************Server PrivateKey**********]

[Peer]
PublicKey = [*********Client PublicKey***********]
AllowedIPs = 10.0.0.2/32
PersistentKeepalive = 25

客户端配置模板:

[Interface]
PrivateKey = [***********Client PrivateKey***********]
Address = 10.0.0.3/24
DNS = 8.8.8.8

[Peer]
PublicKey = [************Server PublicKey************]
AllowedIPs = 0.0.0.0/0
Endpoint = [Server IP]:[Port]
PersistentKeepalive = 25

将模板中[********]的内容替换成相应的值,保存到/etc/wireguard/wg0.conf,即可使用wg-quick进行管理。可以使用命令systemctl enable [email protected]保持开机自动启动。

WireGuard官方目前没有发布Windows客户端,正在紧密开发中。目前的第三方客户端有TunSafe,支持Windows,Linux, OSX。TunSafe自带的配置仅为示例,不能连接,可以申请TunSafe的测试服务器。申请的第一步就是提交了客户端新创建的一个公钥,网站自动将公钥添加到各个测试服务器,第二部就是将配置文件模板中的私钥Placeholder替换为刚刚生成的私钥,这个替换过程使用JavaScript实现,并不提交到服务器,你也可以直接下载未替换的模板在本地用记事本手动替换,然后导入TunSafe进行连接。

TunSafe 1.4-rc1版本会出现握手失败的情况:

[01:14:56] Sending handshake...
[01:14:56] UdpSocketWin32::Write error 0xC000023D
[01:15:01] Retrying handshake, attempt 2...

这是因为路由表没有更新,Added Route 123.123.123.123/32 => 192.168.0.1执行失败,需要以管理员权限手动添加路由:

route add 123.123.123.123/32 192.168.0.1

其中123.123.123.123为服务器IP,要与Added Route后的IP一致,路由添加成功后重新连接,更换节点后重新添加路由。TunSafe添加的路由使得本地所有网络连接通过创建的虚拟网络转发到目的服务器,上面添加的路由使得WireGuard服务器的流量除外,仍然通过本地网关转发,如果没有这个路由,自然无法通过还未建立的虚拟网络与WireGuard服务器通信。

下面是一个服务器端的守护脚本,检测到客户端连接超时150秒后主动重新建立连接点
/etc/cron.hourly/wireguard_watchdog

#!/bin/sh

check_peer_activity() {
  local iface=$1
  local public_key=$2
  last_handshake=`wg show ${iface} latest-handshakes | grep ${public_key} | awk '{print $2}'`
  allowed_ips=`wg show wg0 allowed-ips | grep ${public_key} | awk '{print $2}'`
  [ -z ${last_handshake} ] && return 0;
  [ ${last_handshake} -eq "0" ] && return 0;
  idle_seconds=$((`date +%s`-${last_handshake}))
  [ ${idle_seconds} -lt 150 ] && return 0;
  wg set ${iface} peer ${public_key} remove
  wg set ${iface} peer ${public_key} allowed-ips ${allowed_ips}
  logger -st wireguard_watchdog ${iface} peer ${public_key} has been reset
}

check_peers() {
  local iface=$1
  peers=`wg show ${iface} peers`
  for public_key in ${peers}; do
    check_peer_activity ${iface} ${public_key}
    logger -st wireguard_watchdog ${iface} peer ${public_key} checked
  done
}

check_peers wg0

放入/etc/cron.hourly的脚本需要可执行权限,不能有后缀,每小时执行一次,可通过journalctl -t wireguard_watchdog查看执行记录。

参考资料:
WireGuard ArchWiki
在 Ubuntu 部署 VPN 隧道 WireGuard

原文链接:https://marskid.net/2018/09/20/wireguard-vpn-set-up/