墨少离 - 个人小站，分享一些资源以及心得~ - Wireguard
上篇文章给大家介绍了如何使用 wg-gen-web 来方便快捷地管理 WireGuard 的配置和秘钥，文末埋了两个坑：一个是 WireGuard 的全互联模式（full mesh），另一个是使用 WireGuard 作为 Kubernetes 的 CNI 插件。今天就来填第一个坑。首先解释一下什么是全互联模式（full mesh），全互联模式其实就是一种网络连接形式，即所有结点之间都直接连接，不会通过第三方节点中转流量。和前面提到的点对多点架构 其实是一个意思。1. 全互联模式架构与配置在 WireGuard 的世界里没有 Server 和 Client 之分，所有的节点都是 Peer。大家使用 WireGuard 的常规做法是找一个节点作为中转节点，也就是 VPN 网关，然后所有的节点都和这个网关进行连接，所有节点之间都通过这个网关来进行通信。这种架构中，为了方便理解，我们可以把网关看成 Server，其他的节点看成 Client，但实际上是不区分 Server 和 Client 的。举个例子，假设有 4 个节点，分别是 A/B/C/D，且这 4 个节点都不在同一个局域网，常规的做法是选取一个节点作为 VPN 网关，架构如图：这种架构的缺点我在之前的文章里也介绍过了，缺点相当明显：当 Peer 越来越多时，VPN 网关就会变成垂直扩展的瓶颈。通过 VPN 网关转发流量的成本很高，毕竟云服务器的流量很贵。通过 VPN 网关转发流量会带来很高的延迟。那么全互联模式是什么样的架构呢？还是假设有 A/B/C/D 四个节点，每个节点都和其他节点建立 WireGuard 隧道，架构如图：这种架构带来的直接优势就是快！任意一个 Peer 和其他所有 Peer 都是直连，无需中转流量。那么在 WireGuard 的场景下如何实现全互联模式呢？其实这个问题不难，难点在于配置的繁琐程度，本文的主要目标就是精简 WireGuard 全互联模式的配置流程。为了让大家更容易理解，咱们还是先通过架构图来体现各个 Peer 的配置：配置一目了然，每个 Peer 和其他所有 Peer 都是直连，根本没有 VPN 网关这种角色。当然，现实世界的状况没有图中这么简单，有些 Peer 是没有公网 IP 的，躲在 NAT 后面，这里又分两种情况：NAT 受自己控制。这种情况可以在公网出口设置端口转发，其他 Peer 就可以通过这个公网 IP 和端口连接当前 Peer。如果公网 IP 是动态的，可以通过 DDNS 来解决，但 DDNS 会出现一些小问题，解决方法可以参考 WireGuard 的优化。NAT 不受自己控制。这种情况无法在公网出口设置端口转发，只能通过 UDP 打洞来实现互联.接着上述方案再更进一步，打通所有 Peer 的私有网段，让任意一个 Peer 可以访问其他所有 Peer 的私有网段的机器。上述配置只是初步完成了全互联，让每个 Peer 可以相互访问彼此而已，要想相互访问私有网段，还得继续增加配置，还是直接看图：红色字体部分就是新增的配置，表示允许访问相应 Peer 的私有网段，就是这么简单。详细的配置步骤请看下一节。2. 全互联模式最佳实践对如何配置有了清晰的思路之后，接下来就可以进入实践环节了。我不打算从 WireGuard 安装开始讲起，而是以前几篇文章为基础添砖加瓦。所以我建议读者先按顺序看下这两篇文章：WireGuard 快速安装教程WireGuard 配置教程：使用 wg-gen-web 来管理 WireGuard 的配置咱们直接从配置开始说起。手撸配置的做法是不明智的，因为当节点增多之后工作量会很大，我还是建议通过图形化界面来管理配置，首选 wg-gen-web。现在还是假设有上节所述的 4 个 Peer，我们需要从中挑选一个 Peer 来安装 wg-gen-web，然后通过 wg-gen-web 来生成配置。挑选哪个 Peer 无所谓，这个没有特殊限制，这里假设挑选 AWS 来安装 wg-gen-web。安装的步骤直接略过，不是本文的重点，不清楚的可以阅读我之前的文章 WireGuard 配置教程：使用 wg-gen-web 来管理 WireGuard 的配置。Server 配置如图：生成 Azure 的配置：SUBMIT 之后再点击 EDIT，添加私有网段：查看 wg0.conf 的内容：$ cat /etc/wireguard/wg0.conf # Updated: 2021-02-24 07:34:23.805535396 +0000 UTC / Created: 2021-02-24 07:24:02.208816462 +0000 UTC [Interface] Address = 10.0.0.1/24 ListenPort = 51820 PrivateKey = eEnHKGkGksx0jqrEDogjRj5l417BrEA39lr7WW9L9U0= PreUp = echo WireGuard PreUp PostUp = iptables -I FORWARD -i wg0 -j ACCEPT; iptables -I FORWARD -o wg0 -j ACCEPT; iptables -I INPUT -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PreDown = echo WireGuard PreDown PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -D INPUT -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # Azure / / Updated: 2021-02-24 07:43:52.717385042 +0000 UTC / Created: 2021-02-24 07:43:52.717385042 +0000 UTC [Peer] PublicKey = OzdH42suuOpVY5wxPrxM+rEAyEPFg2eL0ZI29N7eSTY= PresharedKey = 1SyJuVp16Puh8Spyl81EgD9PJZGoTLJ2mOccs2UWDvs= AllowedIPs = 10.0.0.2/32, 192.168.20.0/24 下载 Azure 配置文件：可以看到配置文件内容为：$ cat Azure.conf [Interface] Address = 10.0.0.2/32, 192.168.20.0/24 PrivateKey = IFhAyIWY7sZmabsqDDESj9fqoniE/uZFNIvAfYHjN2o= [Peer] PublicKey = JgvmQFmhUtUoS3xFMFwEgP3L1Wnd8hJc3laJ90Gwzko= PresharedKey = 1SyJuVp16Puh8Spyl81EgD9PJZGoTLJ2mOccs2UWDvs= AllowedIPs = 10.0.0.1/32, 192.168.10.0/24 Endpoint = aws.com:51820 先不急着修改，一鼓作气生成所有 Peer 的配置文件：这时你会发现 wg0.conf 中已经包含了所有 Peer 的配置：$ cat /etc/wireguard/wg0.conf # Updated: 2021-02-24 07:57:00.745287945 +0000 UTC / Created: 2021-02-24 07:24:02.208816462 +0000 UTC [Interface] Address = 10.0.0.1/24 ListenPort = 51820 PrivateKey = eEnHKGkGksx0jqrEDogjRj5l417BrEA39lr7WW9L9U0= PreUp = echo WireGuard PreUp PostUp = iptables -I FORWARD -i wg0 -j ACCEPT; iptables -I FORWARD -o wg0 -j ACCEPT; iptables -I INPUT -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PreDown = echo WireGuard PreDown PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -D INPUT -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # Aliyun / / Updated: 2021-02-24 07:57:45.941019829 +0000 UTC / Created: 2021-02-24 07:57:45.941019829 +0000 UTC [Peer] PublicKey = kVq2ATMTckCKEJFF4TM3QYibxzlh+b9CV4GZ4meQYAo= PresharedKey = v818B5etpRlyVYHGUrv9abM5AIQK5xeoCizdWj1AqcE= AllowedIPs = 10.0.0.4/32, 192.168.40.0/24 # GCP / / Updated: 2021-02-24 07:57:27.3555646 +0000 UTC / Created: 2021-02-24 07:57:27.3555646 +0000 UTC [Peer] PublicKey = qn0Xfyzs6bLKgKcfXwcSt91DUxSbtATDIfe4xwsnsGg= PresharedKey = T5UsVvOEYwfMJQDJudC2ryKeCpnO3RV8GFMoi76ayyI= AllowedIPs = 10.0.0.3/32, 192.168.30.0/24 # Azure / / Updated: 2021-02-24 07:57:00.751653134 +0000 UTC / Created: 2021-02-24 07:43:52.717385042 +0000 UTC [Peer] PublicKey = OzdH42suuOpVY5wxPrxM+rEAyEPFg2eL0ZI29N7eSTY= PresharedKey = 1SyJuVp16Puh8Spyl81EgD9PJZGoTLJ2mOccs2UWDvs= AllowedIPs = 10.0.0.2/32, 192.168.20.0/24 现在问题就好办了，我们只需将 wg0.conf 中的 Aliyun 和 GCP 部分的配置拷贝到 Azure 的配置中，并删除 PresharedKey 的配置，再添加 Endpoint 的配置和 PostUP/PostDown 规则，最后别忘了删除 Address 中的私有网段：$ cat Azure.conf [Interface] Address = 10.0.0.2/32 PrivateKey = IFhAyIWY7sZmabsqDDESj9fqoniE/uZFNIvAfYHjN2o= PostUp = iptables -I FORWARD -i wg0 -j ACCEPT; iptables -I FORWARD -o wg0 -j ACCEPT; iptables -I INPUT -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -D INPUT -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer] PublicKey = JgvmQFmhUtUoS3xFMFwEgP3L1Wnd8hJc3laJ90Gwzko= PresharedKey = 1SyJuVp16Puh8Spyl81EgD9PJZGoTLJ2mOccs2UWDvs= AllowedIPs = 10.0.0.1/32, 192.168.10.0/24 Endpoint = aws.com:51820 # Aliyun / / Updated: 2021-02-24 07:57:45.941019829 +0000 UTC / Created: 2021-02-24 07:57:45.941019829 +0000 UTC [Peer] PublicKey = kVq2ATMTckCKEJFF4TM3QYibxzlh+b9CV4GZ4meQYAo= AllowedIPs = 10.0.0.4/32, 192.168.40.0/24 Endpoint = aliyun.com:51820 # GCP / / Updated: 2021-02-24 07:57:27.3555646 +0000 UTC / Created: 2021-02-24 07:57:27.3555646 +0000 UTC [Peer] PublicKey = qn0Xfyzs6bLKgKcfXwcSt91DUxSbtATDIfe4xwsnsGg= AllowedIPs = 10.0.0.3/32, 192.168.30.0/24 Endpoint = gcp.com:51820 同理，GCP 的配置如下：$ cat GCP.conf [Interface] Address = 10.0.0.3/32 PrivateKey = oK2gIMBAob67Amj2gT+wR9pzkbqWGNtq794nOoD3i2o= PostUp = iptables -I FORWARD -i wg0 -j ACCEPT; iptables -I FORWARD -o wg0 -j ACCEPT; iptables -I INPUT -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -D INPUT -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer] PublicKey = JgvmQFmhUtUoS3xFMFwEgP3L1Wnd8hJc3laJ90Gwzko= PresharedKey = T5UsVvOEYwfMJQDJudC2ryKeCpnO3RV8GFMoi76ayyI= AllowedIPs = 10.0.0.1/32, 192.168.10.0/24 Endpoint = aws.com:51820 # Aliyun / / Updated: 2021-02-24 07:57:45.941019829 +0000 UTC / Created: 2021-02-24 07:57:45.941019829 +0000 UTC [Peer] PublicKey = kVq2ATMTckCKEJFF4TM3QYibxzlh+b9CV4GZ4meQYAo= AllowedIPs = 10.0.0.4/32, 192.168.40.0/24 Endpoint = aliyun.com:51820 # Azure / / Updated: 2021-02-24 07:57:00.751653134 +0000 UTC / Created: 2021-02-24 07:43:52.717385042 +0000 UTC [Peer] PublicKey = OzdH42suuOpVY5wxPrxM+rEAyEPFg2eL0ZI29N7eSTY= AllowedIPs = 10.0.0.2/32, 192.168.20.0/24 Endpoint = azure.com:51820 Aliyun 的配置如下：$ cat Aliyun.conf [Interface] Address = 10.0.0.4/32 PrivateKey = +A1ZESJjmHuskB4yKqTcqC3CB24TwBKHGSffWDHxI28= PostUp = iptables -I FORWARD -i wg0 -j ACCEPT; iptables -I FORWARD -o wg0 -j ACCEPT; iptables -I INPUT -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -D INPUT -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer] PublicKey = JgvmQFmhUtUoS3xFMFwEgP3L1Wnd8hJc3laJ90Gwzko= PresharedKey = v818B5etpRlyVYHGUrv9abM5AIQK5xeoCizdWj1AqcE= AllowedIPs = 10.0.0.1/32, 192.168.10.0/24 Endpoint = aws.com:51820 # GCP / / Updated: 2021-02-24 07:57:27.3555646 +0000 UTC / Created: 2021-02-24 07:57:27.3555646 +0000 UTC [Peer] PublicKey = qn0Xfyzs6bLKgKcfXwcSt91DUxSbtATDIfe4xwsnsGg= AllowedIPs = 10.0.0.3/32, 192.168.30.0/24 Endpoint = gcp.com:51820 # Azure / / Updated: 2021-02-24 07:57:00.751653134 +0000 UTC / Created: 2021-02-24 07:43:52.717385042 +0000 UTC [Peer] PublicKey = OzdH42suuOpVY5wxPrxM+rEAyEPFg2eL0ZI29N7eSTY= AllowedIPs = 10.0.0.2/32, 192.168.20.0/24 Endpoint = azure.com:51820 最后在各自的节点上通过各自的配置文件把 WireGuard 跑起来，就搞定了。整个图形化界面配置过程中不需要手动调整配置，功能还是比较完善的，只有客户端的配置需要手动调整。如果你无法接受手动调整配置，可以尝试另外一个项目：wg-meshconf，这个项目专门用来生成 mesh 的配置，但没有图形化管理界面。各有利弊吧，大家自行选择。3. 总结我知道，很多人可能还是一头雾水，这玩意儿的应用场景有哪些？我随便举个简单的例子，假设你在云服务器上部署了 Kubernetes 集群，可以用本地的机器和云服务器的某台节点组建 WireGuard 隧道，然后在本地的 AllowedIPs 中加上 Pod 网段和 Service 网段，就可以那啥了，你懂吧？好吧，又埋了一个坑，关于如何在家中直接访问云服务器 k8s 集群的 Pod IP 和 Service IP，后面会有专门的文章给大家讲解，虽然我也不确定是多久以后。。
WireGuard 配置教程：使用 wg-gen-web 来管理 WireGuard 的配置
之前花了很大的篇幅介绍了 WireGuard 的工作原理和配置详解，可这里面的内容实在太多了，大部分人根本没兴趣深究，只是将其当成参考书来看。WireGuard 虽然组网逻辑很简洁明了，但秘钥和配置文件的管理是个麻烦事，需要手工配置。为了让大部分读者能够快速上手 WireGuard，体验 WireGuard 的优雅和强大，我决定新开一个 WireGuard 快速上手系列，第一篇之前已经发出来了：WireGuard 快速安装教程这篇文章仅仅介绍了如何快速安装 WireGuard，并没有涉及到如何配置使其正常工作。本文主要介绍如何方便优雅地管理 WireGuard 的配置和秘钥。当然了，这里不会详细解读各个配置参数的含义，也不会告诉你通过哪个命令来创建公钥私钥，如果你对此部分感兴趣，可以查看我之前发布的 WireGuard 配置详解。1. wg-gen-web 配置对于新手来说，如何才能快速把 WireGuard 用起来呢？当然是通过图形管理界面啦，填几个参数，生成个二维码，再拿客户端扫下二维码就连上了，简直是比爽姐还爽~wg-gen-web 就是这样一款图形管理界面，主要包含以下这些功能：根据 CIDR 自动分配 IP 地址给客户端；每个客户端会生成 QR 二维码，方便移动客户端扫描使用；支持通过邮件发送二维码和配置文件；支持启用和禁用某个客户端；支持 IPv6；支持使用 GitHub 和 Oauth2 OIDC 来进行用户认证；颜值还比较高。wg-gen-web 支持直接通过容器来运行，如果你是在本地运行，可以准备一份 docker-compose 文件：version: '3.6' services: wg-gen-web: image: vx3r/wg-gen-web:latest container_name: wg-gen-web restart: always expose: - "8080/tcp" ports: - 80:8080 environment: - WG_CONF_DIR=/data - WG_INTERFACE_NAME=wg0.conf - OAUTH2_PROVIDER_NAME=fake - WG_STATS_API=http://<API_LISTEN_IP>:8182 volumes: - /etc/wireguard:/data network_mode: bridge wg-json-api: image: james/wg-api:latest container_name: wg-json-api restart: always cap_add: - NET_ADMIN network_mode: "host" command: wg-api --device wg0 --listen <API_LISTEN_IP>:8182这里还用到了另外一个项目 wg-api，该项目提供了一个 JSON-RPC 接口，用来暴露 WireGuard 的网络状态信息。其中 <API_LISTEN_IP> 可以直接替换成 docker0 的 IP。执行以下命令运行 wg-gen-web：docker-compose up -d在浏览器中输入 URL 打开图形管理界面，点击 “SERVER” 开始填写服务端和客户端的配置信息：各项配置的含义我就不解释了，都很好理解，实在不理解的请查看 WireGuard 配置详解。填写好配置信息后，直接点击 UPDATE SERVER CONFIGURATION 保存，同时会生成配置文件 wg0.conf： cat /etc/wireguard/wg0.conf # Updated: 2021-01-20 03:59:37.718655459 +0000 UTC / Created: 2021-01-20 03:32:28.045982181 +0000 UTC [Interface] Address = 10.6.6.1/24 ListenPort = 51820 PrivateKey = iLPeSYaKYERfyrOX/YcAam4AIIHCNEBXnqL2oRedAWQ= PreUp = echo WireGuard PreUp PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PreDown = echo WireGuard PreDown PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE接下来点击 CLIENTS，然后点击 ADD NEW CLIENT 开始新增客户端配置：填写客户端配置信息：点击 SUBMIT，就会在 /etc/wireguard 目录下生成客户端的 json 配置文件：cat /etc/wireguard/f5fcc1e7-e03a-48bb-acd9-8d5214c6cb1f { "id": "f5fcc1e7-e03a-48bb-acd9-8d5214c6cb1f", "name": "test", "email": "yangchuansheng33@gmail.com", "enable": true, "ignorePersistentKeepalive": false, "presharedKey": "8QkkeXGt4D/lnLDA1jfJUhB3oiShhRWp/GC8GFQtgKs=", "allowedIPs": [ "10.6.6.0/24" ], "address": [ "10.6.6.2/32" ], "tags": [], "privateKey": "ODN2xN12p5lwcEuj20C4uZV9kJE9yHz4eAHB/4czPEM=", "publicKey": "k2Ut15aQn7+mNHqEd4bwdNx3WcvA4F7SPmETYuWdSjM=", "createdBy": "Unknown", "updatedBy": "", "created": "2021-01-20T05:19:16.659225991Z", "updated": "2021-01-20T05:19:16.659225991Z" }如果勾选了 “Enable client after creation”，还会将 peer 的配置加入 wg0.conf：cat /etc/wireguard/wg0.conf # Updated: 2021-01-20 03:59:37.718655459 +0000 UTC / Created: 2021-01-20 03:32:28.045982181 +0000 UTC [Interface] Address = 10.6.6.1/24 ListenPort = 51820 PrivateKey = iLPeSYaKYERfyrOX/YcAam4AIIHCNEBXnqL2oRedAWQ= PreUp = echo WireGuard PreUp PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PreDown = echo WireGuard PreDown PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # test / yangchuansheng33@gmail.com / Updated: 2021-01-20 05:19:16.659225991 +0000 UTC / Created: 2021-01-20 05:19:16.659225991 +0000 UTC [Peer] PublicKey = k2Ut15aQn7+mNHqEd4bwdNx3WcvA4F7SPmETYuWdSjM= PresharedKey = 8QkkeXGt4D/lnLDA1jfJUhB3oiShhRWp/GC8GFQtgKs= AllowedIPs = 10.6.6.2/32最后直接启动 wg-quick 服务就行了：systemctl start wg-quick@wg0如果你之前已经启动过该服务，现在只需要重启就行了：systemctl restart wg-quick@wg0重启之后 WireGuard 会断开重连，体验不太好。事实上 WireGuard 可以做到在不中断活跃连接的情况下重新加载配置文件，命令如下：wg syncconf wg0 <(wg-quick strip wg0)我们可以将这个命令作为 systemd 服务的 reload 命令：# /usr/lib/systemd/system/wg-quick@.service [Unit] Description=WireGuard via wg-quick(8) for %I After=network-online.target nss-lookup.target Wants=network-online.target nss-lookup.target PartOf=wg-quick.target Documentation=man:wg-quick(8) Documentation=man:wg(8) Documentation=https://www.wireguard.com/ Documentation=https://www.wireguard.com/quickstart/ Documentation=https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8 Documentation=https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8 [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/wg-quick up %i ExecStop=/usr/bin/wg-quick down %i ExecReload=/bin/bash -c 'exec /usr/bin/wg syncconf %i <(exec /usr/bin/wg-quick strip %i)' Environment=WG_ENDPOINT_RESOLUTION_RETRIES=infinity [Install] WantedBy=multi-user.target如果你按照 WireGuard 快速安装教程 这篇文章的步骤来安装 WireGuard，ExecReload 默认已经被加进去了，到这一步不需要做任何改动。后面再更新配置文件时，直接 reload 就行了：systemctl reload wg-quick@wg0每次更新配置后都要手动 reload 还是很麻烦的，我们可以通过 systemd 来监听配置文件的实时变化，一但配置文件有所改动，就立即触发 reload。方法也很简单，先创建一个 wg-gen-web.service 用来 reload：# /etc/systemd/system/wg-gen-web.service [Unit] Description=Restart WireGuard After=network.target [Service] Type=oneshot ExecStart=/usr/bin/systemctl reload wg-quick@wg0.service [Install] WantedBy=multi-user.target然后再创建一个同名的 wg-gen-web.path 用来监听文件变化：# /etc/systemd/system/wg-gen-web.path [Unit] Description=Watch /etc/wireguard for changes [Path] PathModified=/etc/wireguard [Install] WantedBy=multi-user.target设置开机自启：systemctl enable wg-gen-web.service wg-gen-web.path --now后面如果再到 Web 页面上更新配置信息，会立即触发 reload，不需要再自己手动 reload 了。查看接口信息：wg show wg0 interface: wg0 public key: dG5xPA7Q6X7ByeNl5pasI/8ZPhiTOsfsy0NUX4w2wmI= private key: (hidden) listening port: 51820 peer: k2Ut15aQn7+mNHqEd4bwdNx3WcvA4F7SPmETYuWdSjM= preshared key: (hidden) allowed ips: 10.6.6.2/32目前还没有客户端与之连接，所以还看不到连接信息。下面以 macOS 为例演示连接过程。2. 客户端建立连接macOS 目前只有两种客户端软件，一个是图形界面，一个是命令行工具。图形界面只上架了 App Store，而且需要美区 Apple ID，比较麻烦。我推荐直接安装命令行工具：brew install wireguard-toolsmacOS 中的 wg-quick 默认也是读取的 /etc/wireguard 目录，所以需要先创建该目录：sudo mkdir /etc/wireguard然后直接下载配置文件：将其移动到 /etc/wireguard 目录，并重命名为 wg0.conf：sudo mv ~/Downloads/test.conf /etc/wireguard/wg0.conf查看配置文件内容：cat /etc/wireguard/wg0.conf [Interface] Address = 10.6.6.2/32 PrivateKey = ODN2xN12p5lwcEuj20C4uZV9kJE9yHz4eAHB/4czPEM= [Peer] PublicKey = dG5xPA7Q6X7ByeNl5pasI/8ZPhiTOsfsy0NUX4w2wmI= PresharedKey = 8QkkeXGt4D/lnLDA1jfJUhB3oiShhRWp/GC8GFQtgKs= AllowedIPs = 10.6.6.0/24 Endpoint = 172.16.7.3:51820 PersistentKeepalive = 25直接启动：sudo wg-quick up wg0查看链接信息：sudo wg interface: utun2 public key: k2Ut15aQn7+mNHqEd4bwdNx3WcvA4F7SPmETYuWdSjM= private key: (hidden) listening port: 60082 peer: dG5xPA7Q6X7ByeNl5pasI/8ZPhiTOsfsy0NUX4w2wmI= preshared key: (hidden) endpoint: 172.16.7.3:51820 allowed ips: 10.6.6.0/24 latest handshake: 7 seconds ago transfer: 840 B received, 840 B sent persistent keepalive: every 25 seconds可以看到输出中有两行重要的信息：transfer: 840 B received, 840 B sent persistent keepalive: every 25 seconds表示和服务端已经握手成功了，并且开始传输数据。到服务端所在的机器查看连接信息：wg show wg0 interface: wg0 public key: dG5xPA7Q6X7ByeNl5pasI/8ZPhiTOsfsy0NUX4w2wmI= private key: (hidden) listening port: 51820 peer: k2Ut15aQn7+mNHqEd4bwdNx3WcvA4F7SPmETYuWdSjM= preshared key: (hidden) endpoint: 10.2.0.2:60082 allowed ips: 10.6.6.2/32 latest handshake: 25 seconds ago transfer: 1.64 KiB received, 1.61 KiB sent可以看到握手成功了。Web 页面也能看到连接信息：如果想增加更多的客户端，直接在 Web 页面新增客户端配置就行了，不需要做任何额外的操作，解放了双手。手机客户端直接扫描二维码就能连接，还是挺爽的。3. 优化最后一部分主要介绍 WireGuard 的优化。动态 IP对于 WireGuard 而言，只需要一端具有公网 IP 地址便可建立连接，哪怕这一端的 IP 是动态变化的也没问题，可以使用 DDNS 来解决这个问题，WireGuard 会在启动时解析域名的 IP 地址，然后将该 IP 地址作为 peer 的 Endpoint。但这里有一个小瑕疵，WireGuard 只会在启动时解析配置文件中域名的 IP 地址，后续如果域名对应的 IP 地址有更新，也不会重新解析。wireguard-tools 项目中提供了一个脚本 reresolve-dns.sh 可以用来解决这个问题，该脚本会解析 WireGuard 的配置文件并更新 Endpoint 的 IP 地址。同时，我们还需要创建一个定时任务来定期触发该脚本更新配置，比如每 30 秒执行一次。具体操作步骤如下：首先克隆 wireguard-tools 仓库：git clone https://git.zx2c4.com/wireguard-tools /usr/share/wireguard-tools脚本内容：cat /usr/share/wireguard-tools/contrib/reresolve-dns/reresolve-dns.sh #!/bin/bash # SPDX-License-Identifier: GPL-2.0 # # Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. set -e shopt -s nocasematch shopt -s extglob export LC_ALL=C CONFIG_FILE="$1" [[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]] && CONFIG_FILE="/etc/wireguard/$CONFIG_FILE.conf" [[ $CONFIG_FILE =~ /?([a-zA-Z0-9_=+.-]{1,15})\.conf$ ]] INTERFACE="${BASH_REMATCH[1]}" process_peer() { | -z $PUBLIC_KEY || -z $ENDPOINT && return 0 [[ $(wg show "$INTERFACE" latest-handshakes) =~ ${PUBLIC_KEY//+/\\+}\ ([0-9]+) ]] || return 0 (( ($(date +%s) - ${BASH_REMATCH[1]}) > 135 )) || return 0 wg set "$INTERFACE" peer "$PUBLIC_KEY" endpoint "$ENDPOINT" reset_peer_section } reset_peer_section() { PEER_SECTION=0 PUBLIC_KEY="" ENDPOINT="" } reset_peer_section while read -r line || -n $line ; do stripped="${line%%\#*}" key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}" value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}" [[ $key == "["* ]] && { process_peer; reset_peer_section; } [[ $key == "[Peer]" ]] && PEER_SECTION=1 if $PEER_SECTION -eq 1 ; then case "$key" in PublicKey) PUBLIC_KEY="$value"; continue ;; Endpoint) ENDPOINT="$value"; continue ;; esac fi done < "$CONFIG_FILE" process_peer然后创建一个 Service 文件：# /etc/systemd/system/wireguard_reresolve-dns.service [Unit] Description=Reresolve DNS of all WireGuard endpoints Wants=network-online.target After=network-online.target [Service] Type=oneshot ExecStart=/bin/sh -c 'for i in /etc/wireguard/*.conf; do /usr/share/wireguard-tools/contrib/reresolve-dns/reresolve-dns.sh "$i"; done'再创建一个同名的 wireguard_reresolve-dns.timer 实现定时任务：# /etc/systemd/system/wireguard_reresolve-dns.timer [Unit] Description=Periodically reresolve DNS of all WireGuard endpoints [Timer] OnCalendar=*:*:0/30 [Install] WantedBy=timers.target设置开机自启动：systemctl enable wireguard_reresolve-dns.service wireguard_reresolve-dns.timer --now打印 debug 日志在支持动态调试的内核上使用 Linux 内核模块时，可以将 WireGuard 的调试信息写入内核环形缓冲区中：modprobe wireguard echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control然后就可以使用 journalctl 或者 dmesg 来查看调试信息了：journalctl -xf|grep wireguard Jan 20 15:07:00 k8s03 kernel: wireguard: wg0: Receiving keepalive packet from peer 25 (125.122.107.150:52647) Jan 20 15:07:00 k8s03 kernel: wireguard: wg0: Receiving handshake initiation from peer 25 (125.122.107.150:52647) Jan 20 15:07:00 k8s03 kernel: wireguard: wg0: Sending handshake response to peer 25 (125.122.107.150:52647) Jan 20 15:07:00 k8s03 kernel: wireguard: wg0: Keypair 83096 destroyed for peer 25 Jan 20 15:07:00 k8s03 kernel: wireguard: wg0: Keypair 83112 created for peer 25 dmesg|tail -20|grep wireguard [4222650.389928] wireguard: wg0: Receiving keepalive packet from peer 23 (125.122.107.150:50904) [4222652.081319] wireguard: wg0: Receiving keepalive packet from peer 22 (125.122.107.150:58715) [4222654.802308] wireguard: wg0: Receiving keepalive packet from peer 25 (125.122.107.150:53533) [4222675.389578] wireguard: wg0: Receiving keepalive packet from peer 23 (125.122.107.150:50904)
WireGuard 的安装和使用条件非常苛刻，对内核版本要求极高，不仅如此，在不同的系统中，内核，内核源码包，内核头文件必须存在且这三者版本要一致。所以一般不建议在生成环境中安装，除非你对自己的操作很有把握。Red Hat、CentOS、Fedora 等系统的内核，内核源码包，内核头文件包名分别为 kernel、kernel-devel、kernel-headers，Debian、Ubuntu 等系统的内核，内核源码包，内核头文件包名分别为 kernel、linux-headers。果这三者任一条件不满足的话，则不管是从代码编译安装还是从 repository 直接安装，也只是安装了 wireguard-tools 而已。而 WireGuard 真正工作的部分，是 wireguard-dkms，也就是动态内核模块支持(DKMS)，是它将 WireGuard 编译到系统内核中。因此，在某些 VPS 商家，是需要你先自主更换系统内核，并事先将这三者安装好，才有可能不会出现编译或安装失败。当然，目前 WireGuard 已经被合并到 Linux 5.6 内核中了，如果你的内核版本 >= 5.6，就可以用上原生的 WireGuard 了，只需要安装 wireguard-tools 即可。例如，对于 Ubuntu 20.04 来说，它的内核版本是 5.4，虽然小于 5.6，但经过我的测试发现它已经将 WireGuard 合并到了内核中，我们只需要安装 wireguard-tools 即可：$ sudo apt install wireguard -y下面讨论 WireGuard 在低版本内核中的安装方法。1. 升级内核对于 Ubuntu 等 apt 系的发行版来说，不需要升级内核即可安装 WireGuard，可以略过此步骤。如果你使用的是 CentOS 等 rpm 系的发行版，必须要升级内核，步骤如下：① 载入公钥$ rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org``` ② 升级安装 elrepo$ rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm`③ 载入 elrepo-kernel 元数据$ yum --disablerepo=\* --enablerepo=elrepo-kernel repolist``` ④ 安装最新版本的内核$ yum --disablerepo=* --enablerepo=elrepo-kernel install kernel-ml.x86_64 -y`⑤ 删除旧版本工具包$ yum remove kernel-tools-libs.x86_64 kernel-tools.x86_64 -y``` ⑥ 安装新版本工具包$ yum --disablerepo=* --enablerepo=elrepo-kernel install kernel-ml-tools kernel-ml-devel kernel-ml-headers -y`⑦ 查看内核插入顺序$ grep "^menuentry" /boot/grub2/grub.cfg | cut -d "'" -f2 CentOS Linux (3.10.0-1127.10.1.el7.x86_64) 7 (Core) CentOS Linux (5.7.2-1.el7.elrepo.x86_64) 7 (Core) CentOS Linux (0-rescue-96820b9851c24560b5f942f2496b9aeb) 7 (Core)``` 默认新内核是从头插入，默认启动顺序也是从 0 开始。 ⑧ 查看当前实际启动顺序$ grub2-editenv listsaved_entry=CentOS Linux (3.10.0-1127.10.1.el7.x86_64) 7 (Core)`⑨ 设置默认启动$ grub2-set-default 'CentOS Linux (5.7.2-1.el7.elrepo.x86_64) 7 (Core)'``` 最后重启检查：$ reboot$ uname -r`2. 安装 WireGuard升级内核之后，就可以根据官方文档来安装 WireGuard 了。不过这里我要介绍一个更狂野的安装方法，它更高效，也更不容易出错，那就是通过源代码编译安装。先别急着反驳，我知道从源代码编译看起来一点都不容易，但请听我说完。你以为我会教你如何从头开始编译吗？那不可能，有违我这篇文章的初衷，我要推荐一位大佬——秋水逸冰的一键安装脚本，它可以让你哼着小曲就能从源码编译安装 WireGuard，只需一条命令即可。脚本的使用方法超级简单，先下载脚本，然后赋予执行权限，最后执行一条命令搞定：$ wget --no-check-certificate -O /opt/wireguard.sh https://raw.githubusercontent.com/teddysun/across/master/wireguard.sh $ chmod 755 /opt/wireguard.sh $ /opt/wireguard.sh -s关于该脚本需要说明几点：支持两种安装方式：既支持从源代码编译安装，也支持从包管理器直接安装。脚本会创建默认的 wg0 设备，以及 wg0 的客户端配置，并生成客户端配置对应的二维码 png 图片。脚本会修改本机防火墙设置，如果未启用防火墙，则会出现警告提示，需要手动去设置。脚本会从 1024 到 20480 随机生成监听端口。脚本支持新增，删除，列出客户端功能。脚本支持查看已安装的 WireGuard 的版本号。脚本支持从代码编译安装的方式升级 WireGuard 到当前最新版本。对于咱手艺人来说，肯定是不想用它自动生成的配置的，如果你想自己生成配置文件，请直接将配置文件目录清空：$ rm -rf /etc/wireguard/*然后手动生成秘钥和配置文件，具体的流程请参考：WireGuard 的搭建使用与配置详解。如果你想通过 Web UI 来管理 WireGuard 的配置文件，可以看看这个项目：Wg Gen Web参考文档WireGuard 一键安装脚本
上篇文章介绍了 WireGuard 相对于其他 VPN 协议的优点和 WireGuard 的工作原理，本文将会学习如何从零开始配置 WireGuard，这里会涉及到很多高级的配置方法，例如动态 IP、NAT 到 NAT、IPv6 等等。1. 快速开始配置 WireGuard 的大致流程如下：安装Centos7$ yum install epel-release https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm $ yum install yum-plugin-elrepo $ yum install kmod-wireguard wireguard-tools # 如果你使用的是非标准内核，需要安装 DKMS 包 $ yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm $ curl -o /etc/yum.repos.d/jdoss-wireguard-epel-7.repo https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo $ yum install wireguard-dkms wireguard-toolsUbuntu# Ubuntu ≥ 18.04 $ apt install wireguard # Ubuntu ≤ 16.04 $ add-apt-repository ppa:wireguard/wireguard $ apt-get update $ apt-get install wireguardMacOS:$ brew install wireguard-toolsWindows:https://www.moyann.com/archives/298/ https://download.wireguard.com/windows-client/wireguard-amd64-0.1.1.msi在中继服务器上开启 IP 地址转发：$ echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf $ echo "net.ipv4.conf.all.proxy_arp = 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 $ iptables -t nat -A POSTROUTING -s 192.0.2.0/24 -o eth0 -j MASQUERADE需要把 eth0 改成你实际使用的网卡接口名称。编写配置文件配置文件可以放在任何路径下，但必须通过绝对路径引用。默认路径是 /etc/wireguard/wg0.conf。配置文件内容解析放到下一章节再讲。生成密钥#生成私钥 $ wg genkey > example.key # 生成公钥 $ wg pubkey < example.key > example.key.pub启动与停止$ wg-quick up /full/path/to/wg0.conf $ wg-quick down /full/path/to/wg0.conf # 启动/停止 VPN 网络接口 $ ip link set wg0 up $ ip link set wg0 down # 注册/注销 VPN 网络接口 $ ip link add dev wg0 type wireguard $ ip link delete dev wg0 # 注册/注销 本地 VPN 地址 $ ip address add dev wg0 192.0.2.3/32 $ ip address delete dev wg0 192.0.2.3/32 # 添加/删除 VPN 路由 $ ip route add 192.0.2.3/32 dev wg0 $ ip route delete 192.0.2.3/32 dev wg0查看信息接口：# 查看系统 VPN 接口信息 $ ip link show wg0 # 查看 VPN 接口详细信息 $ wg show all $ wg show wg0地址：# 查看 VPN 接口地址 $ ip address show wg0路由# 查看系统路由表 $ ip route show table main $ ip route show table local # 获取到特定 IP 的路由 $ ip route get 192.0.2.3一键安装一键安装请参考这个项目：WireGuard installer2. 配置详解WireGuard 使用 INI 语法作为其配置文件格式。配置文件可以放在任何路径下，但必须通过绝对路径引用。默认路径是 /etc/wireguard/wg0.conf。配置文件的命名形式必须为 ${WireGuard 接口的名称}.conf。通常情况下 WireGuard 接口名称以 wg 为前缀，并从 0 开始编号，但你也可以使用其他名称，只要符合正则表达式 ^[a-zA-Z0-9_=+.-]{1,15}$ 就行。你可以选择使用 wg 命令来手动配置 VPN，但一般建议使用 wg-quick，它提供了更强大和用户友好的配置体验，可以通过配置文件来管理配置。下面是一个配置文件示例：[Interface] # Name = node1.example.tld Address = 192.0.2.3/32 ListenPort = 51820 PrivateKey = localPrivateKeyAbcAbcAbc= DNS = 1.1.1.1,8.8.8.8 Table = 12345 MTU = 1500 PreUp = /bin/example arg1 arg2 %i PostUp = /bin/example arg1 arg2 %i PreDown = /bin/example arg1 arg2 %i PostDown = /bin/example arg1 arg2 %i [Peer] # Name = node2-node.example.tld AllowedIPs = 192.0.2.1/24 Endpoint = node1.example.tld:51820 PublicKey = remotePublicKeyAbcAbcAbc= PersistentKeepalive = 25[Interface]这一节定义本地 VPN 配置。例如：本地节点是客户端，只路由自身的流量，只暴露一个 IP。[Interface] # Name = phone.example-vpn.dev Address = 192.0.2.5/32 PrivateKey = <private key for phone.example-vpn.dev>本地节点是中继服务器，它可以将流量转发到其他对等节点（peer），并公开整个 VPN 子网的路由。[Interface] # Name = public-server1.example-vpn.tld Address = 192.0.2.1/24 ListenPort = 51820 PrivateKey = <private key for public-server1.example-vpn.tld> DNS = 1.1.1.1① # Name这是 INI 语法中的标准注释，用于展示该配置部分属于哪个节点。这部分配置会被 WireGuard 完全忽略，对 VPN 的行为没有任何影响。② Address定义本地节点应该对哪个地址范围进行路由。如果是常规的客户端，则将其设置为节点本身的单个 IP（使用 CIDR 指定，例如 192.0.2.3/32）；如果是中继服务器，则将其设置为可路由的子网范围。例如：常规客户端，只路由自身的流量：Address = 192.0.2.3/32中继服务器，可以将流量转发到其他对等节点（peer）：Address = 192.0.2.1/24也可以指定多个子网或 IPv6 子网：Address = 192.0.2.1/24,2001:DB8::/64③ ListenPort当本地节点是中继服务器时，需要通过该参数指定端口来监听传入 VPN 连接，默认端口号是 51820。常规客户端不需要此选项。④ PrivateKey本地节点的私钥，所有节点（包括中继服务器）都必须设置。不可与其他服务器共用。私钥可通过命令 wg genkey > example.key 来生成。⑤ DNS通过 DHCP 向客户端宣告 DNS 服务器。客户端将会使用这里指定的 DNS 服务器来处理 VPN 子网中的 DNS 请求，但也可以在系统中覆盖此选项。例如：如果不配置则使用系统默认 DNS可以指定单个 DNS：DNS = 1.1.1.1也可以指定多个 DNS：DNS = 1.1.1.1,8.8.8.8⑥ Table定义 VPN 子网使用的路由表，默认不需要设置。该参数有两个特殊的值需要注意：Table = off : 禁止创建路由Table = auto（默认值） : 将路由添加到系统默认的 table 中，并启用对默认路由的特殊处理。例如：Table = 1234⑦ MTU定义连接到对等节点（peer）的 MTU（Maximum Transmission Unit，最大传输单元），默认不需要设置，一般由系统自动确定。⑧ PreUp启动 VPN 接口之前运行的命令。这个选项可以指定多次，按顺序执行。例如：添加路由：PreUp = ip rule add ipproto tcp dport 22 table 1234⑨ PostUp启动 VPN 接口之后运行的命令。这个选项可以指定多次，按顺序执行。例如：从文件或某个命令的输出中读取配置值：PostUp = wg set %i private-key /etc/wireguard/wg0.key <(some command here)添加一行日志到文件中：PostUp = echo "$(date +%s) WireGuard Started" >> /var/log/wireguard.log调用 WebHook：PostUp = curl https://events.example.dev/wireguard/started/?key=abcdefg添加路由：PostUp = ip rule add ipproto tcp dport 22 table 1234添加 iptables 规则，启用数据包转发：PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE强制 WireGuard 重新解析对端域名的 IP 地址：PostUp = resolvectl domain %i "~."; resolvectl dns %i 192.0.2.1; resolvectl dnssec %i yes⑩ PreDown停止 VPN 接口之前运行的命令。这个选项可以指定多次，按顺序执行。例如：添加一行日志到文件中：PreDown = echo "$(date +%s) WireGuard Going Down" >> /var/log/wireguard.log调用 WebHook：PreDown = curl https://events.example.dev/wireguard/stopping/?key=abcdefg⑪ PostDown停止 VPN 接口之后运行的命令。这个选项可以指定多次，按顺序执行。例如：添加一行日志到文件中：PostDown = echo "$(date +%s) WireGuard Going Down" >> /var/log/wireguard.log调用 WebHook：PostDown = curl https://events.example.dev/wireguard/stopping/?key=abcdefg删除 iptables 规则，关闭数据包转发：PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE[Peer]定义能够为一个或多个地址路由流量的对等节点（peer）的 VPN 设置。对等节点（peer）可以是将流量转发到其他对等节点（peer）的中继服务器，也可以是通过公网或内网直连的客户端。中继服务器必须将所有的客户端定义为对等节点（peer），除了中继服务器之外，其他客户端都不能将位于 NAT 后面的节点定义为对等节点（peer），因为路由不可达。对于那些只为自己路由流量的客户端，只需将中继服务器作为对等节点（peer），以及其他需要直接访问的节点。举个例子，在下面的配置中，public-server1 作为中继服务器，其他的客户端有的是直连，有的位于 NAT 后面：public-server1（中继服务器）[peer] : public-server2, home-server, laptop, phonepublic-server2（直连客户端）[peer] : public-server1home-server（客户端位于 NAT 后面）[peer] : public-server1, public-server2laptop（客户端位于 NAT 后面）[peer] : public-server1, public-server2phone（客户端位于 NAT 后面）[peer] : public-server1, public-server2配置示例：对等节点（peer）是路由可达的客户端，只为自己路由流量[Peer] # Name = public-server2.example-vpn.dev Endpoint = public-server2.example-vpn.dev:51820 PublicKey = <public key for public-server2.example-vpn.dev> AllowedIPs = 192.0.2.2/32对等节点（peer）是位于 NAT 后面的客户端，只为自己路由流量[Peer] # Name = home-server.example-vpn.dev Endpoint = home-server.example-vpn.dev:51820 PublicKey = <public key for home-server.example-vpn.dev> AllowedIPs = 192.0.2.3/32对等节点（peer）是中继服务器，用来将流量转发到其他对等节点（peer）[Peer] # Name = public-server1.example-vpn.tld Endpoint = public-server1.example-vpn.tld:51820 PublicKey = <public key for public-server1.example-vpn.tld> # 路由整个 VPN 子网的流量 AllowedIPs = 192.0.2.1/24 PersistentKeepalive = 25① Endpoint指定远端对等节点（peer）的公网地址。如果对等节点（peer）位于 NAT 后面或者没有稳定的公网访问地址，就忽略这个字段。通常只需要指定中继服务器的 Endpoint，当然有稳定公网 IP 的节点也可以指定。例如：通过 IP 指定：Endpoint = 123.124.125.126:51820通过域名指定：Endpoint = public-server1.example-vpn.tld:51820② AllowedIPs允许该对等节点（peer）发送过来的 VPN 流量中的源地址范围。同时这个字段也会作为本机路由表中 wg0 绑定的 IP 地址范围。如果对等节点（peer）是常规的客户端，则将其设置为节点本身的单个 IP；如果对等节点（peer）是中继服务器，则将其设置为可路由的子网范围。可以使用 , 来指定多个 IP 或子网范围。该字段也可以指定多次。当决定如何对一个数据包进行路由时，系统首先会选择最具体的路由，如果不匹配再选择更宽泛的路由。例如，对于一个发往 192.0.2.3 的数据包，系统首先会寻找地址为 192.0.2.3/32 的对等节点（peer），如果没有再寻找地址为 192.0.2.1/24 的对等节点（peer），以此类推。例如：对等节点（peer）是常规客户端，只路由自身的流量：AllowedIPs = 192.0.2.3/32对等节点（peer）是中继服务器，可以将流量转发到其他对等节点（peer）：AllowedIPs = 192.0.2.1/24对等节点（peer）是中继服务器，可以转发所有的流量，包括外网流量和 VPN 流量，可以用来干嘛你懂得：AllowedIPs = 0.0.0.0/0,::/0对等节点（peer）是中继服务器，可以路由其自身和其他对等节点（peer）的流量：AllowedIPs = 192.0.2.3/32,192.0.2.4/32对等节点（peer）是中继服务器，可以路由其自身的流量和它所在的内网的流量：AllowedIPs = 192.0.2.3/32,192.168.1.1/24③ PublicKey对等节点（peer）的公钥，所有节点（包括中继服务器）都必须设置。可与其他对等节点（peer）共用同一个公钥。公钥可通过命令 wg pubkey < example.key > example.key.pub 来生成，其中 example.key 是上面生成的私钥。例如：PublicKey = somePublicKeyAbcdAbcdAbcdAbcd=④ PersistentKeepalive如果连接是从一个位于 NAT 后面的对等节点（peer）到一个公网可达的对等节点（peer），那么 NAT 后面的对等节点（peer）必须定期发送一个出站 ping 包来检查连通性，如果 IP 有变化，就会自动更新Endpoint。例如：本地节点与对等节点（peer）可直连：该字段不需要指定，因为不需要连接检查。对等节点（peer）位于 NAT 后面：该字段不需要指定，因为维持连接是客户端（连接的发起方）的责任。本地节点位于 NAT 后面，对等节点（peer）公网可达：需要指定该字段 PersistentKeepalive = 25，表示每隔 25 秒发送一次 ping 来检查连接。3 . 高级特性IPv6前面的例子主要使用 IPv4，WireGuard 也支持 IPv6。例如：[Interface] AllowedIps = 192.0.2.3/24, 2001:DB8::/64 [Peer] ... AllowedIPs = 0.0.0.0/0, ::/0转发所有流量如果你想通过 VPN 转发所有的流量，包括 VPN 子网和公网流量，需要在 [Peer] 的 AllowedIPs 中添加 0.0.0.0/0, ::/0。即便只转发 IPv4 流量，也要指定一个 IPv6 网段，以避免将 IPv6 数据包泄露到 VPN 之外。详情参考：https://www.reddit.com/r/WireGuard/comments/b0m5g2/ipv6_leaks_psa_for_anyone_here_using_wireguard_to/例如：[Interface] # Name = phone.example-vpn.dev Address = 192.0.2.3/32 PrivateKey = <private key for phone.example-vpn.dev> [Peer] # Name = public-server1.example-vpn.dev PublicKey = <public key for public-server1.example-vpn.dev> Endpoint = public-server1.example-vpn.dev:51820 AllowedIPs = 0.0.0.0/0, ::/0一般只有把 VPN 当做武当纵云梯来用时，才会需要转发所有流量，不多说，点到为止。NAT-to-NAT 连接如果两个对等节点（peer）都位于 NAT 后面，想不通过中继服务器直接连接，需要保证至少有一个对等节点（peer）具有稳定的公网出口，使用静态公网 IP 或者通过 DDNS 动态更新 FQDN 都可以。WebRTC 协议可以动态配置两个 NAT 之间的连接，它可以通过信令服务器来检测每个主机的 IP:Port 组合。而 WireGuard 没有这个功能，它没有没有信令服务器来动态搜索其他主机，只能硬编码 Endpoint+ListenPort，并通过 PersistentKeepalive 来维持连接。总结一下 NAT-to-NAT 连接的前提条件：至少有一个对等节点（peer）有固定的公网 IP，如果都没有固定的公网 IP，也可以使用 DDNS 来维护一个稳定的域名。至少有一个对等节点（peer）指定 UDP ListenPort，而且它的 NAT 路由器不能做 UDP 源端口随机化，否则返回的数据包将被发送到之前指定的 ListenPort，并被路由器丢弃，不会发送到新分配的随机端口。所有的对等节点（peer）必须在 [Peer] 配置中启用其他对等节点（peer）的 PersistentKeepalive，这样就可以维持连接的持久性。对于通信双方来说，只要服务端所在的 NAT 路由器没有指定到 NAT 后面的对等节点（peer）的转发规则，就需要进行 UDP 打洞。UDP 打洞的原理：Peer1 向 Peer2 发送一个 UDP 数据包，不过 Peer2 的 NAT 路由器不知道该将这个包发给谁，直接丢弃了，不过没关系，这一步的目的是让 Peer1 的 NAT 路由器能够接收 UDP 响应并转 发到后面的 Peer1。Peer2 向 Peer1 发送一个 UDP 数据包，由于上一步的作用，Peer1 的 NAT 路由器已经建立临时转发规则，可以接收 UDP 响应，所以可以接收到该数据包，并转发到 Peer1。Peer1 向 Peer2 发送一个 UDP 响应，由于上一步的作用，由于上一步的作用，Peer2 的 NAT 路由器已经可以接收 UDP 响应，所以可以接收到该数据包，并转发到 Peer2。这种发送一个初始的数据包被拒绝，然后利用路由器已建立的转发规则来接收响应的过程被称为 『UDP 打洞』。当你发送一个 UDP 数据包出去时，路由器通常会创建一个临时规则来映射源地址/端口和目的地址/端口，反之亦然。从目的地址和端口返回的 UDP 数据包会被转发到原来的源地址和端口，这就是大多数 UDP 应用在 NAT 后面的运作方式（如 BitTorrent、Skype 等）。这个临时规则会在一段时间后失效，所以 NAT 后面的客户端必须通过 PersistentKeepalive 定期发送数据包来维持连接的持久性。当两个对等节点（peer）都位于 NAT 后面时，要想让 UDP 打洞生效，需要两个节点在差不多的时间向对方发送数据包，这就意味着双方需要提前知道对方的公网地址和端口号，可以在 wg0.conf 中指定。UDP 打洞的局限性从 2019 年开始，很多以前用过的老式打洞方法都不再有效了。以前很著名的就是 pwnat 开创的一种新的打洞方法，它能够在不需要代理、第三方服务器、upnp、DMZ、sproofing、dns 转换的情况下实现 NAT 中的 P2P 通信。它的原理也很简单：通过让客户端假装成为一个互联网上任意的 ICMP 跳跃点（ a random hop on the Internet）来解决这个问题，从而让服务端能够获取到客户端的 IP 地址。traceroute 命令也是使用这项技术来检测 Internet 上的跳跃点。具体来说，当服务器启动时，它开始向固定地址 3.3.3.3 发送固定的 ICMP 回应请求包（ICMP echo request packets）。显然，我们无法从 3.3.3.3 收到返回的 ICMP 回应数据包（ICMP echo packets）。然而，3.3.3.3 并不是我们可以访问的主机，我们也不是想伪装成它来发 ICMP 回应数据包。相反，pwnat 技术的实现原理在于，当我们的客户端想要连接服务端时，客户端（知道服务器IP地址）会向服务端送 ICMP 超时数据包（ICMP Time Exceeded packet）。 这个 ICMP 数据包里面包含了服务端发送到 3.3.3.3 的原始固定 ICMP 回应请求包。为什么要这样做呢？好吧，我们假装是互联网上的一个 ICMP 跳越点，礼貌地告诉服务器它原来的 ICMP 回应请求包无法传递到 3.3.3.3。而你的 NAT 是一个聪明的设备，它会注意到 ICMP 超时数据包内的数据包与服务器发出 ICMP 回应请求包相匹配。然后它将 ICMP 超时数据包转发回 NAT 后面的服务器，包括来自客户端的完整 IP 数据包头，从而让服务端知道客户端 IP 地址是什么！现在这种类似的 UDP 打洞方法受到了很多的限制，详情可以参考上篇文章，这里不过多阐述。除了 UDP 打洞之外，我们仍然可以使用硬编码的方式指定两个对等节点（peer）的公网地址和端口号，这个方法对大多数 NAT 网络都有效。源端口随机化如果所有的对等节点（peer）都在具有严格的 UDP 源端口随机化的 NAT 后面（比如大多数蜂窝网络），那么无法实现 NAT-to-NAT 连接。因为双方都无法协商出一个 ListenPort，并保证自己的 NAT 在发出 ping 包后能够接收发往该端口的流量，所以就无法初始化打洞，导致连接失败。因此，一般在 LTE/3G 网络中无法进行 p2p 通信。使用信令服务器上节提到了，如果所有的对等节点（peer）都在具有严格的 UDP 源端口随机化的 NAT 后面，就无法直接实现 NAT-to-NAT 连接，但通过第三方的信令服务器是可以实现的。信令服务器相当于一个中转站，它会告诉通信双方关于对方的 IP:Port 信息。这里有几个项目可以参考：https://github.com/takutakahashi/wg-connecthttps://git.zx2c4.com/wireguard-tools/tree/contrib/nat-hole-punching/动态 IP 地址WireGuard 只会在启动时解析域名，如果你使用 DDNS 来动态更新域名解析，那么每当 IP 发生变化时，就需要重新启动 WireGuard。目前建议的解决方案是使用 PostUp 钩子每隔几分钟或几小时重新启动 WireGuard 来强制解析域名。总的来说，NAT-to-NAT 连接极为不稳定，而且还有一堆其他的限制，所以还是建议通过中继服务器来通信。NAT-to-NAT 配置示例：Peer1：[Interface] ... ListenPort = 12000 [Peer] ... Endpoint = peer2.example-vpn.dev:12000 PersistentKeepalive = 25Peer2：[Interface] ... ListenPort = 12000 [Peer] ... Endpoint = peer1.example-vpn.dev:12000 PersistentKeepalive = 25更多资料：https://github.com/samyk/pwnathttps://en.wikipedia.org/wiki/UDP_hole_punchinghttps://stackoverflow.com/questions/8892142/udp-hole-punching-algorithmhttps://stackoverflow.com/questions/12359502/udp-hole-punching-not-going-through-on-3ghttps://stackoverflow.com/questions/11819349/udp-hole-punching-not-possible-with-mobile-providerhttps://github.com/WireGuard/WireGuard/tree/master/contrib/examples/nat-hole-punchinghttps://staaldraad.github.io/2017/04/17/nat-to-nat-with-wireguard/https://golb.hplar.ch/2019/01/expose-server-vpn.html动态分配子网 IP这里指的是对等节点（peer）的 VPN 子网 IP 的动态分配，类似于 DHCP，不是指 Endpoint。WireGuard 官方已经在开发动态分配子网 IP 的功能，具体的实现可以看这里：WireGuard/wg-dynamic当然，你也可以使用 PostUp 在运行时从文件中读取 IP 值来实现一个动态分配 IP 的系统，类似于 Kubernetes 的 CNI 插件。例如：[Interface] ... PostUp = wg set %i allowed-ips /etc/wireguard/wg0.key <(some command)共享一个 peers.conf 文件介绍一个秘密功能，可以简化 WireGuard 的配置工作。如果某个 peer 的公钥与本地接口的私钥能够配对，那么 WireGuard 会忽略该 peer。利用这个特性，我们可以在所有节点上共用同一个 peer 列表，每个节点只需要单独定义一个 [Interface] 就行了，即使列表中有本节点，也会被忽略。具体方式如下：每个对等节点（peer）都有一个单独的 /etc/wireguard/wg0.conf 文件，只包含 [Interface] 部分的配置。每个对等节点（peer）共用同一个 /etc/wireguard/peers.conf 文件，其中包含了所有的 peer。Wg0.conf 文件中需要配置一个 PostUp 钩子，内容为 PostUp = wg addconf /etc/wireguard/peers.conf。关于 peers.conf 的共享方式有很多种，你可以通过 ansible 这样的工具来分发，可以使用 Dropbox 之类的网盘来同步，当然也可以使用 ceph 这种分布式文件系统来将其挂载到不同的节点上。从文件或命令输出中读取配置WireGuard 也可以从任意命令的输出或文件中读取内容来修改配置的值，利用这个特性可以方便管理密钥，例如可以在运行时从 Kubernetes Secrets 或 AWS KMS 等第三方服务读取密钥。容器化WireGuard 也可以跑在容器中，最简单的方式是使用 --privileged 和 --cap-add=all 参数，让容器可以加载内核模块。你可以让 WireGuard 跑在容器中，向宿主机暴露一个网络接口；也可以让 WireGuard 运行在宿主机中，向特定的容器暴露一个接口。下面给出一个具体的示例，本示例中的 vpn_test 容器通过 WireGuard 中继服务器来路由所有流量。本示例中给出的容器配置是 docker-compose 的配置文件格式。中继服务器容器配置：version: '3' services: wireguard: image: linuxserver/wireguard ports: - 51820:51820/udp cap_add: - NET_ADMIN - SYS_MODULE volumes: - /lib/modules:/lib/modules - ./wg0.conf:/config/wg0.conf:ro中继服务器 WireGuard 配置 wg0.conf：[Interface] # Name = relay1.wg.example.com Address = 192.0.2.1/24 ListenPort = 51820 PrivateKey = oJpRt2Oq27vIB5/UVb7BRqCwad2YMReQgH5tlxz8YmI= DNS = 1.1.1.1,8.8.8.8 PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth0 -j MASQUERADE [Peer] # Name = peer1.wg.example.com PublicKey = I+hXRAJOG/UE2IQvIHsou2zTgkUyPve2pzvHTnd/2Gg= AllowedIPs = 192.0.2.2/32客户端容器配置：version: '3' services: wireguard: image: linuxserver/wireguard cap_add: - NET_ADMIN - SYS_MODULE volumes: - /lib/modules:/lib/modules - ./wg0.conf:/config/wg0.conf:ro vpn_test: image: curlimages/curl entrypoint: curl -s http://whatismyip.akamai.com/ network_mode: 'service:wireguard'客户端 WireGuard 配置 wg0.conf：[Interface] # Name = peer1.wg.example.com Address = 192.0.2.2/32 PrivateKey = YCW76edD4W7nZrPbWZxPZhcs32CsBLIi1sEhsV/sgk8= DNS = 1.1.1.1,8.8.8.8 [Peer] # Name = relay1.wg.example.com Endpoint = relay1.wg.example.com:51820 PublicKey = zJNKewtL3gcHdG62V3GaBkErFtapJWsAx+2um0c0B1s= AllowedIPs = 192.0.2.1/24,0.0.0.0/0 PersistentKeepalive = 21
WireGuard 是由 Jason Donenfeld 等人用 C 语言编写的一个开源 VPN 协议，被视为下一代 VPN 协议，旨在解决许多困扰 IPSec/IKEv2、OpenVPN 或 L2TP 等其他 VPN 协议的问题。它与 Tinc 和 MeshBird 等现代 VPN 产品有一些相似之处，即加密技术先进、配置简单。从 2020 年 1 月开始，它已经并入了 Linux 内核的 5.6 版本，这意味着大多数 Linux 发行版的用户将拥有一个开箱即用的 WireGuard。无论你是想破墙而出，还是想在服务器之间组网，WireGuard 都不会让你失望，它就是组网的『乐高积木』，就像 ZFS 是构建文件系统的『乐高积木』一样。WireGuard 与其他 VPN 协议的性能测试对比：可以看到 WireGuard 直接碾压其他 VPN 协议。再来说说 OpenVPN，大约有 10 万行代码，而 WireGuard 只有大概 4000 行代码，代码库相当精简，简直就是件艺术品啊。你再看看 OpenVPN 的性能，算了不说了。WireGuard 优点：配置精简，可直接使用默认值只需最少的密钥管理工作，每个主机只需要 1 个公钥和 1 个私钥。就像普通的以太网接口一样，以 Linux 内核模块的形式运行，资源占用小。能够将部分流量或所有流量通过 VPN 传送到局域网内的任意主机。能够在网络故障恢复之后自动重连，戳到了其他 VPN 的痛处。比目前主流的 VPN 协议，连接速度要更快，延迟更低（见上图）。使用了更先进的加密技术，具有前向加密和抗降级攻击的能力。支持任何类型的二层网络通信，例如 ARP、DHCP 和 ICMP，而不仅仅是 TCP/HTTP。可以运行在主机中为容器之间提供通信，也可以运行在容器中为主机之间提供通信。WireGuard 不能做的事：类似 gossip 协议实现网络自愈。通过信令服务器突破双重 NAT。通过中央服务器自动分配和撤销密钥。发送原始的二层以太网帧。当然，你可以使用 WireGuard 作为底层协议来实现自己想要的功能，从而弥补上述这些缺憾。本系列 WireGuard 教程分为两个部分，第一部分偏理论，第二部分偏实践。本文是第一部分，下面开始正文教程。1. WireGuard 术语Peer/Node/Device连接到 VPN 并为自己注册一个 VPN 子网地址（如 192.0.2.3）的主机。还可以通过使用逗号分隔的 CIDR 指定子网范围，为其自身地址以外的 IP 地址选择路由。中继服务器（Bounce Server）一个公网可达的对等节点，可以将流量中继到 NAT 后面的其他对等节点。Bounce Server 并不是特殊的节点，它和其他对等节点一样，唯一的区别是它有公网 IP，并且开启了内核级别的 IP 转发，可以将 VPN 的流量转发到其他客户端。子网（Subnet）一组私有 IP，例如 192.0.2.1-255 或 192.168.1.1/24，一般在 NAT 后面，例如办公室局域网或家庭网络。CIDR 表示法这是一种使用掩码表示子网大小的方式，这个不用解释了。NAT子网的私有 IP 地址由路由器提供，通过公网无法直接访问私有子网设备，需要通过 NAT 做网络地址转换。路由器会跟踪发出的连接，并将响应转发到正确的内部 IP。公开端点（Public Endpoint）节点的公网 IP 地址:端口，例如 123.124.125.126:1234，或者直接使用域名 some.domain.tld:1234。如果对等节点不在同一子网中，那么节点的公开端点必须使用公网 IP 地址。私钥（Private key）单个节点的 WireGuard 私钥，生成方法是：wg genkey > example.key。公钥（Public key）单个节点的 WireGuard 公钥，生成方式为：wg pubkey < example.key > example.key.pub。DNS域名服务器，用于将域名解析为 VPN 客户端的 IP，不让 DNS请求泄漏到 VPN 之外。2. WireGuard 工作原理中继服务器工作原理中继服务器（Bounce Server）和普通的对等节点一样，它能够在 NAT 后面的 VPN 客户端之间充当中继服务器，可以将收到的任何 VPN 子网流量转发到正确的对等节点。事实上 WireGuard 并不关心流量是如何转发的，这个由系统内核和 iptables 规则处理。如果所有的对等节点都是公网可达的，则不需要考虑中继服务器，只有当有对等节点位于 NAT 后面时才需要考虑。在 WireGuard 里，客户端和服务端基本是平等的，差别只是谁主动连接谁而已。双方都会监听一个 UDP 端口，谁主动连接，谁就是客户端。主动连接的客户端需要指定对端的公网地址和端口，被动连接的服务端不需要指定其他对等节点的地址和端口。如果客户端和服务端都位于 NAT 后面，需要加一个中继服务器，客户端和服务端都指定中继服务器作为对等节点，它们的通信流量会先进入中继服务器，然后再转发到对端。WireGuard 是支持漫游的，也就是说，双方不管谁的地址变动了，WireGuard 在看到对方从新地址说话的时候，就会记住它的新地址（跟 mosh 一样，不过是双向的）。所以双方要是一直保持在线，并且通信足够频繁的话（比如配置 persistent-keepalive），两边的 IP 都不固定也不影响的。Wireguard 如何路由流量利用 WireGuard 可以组建非常复杂的网络拓扑，这里主要介绍几个典型的拓扑：① 端到端直接连接这是最简单的拓扑，所有的节点要么在同一个局域网，要么直接通过公网访问，这样 WireGuard 可以直接连接到对端，不需要中继跳转。② 一端位于 NAT 后面，另一端直接通过公网暴露这种情况下，最简单的方案是：通过公网暴露的一端作为服务端，另一端指定服务端的公网地址和端口，然后通过 persistent-keepalive 选项维持长连接，让 NAT 记得对应的映射关系。③ 两端都位于 NAT 后面，通过中继服务器连接大多数情况下，当通信双方都在 NAT 后面的时候，NAT 会做源端口随机化处理，直接连接可能比较困难。可以加一个中继服务器，通信双方都将中继服务器作为对端，然后维持长连接，流量就会通过中继服务器进行转发。④ 两端都位于 NAT 后面，通过 UDP NAT 打洞上面也提到了，当通信双方都在 NAT 后面的时候，直接连接不太现实，因为大多数 NAT 路由器对源端口的随机化相当严格，不可能提前为双方协调一个固定开放的端口。必须使用一个信令服务器（STUN），它会在中间沟通分配给对方哪些随机源端口。通信双方都会和公共信令服务器进行初始连接，然后它记录下随机的源端口，并将其返回给客户端。这其实就是现代 P2P 网络中 WebRTC 的工作原理。有时候，即使有了信令服务器和两端已知的源端口，也无法直接连接，因为 NAT 路由器严格规定只接受来自原始目的地址（信令服务器）的流量，会要求新开一个随机源端口来接受来自其他 IP 的流量（比如其他客户端试图使用原来的通信源端口）。运营商级别的 NAT 就是这么干的，比如蜂窝网络和一些企业网络，它们专门用这种方法来防止打洞连接。更多细节请参考下一部分的 NAT 到 NAT 连接实践的章节。如果某一端同时连接了多个对端，当它想访问某个 IP 时，如果有具体的路由可用，则优先使用具体的路由，否则就会将流量转发到中继服务器，然后中继服务器再根据系统路由表进行转发。你可以通过测量 ping 的时间来计算每一跳的长度，并通过检查对端的输出（wg show wg0）来找到 WireGuard 对一个给定地址的路由方式。WireGuard 报文格式WireGuard 使用加密的 UDP 报文来封装所有的数据，UDP 不保证数据包一定能送达，也不保证按顺序到达，但隧道内的 TCP 连接可以保证数据有效交付。WireGuard 的报文格式如下图所示：关于 WireGuard 报文的更多信息可以参考下面几篇文档：https://www.wireshark.org/docs/dfref/w/wg.htmlhttps://github.com/Lekensteyn/wireguard-dissectorhttps://nbsoftsolutions.com/blog/viewing-wireguard-traffic-with-tcpdumpWireGuard 的性能WireGuard 声称其性能比大多数 VPN 协议更好，但这个事情有很多争议，比如某些加密方式支持硬件层面的加速。WireGuard 直接在内核层面处理路由，直接使用系统内核的加密模块来加密数据，和 Linux 原本内置的密码子系统共存，原有的子系统能通过 API 使用 WireGuard 的 Zinc 密码库。WireGuard 使用 UDP 协议传输数据，在不使用的情况下默认不会传输任何 UDP 数据包，所以比常规 VPN 省电很多，可以像 55 一样一直挂着使用，速度相比其他 VPN 也是压倒性优势。关于性能比较的更多信息可以参考下面几篇文档：https://www.wireguard.com/performance/https://www.reddit.com/r/linux/comments/9bnowo/wireguard_benchmark_between_two_servers_with_10/https://restoreprivacy.com/openvpn-ipsec-wireguard-l2tp-ikev2-protocols/WireGuard 安全模型WireGuard 使用以下加密技术来保障数据的安全：使用 ChaCha20 进行对称加密，使用 Poly1305 进行数据验证。利用 Curve25519 进行密钥交换。使用 BLAKE2 作为哈希函数。使用 HKDF 进行解密。WireGuard 的加密技术本质上是 Trevor Perrin 的 Noise 框架的实例化，它简单高效，其他的 VPN 都是通过一系列协商、握手和复杂的状态机来保障安全性。WireGuard 就相当于 VPN 协议中的 qmail，代码量比其他 VPN 协议少了好几个数量级。关于 WireGuard 加密的更多资料请参考下方链接：https://www.wireguard.com/papers/wireguard.pdfhttps://eprint.iacr.org/2018/080.pdfhttps://courses.csail.mit.edu/6.857/2018/project/He-Xu-Xu-WireGuard.pdfhttps://www.wireguard.com/talks/blackhat2018-slides.pdfhttps://arstechnica.com/gadgets/2018/08/wireguard-vpn-review-fast-connections-amaze-but-windows-support-needs-to-happen/WireGuard 密钥管理WireGuard 通过为每个对等节点提供简单的公钥和私钥来实现双向认证，每个对等节点在设置阶段生成密钥，且只在对等节点之间共享密钥。每个节点除了公钥和私钥，不再需要其他证书或预共享密钥。在更大规模的部署中，可以使用 Ansible 或 Kubernetes Secrets 等单独的服务来处理密钥的生成、分发和销毁。下面是一些有助于密钥分发和部署的服务：https://pypi.org/project/wireguard-p2p/https://github.com/trailofbits/algohttps://github.com/StreisandEffect/streisandhttps://github.com/its0x08/wg-installhttps://github.com/brittson/wireguard_config_makerhttps://www.wireguardconfig.com/如果你不想在 wg0.conf 配置文件中直接硬编码，可以从文件或命令中读取密钥，这使得通过第三方服务管理密钥变得更加容易：[Interface] ... PostUp = wg set %i private-key /etc/wireguard/wg0.key <(cat /some/path/%i/privkey)从技术上讲，多个服务端之间可以共享相同的私钥，只要客户端不使用相同的密钥同时连接到两个服务器。但有时客户端会需要同时连接多台服务器，例如，你可以使用 DNS 轮询来均衡两台服务器之间的连接，这两台服务器配置相同。大多数情况下，每个对等节点都应该使用独立的的公钥和私钥，这样每个对等节点都不能读取到对方的流量，保障了安全性。理论部分就到这里，下篇文章将会手把手教你如何从零开始配置 WireGuard，这里会涉及到很多高级的配置方法，例如动态 IP、NAT 到 NAT、IPv6 等等。
2017年，新一代VPN技术wireguard诞生。wireguard基于linux kernel内核运行，效率极高，速度很快，而且支持设备IP地址漫游功能，不仅适合服务器之间的互联，还适合在NAT环境下使用，包括家中的智能路由器，配合openwrt等路由器，可安装wireguard，实现路由器绑定wireguard代理功能。其实我已经用Wireguard 很久了，但是一直没有时间去写如何配置，抽空写一篇博客来证明一下我自己还躺下一、 Wireguard 服务器配置我用的是CentOS 8 64bit系统，首先先更新系统内核，我们执行以下命令#1.导入key rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org #2.安装 ELRepo for RHEL-8 or CentOS-8: dnf install https://www.elrepo.org/elrepo-release-8.1-1.el8.elrepo.noarch.rpm #3.安装内核 dnf --enablerepo=elrepo-kernel install kernel-ml-devel kernel-ml 重启服务器后确认下内核是否安装成功grubby --default-kernel /boot/vmlinuz-5.5.5-1.el8.elrepo.x86_64 uname -r 5.5.5-1.el8.elrepo.x86_64启动完毕后我们加入 Wireguard 的 yum 源sudo curl -Lo /etc/yum.repos.d/wireguard.repo https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo sudo yum install epel-release -y sudo yum install wireguard-dkms wireguard-tools -y记得要开启IPv4的转发echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf sysctl -p随后，使用命令创建Publickey和PrivateKeymkdir /etc/wireguard cd /etc/wireguard wg genkey | tee privatekey | wg pubkey > publickey chmod 777 -R /etc/wireguard vim /etc/wireguard/wg0.conf服务器端内容如下：[Interface] Address = 10.0.0.1/24 ListenPort = 6677 PrivateKey = <Private Key> PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE SaveConfig = truePrivateKey则是你刚生成的PrivateKey，需要填入进去。PostUP和PostDown是开启和关闭时分别执行的命令，你需要根据需求自行修改。创建服务器端的自动启动systemctl enable wg-quick@wg0启动服务器端wg-quick up wg0至此，服务器端已经配置完毕，我们需要配置客户端二、客户端配置安装过程与服务器一致，但是配置文件是不一样的，具体的需要看你的需求。假设我们需要将两台服务器互联，以便访问其内网中设备。我们的配置将如下：[Interface] Address = 10.0.0.2/24 ListenPort = 6677 PrivateKey = <Private Key> PostUp = bash /etc/route-add PostDown = bash /etc/route-del SaveConfig = true [Peer] PublicKey = <服务器端的Public Key> AllowedIPs = 10.0.0.1/32 Endpoint = 服务器端的公网IP:6677然后，这边需要注意的是AllowedIPs 如果你写了0.0.0.0/0，你可能会被全部reroute，从而导致连不上服务器。因此我这边推荐你设置为两边的IP先测试完毕再调全局。随后一样的，启动wireguard。在服务器端设置以下内容wg set wg0 peer <客户端的Public Key> allowed-ips 10.0.0.1/32然后你会发现两个内网IP可以互通，ping -c 10 10.0.0.1 PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data. 64 bytes from 10.0.0.1: icmp_seq=1 ttl=64 time=28.5 ms 64 bytes from 10.0.0.1: icmp_seq=2 ttl=64 time=28.4 ms 64 bytes from 10.0.0.1: icmp_seq=3 ttl=64 time=28.5 ms 64 bytes from 10.0.0.1: icmp_seq=4 ttl=64 time=28.5 ms 64 bytes from 10.0.0.1: icmp_seq=5 ttl=64 time=28.5 ms 64 bytes from 10.0.0.1: icmp_seq=6 ttl=64 time=28.3 ms 64 bytes from 10.0.0.1: icmp_seq=7 ttl=64 time=28.6 ms 64 bytes from 10.0.0.1: icmp_seq=8 ttl=64 time=28.6 ms 64 bytes from 10.0.0.1: icmp_seq=9 ttl=64 time=28.3 ms 64 bytes from 10.0.0.1: icmp_seq=10 ttl=64 time=28.5 ms --- 10.0.0.1 ping statistics --- 10 packets transmitted, 10 received, 0% packet loss, time 9012ms rtt min/avg/max/mdev = 28.360/28.522/28.688/0.207 ms那么，我们的wireguard就算是通了，现在要仔细来调整这个路由让他来符合我们的需求。就拿刚刚所说，如果是为了访问互相的内网，你需要把内网IP加入到 AllowedIPs 里面，用逗号区分。例如:[Interface] Address = 10.0.0.2/24 ListenPort = 56677 PrivateKey = <Private Key> PostUp = bash /etc/route-add PostDown = bash /etc/route-del SaveConfig = true [Peer] PublicKey = <服务器端的Public Key> AllowedIPs = 10.0.0.1/32, 192.168.0.0/16 Endpoint = 服务器端的公网IP:6677在你启动wireguard后，你能访问到服务器端的192.168.0.0/16这个段，当然，这种可以认为是对等互联，所以不存在服务器或者客户端这种说法。而另外一种做法，是在路由器上部署的，实现翻墙功能，这种配置应该是这么写的[Interface] Address = 10.0.0.2/24 ListenPort = 56660 PrivateKey = <Private Key> PostUp = bash /etc/route-add PostDown = bash /etc/route-del SaveConfig = true [Peer] PublicKey = <服务器端的Public Key> AllowedIPs = 0.0.0.0/0 Endpoint = 服务器端的公网IP:6677 PersistentKeepalive = 25另外一点，你需要编辑一下 /etc/route-add 来确保你的服务器IP不走wireguard，否则可能会连不上。启动后，默认会将你所有流量都通过wg0这个接口到你的服务器上，实现翻墙另外附送一个配置脚本（并非安装哈）：#! /bin/bash config_dir="$HOME/.wireguard/" mkdir -p "$config_dir" cd "$config_dir" || { echo 切换目录失败，程序退出 exit } # 生成两对密钥，分别用作服务器和客户端使用 wg genkey | tee pri1 | wg pubkey >pub1 wg genkey | tee pri2 | wg pubkey >pub2 # 设置密钥访问权限 chmod 600 pri1 chmod 600 pri2 interface=$(ip -o -4 route show to default | awk '{print $5}') ip=$(ip -4 addr show "$interface" | grep -oP '(?<=inet\s)\d+(\.\d+){3}') # 生成服务端配置文件 cat >wg0.conf <<EOL [Interface] PrivateKey = $(cat pri1) Address = 10.10.10.1 ListenPort = 54321 PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o $interface -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o $interface -j MASQUERADE [Peer] PublicKey = $(cat pub2) AllowedIPs = 10.10.10.2/32 EOL # 生成客户端配置文件 cat >client.conf <<EOL [Interface] PrivateKey = $(cat pri2) Address = 10.10.10.2 DNS = 8.8.8.8 [Peer] PublicKey = $(cat pub1) Endpoint = $ip:54321 AllowedIPs = 0.0.0.0/0 EOL # 复制配置文件并启动 sudo cp wg0.conf /etc/wireguard/ || { echo 复制失败,请检查/etc/wireguard目录或wg0.conf是否存在 exit } sudo systemctl start wg-quick@wg0 || { echo 启动wireguard失败，请检查/etc/wireguard/wg0.conf是否存在错误 exit } sudo systemctl enable wg-quick@wg0 # 显示客户端配置文件 echo "----------以下是客户端配置文件，请保存并在客户端中使用----------" cat client.conf echo "----------以下是客户端配置二维码----------" echo "qrencode -t ansiutf8 <~/.wireguard/client.conf 再次显示" qrencode -t ansiutf8 <client.conf参考文章：https://wiki.archlinux.org/index.php/WireGuard_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)