1. 前言

这个月的空闲时间几乎都花在wireguard

  • 在KVM架构(内核空间)和OpenVZ架构(用户空间)上部署了wireguard
  • 过了一遍白皮书
  • 仔细过了一遍wireguard的go源码,粗略过了一遍wiregurad的c源码

2. 安装部署

两个服务端环境分别是

  • CentOS 7.6:KVM虚拟化,64位,Linux 5.1.15内核,使用内核模块wireguard
  • Ubuntu 16.04.6:OpenVZ虚拟化,64位,Linux 2.6.32内核,使用用户空间程序wireguard-go

以下是涉及的相关工具

  • 快速启动工具:wg-quick(shell脚本)
  • 用户空间命令行工具:wg(C语言实现)
  • 内核模块:wireguard模块(C语言实现)
  • 程序启动:wg-quick调用wg,wg执行系统调用运行内核模块
  • 进程间通信:Netlink套接字
  • 用户空间程序:wireguard-go(Go语言实现)
  • 程序启动:wg-quick直接调用用户空间程序
  • 进程间通信:Unix域套接字
  • systemd配置文件:wg-quick@.service
  • 公私钥及预共享密钥签发:wg(C语言实现)
  • IP地址、端口、密钥、MTU、DNS:手动编辑配置文件
  • 防火墙与路由:手动执行iptables命令配置

总的来讲,我们需要

  • 一个提供VPN服务端的程序(wireguard-go或wireguard模块)
  • 一个与服务端进程通信的程序(wg命令行工具)
  • 一个快速启动工具wg-quick(解析配置文件并自动调用wireguard-go或wg命令行工具)
  • 一个管理wg-quick的systemd服务(wg-quick@.service)

因此,在两个系统上,都需要安装wireguard-tools,KVM主机上需安装动态内核模块wireguard-dkms,OpenVZ主机上需编译安装wireguard-go

参考文档:WireGuard Installation

2.1 KVM(CentOS 7.6)

2.1.1 安装前的准备

  • 内核版本需要大于3.10才能安装,CentOS 7.6默认内核满足需求
  • 由于dkms及tools含有许多特定依赖,需要提前安装epel源

2.1.2 安装命令

1
2
curl -Lo /etc/yum.repos.d/wireguard.repo https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo
yum install -y wireguard-dkms wireguard-tools

2.2 OpenVZ(Ubuntu 16.04.6)

2.2.1 安装前的准备

  • TUN/TAP支持,确认是否存在/dev/net/tun设备
  • 自行编译程序的准备
  • Go 1.12环境
  • wireguard-go源码
  • 移除main.go中的warning()函数
  • 修改DoNotUseThisProgramOnLinux数值为0xdeadbabe
  • 2.2.2 工具安装

命令行工具、快速启动工具、systemd配置文件、示例等,全部在wireguard-tools中

1
2
3
add-apt-repository ppa:wireguard/wireguard
apt-get update
apt-get install -y wireguard-tools

2.2.3 用户空间程序安装

2.2.3.1 预编译程序

安装命令

1
2
3
4
curl -L https://github.com/mikumaycry/wireguard-go/releases/download/20190701/wireguard-go.tar.gz -o wireguard-go.tar.gz
tar xvf wireguard-go.tar.gz
chmod 755 wireguard-go
mv wireguard-go /usr/bin/wireguard-go

2.2.3.2 手动编译

Go 1.12环境安装不再赘述

下载源码

1
git clone https://github.com/WireGuard/wireguard-go

修改源码两个源码文件,移除警告与编译限制

main.go

1
2
3
func main() {
// warning()
}

donotuseon_linux.go

1
const DoNotUseThisProgramOnLinux = 0xdeadbabe

编译与安装程序

1
2
3
GOOS=linux go build -ldflags "-s -w" -o wireguard-go
chmod 755 wireguard-go
mv wireguard-go /usr/bin/wireguard-go

2.3 配置

一个WireGuard用户可以同时是服务端和客户端,它的配置文件就体现了这个概念。为了简化概念,这里以内网用户作为客户端,公网用户作为服务端,并假定

  • 服务端公网IP地址为233.233.233.233
  • 服务端WireGuard监听UDP端口6666
  • 服务端及客户端WireGuard虚拟网卡为wg0
  • 虚拟IP地址池使用10.0.0.0/16
  • MTU固定为1420
  • 服务端IP为10.0.0.1
  • 客户端IP为10.0.0.2
  • 客户端DNS使用8.8.8.8
  • 服务端使用wg-quick配置
  • 客户端使用WireGuard Windows与WireGuard macOS

2.3.1 man手册

这里记录一下man手册中关于配置文件的参数部分

wg手册:https://git.zx2c4.com/WireGuard/about/src/tools/man/wg.8

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
The configuration file format is based on INI. There are two top level sections -- Interface and Peer. Multiple Peer sections may be specified, but
only one Interface section may be specified.                                                                                                       
                                                                                                                                                   
                                                                                                                                                   
The Interface section may contain the following fields:                                                                                            
                                                                                                                                                   
·      PrivateKey — a base64 private key generated by wg genkey. Required.                                                                         
                                                                                                                                                   
·      ListenPort — a 16-bit port for listening. Optional; if not specified, chosen randomly.                                                      
                                                                                                                                                   
·      FwMark  — a 32-bit fwmark for outgoing packets. If set to 0 or "off", this option is disabled. May be specified in hexadecimal by prepending
       "0x". Optional.                                                                                                                             
                                                                                                                                                   
The Peer sections may contain the following fields:                                                                                                
                                                                                                                                                   
·      PublicKey — a base64 public key calculated by wg pubkey from a private key, and usually transmitted out of band to the author of the config‐
       uration file. Required.                                                                                                                     
                                                                                                                                                   
·      PresharedKey  — a base64 preshared key generated by wg genpsk. Optional, and may be omitted. This option adds an additional layer of symmet‐
       ric-key cryptography to be mixed into the already existing public-key cryptography, for post-quantum resistance.                            
                                                                                                                                                   
·      AllowedIPs — a comma-separated list of IP (v4 or v6) addresses with CIDR masks from which incoming traffic for this peer is allowed  and  to
       which  outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may be specified for matching all IPv4 addresses, and ::/0 may be
       specified for matching all IPv6 addresses. May be specified multiple times.                                                                 
                                                                                                                                                   
·      Endpoint — an endpoint IP or hostname, followed by a colon, and then a port number. This endpoint will be updated automatically to the  most
       recent source IP address and port of correctly authenticated packets from the peer.  Optional.                                              
                                                                                                                                                   
·      PersistentKeepalive  — a seconds interval, between 1 and 65535 inclusive, of how often to send an authenticated empty packet to the peer for
       the purpose of keeping a stateful firewall or NAT mapping valid persistently. For example, if the interface very rarely sends  traffic,  but
       it might at anytime receive traffic from a peer, and it is behind NAT, the interface might benefit from having a persistent keepalive inter‐
       val of 25 seconds. If set to 0 or "off", this option is disabled. By default or when unspecified, this option is off. Most  users  will  not
       need this. Optional.                                                                                                                        

wg-quick手册:https://git.zx2c4.com/WireGuard/about/src/tools/man/wg-quick.8

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
The configuration file adds a few extra configuration values to the format understood by wg(8) in order to configure  additional  attribute  of  an
interface. It handles the values that it understands, and then it passes the remaining ones directly to wg(8) for further processing.              
                                                                                                                                                   
It  infers  all  routes from the list of peers' allowed IPs, and automatically adds them to the system routing table. If one of those routes is the
default route (0.0.0.0/0 or ::/0), then it uses ip-rule(8) to handle overriding of the default gateway.                                            
                                                                                                                                                   
The configuration file will be passed directly to wg(8)'s `setconf' sub-command, with the exception of the following  additions  to  the  Interface
section, which are handled by this tool:                                                                                                           
                                                                                                                                                   
                                                                                                                                                   
·      Address  —  a comma-separated list of IP (v4 or v6) addresses (optionally with CIDR masks) to be assigned to the interface. May be specified
       multiple times.                                                                                                                             
                                                                                                                                                   
·      DNS — a comma-separated list of IP (v4 or v6) addresses to be set as the interface's DNS servers. May  be  specified  multiple  times.  Upon
       bringing  the  interface  up, this runs `resolvconf -a tun.INTERFACE -m 0 -x` and upon bringing it down, this runs `resolvconf -d tun.INTER‐
       FACE`. If these particular invocations of resolvconf(8) are undesirable, the PostUp and PostDown keys below may be used instead.            
                                                                                                                                                   
·      MTU — if not specified, the MTU is automatically determined from the endpoint addresses or the system default route, which is usually a sane
       choice. However, to manually specify an MTU to override this automatic discovery, this value may be specified explicitly.                   
                                                                                                                                                   
·      Table  —  Controls  the  routing  table to which routes are added. There are two special values: `off' disables the creation of routes alto‐
       gether, and `auto' (the default) adds routes to the default table and enables special handling of default routes.                           
                                                                                                                                                   
·      PreUp, PostUp, PreDown, PostDown — script snippets which will be executed by bash(1) before/after setting  up/tearing  down  the  interface,
       most  commonly  used  to  configure  custom DNS options or firewall rules. The special string `%i' is expanded to INTERFACE. Each one may be
       specified multiple times, in which case the commands are executed in order.                                                                 
                                                                                                                                                   
·      SaveConfig — if set to `true', the configuration is saved from the current state of the interface upon shutdown.                            
                                                                                                                                                   
                                                                                                                                                   
Recommended INTERFACE names include `wg0' or `wgvpn0' or even `wgmgmtlan0'.  However, the number at the end is in fact  optional,  and  really  any
free-form  string  [a-zA-Z0-9_=+.-]{1,15}  will work. So even interface names corresponding to geographic locations would suffice, such as `cincin‐
nati', `nyc', or `paris', if that's somehow desirable.                                                                                             

wg与wg-quick也可以通过命令行直接传递参数,但这里不做展开,会在关于后续关于源码的部分再讲

wg-quick解析了参数并传递给wg/wireguard-go,但增加了自动配置DNS、iptables规则、数据包标记等功能,默认读取/etc/wireguard目录下的配置文件

2.3.2 密钥生成

WireGuard使用了非对称密钥,服务端持有服务端私钥和所有客户端私钥,每个客户端持有自己的私钥与服务端公钥,最初的握手会使用这些密钥加密来获取会话密钥,后续的通信会使用握手得到的密钥加密

1
2
wg genkey | tee server_prikey | wg pubkey > server_pubkey
wg genkey | tee client_prikey | wg pubkey > client_pubkey

2.3.3 服务端配置

服务端配置文件:/etc/wireguard/wg0.conf

其中PrivateKey为server_prikey内容,PublicKey为client_pubkey内容,增加客户端时需要增加客户端公私钥对,并增加Peer配置

1
2
3
4
5
6
7
8
9
[Interface]
PrivateKey = QGLNKjYBvd92YVktnjZU+ybnD4yDwteRJlhybfzuTFg=
Address = 10.0.0.1/32
ListenPort = 6666
MTU = 1420

[Peer]
PublicKey = O/fTJfFi3kc0OvqG7BQ2tkXPXVBGqZRK7nMQnL0Tg0c=
AllowedIPs = 10.0.0.2/32

上述配置中未设置预共享密钥(256位的长度),程序会默认使用0填充

若有配置防火墙,应该增加配置开放6666/UDP端口,允许转发及NAT转换

KVM主机(默认网卡一般为eth0)

1
2
3
4
iptables -A INPUT -i wg0 -j ACCEPT
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A INPUT -p udp -m udp --dport 6666 -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

OpenVZ主机(默认网卡一般为venet0)

1
2
3
4
iptables -A INPUT -i wg0 -j ACCEPT
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A INPUT -p udp -m udp --dport 6666 -j ACCEPT
iptables -t nat -A POSTROUTING -o venet0 -j MASQUERADE

最后启动程序并设置开机启动

1
2
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0

上述命令中@符号后的wg0会作为变量传入service文件,对应/etc/wireguard目录下的同名conf文件

2.3.4 客户端配置

客户端配置文件:wg0.conf

其中PrivateKey为client_prikey内容,PublicKey为server_pubkey内容,可设置DNS参数,配置是否拦截全部流量(AllowedIPs)

拦截特定IP流量

wg0作为普通网卡,AllowedIPs拦截虚拟地址池、服务器、谷歌DNS,只有发往上述IP的流量会被拦截转发

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[Interface]
PrivateKey = mAgcpsFcxAUVnRYBXolE/bHl771HhLFA8CaTD8XZ/Gc=
Address = 10.0.0.2/32
DNS = 8.8.8.8
MTU = 1420

[Peer]
PublicKey = PKTDqAdYC2nkjshy9DxvLXCoAwkoj/eiktakMpBePSk=
AllowedIPs = 10.0.0.0/16, 233.233.233.233/32, 8.8.8.8/32
Endpoint = 233.233.233.233:6666

拦截全部流量

wg0作为默认网卡,AllowedIPs设置为0.0.0.0/0,所有流量走wg0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[Interface]
PrivateKey = mAgcpsFcxAUVnRYBXolE/bHl771HhLFA8CaTD8XZ/Gc=
Address = 10.0.0.2/32
DNS = 8.8.8.8
MTU = 1420

[Peer]
PublicKey = PKTDqAdYC2nkjshy9DxvLXCoAwkoj/eiktakMpBePSk=
AllowedIPs = 0.0.0.0/0
Endpoint = 233.233.233.233:6666

2.4 Protocols over WireGuard

WireGuard在底层使用UDP构建了隧道,当配置AllowedIPs拦截全部流量时,我们的所有网络通信已经走wg0。但对于一些情况,我们只想要部分请求走wg0,这时需要启用拦截特定IP流量的策略

客户端启用WireGuard后,相当于服务端的一个内网主机,这种情况下,可直接通过IP访问服务端上的服务,例如ss、socks5、http proxy等

首先在服务端分别启用privoxy监听8118端口(http代理),启用ss-local监听1080端口(socks5代理),然后在SwitchOmega中使用服务端内网IP(10.0.0.1)配置代理链接,可以达到类似使用本地ss的效果

但实际测试效果比较感人,客户端到服务端之间的流量首先通过WireGuard加密,SpeedTest测试都无法稳定,问题可能当前网络环境下UDP丢包严重或UDP发包算法不佳导致