【OPS.0x04】使用 Wireguard 进行异地组网

本文最后更新于:2024年10月23日 凌晨

龙哥就是龙!惹啊!

0x00. 一切开始之前

最近手上的各种服务器不知道为啥多了起来,但大都没有公网 IP,又都处在不同的局域网里,笔者的肉身连带自己的笔记本也没有办法同时处在这些不同的网络当中,因此笔者迫切需要一个可用且优雅的异地组网方案,从而让笔者能够无论身处何处都能随时访问不同局域网中的任意一台服务器, 当然现在看来这些服务器大部分情况下的用途似乎就是让笔者闲暇时刻运行 neofetch 和 btop 玩玩

局域网(Local Area Network)也就是我们常说的内网,此类网络通常共用一个连接到互联网(公网)的路由器,共用同一公网 IP,利用网络地址转换(Network Address Translation)技术为内网中的设备与公网服务器建立连接(简而言之就是公网 IP:端口 到内网 IP:端口 整了个映射),因此要与内网中的设备进行通信,我们实质上要实现 内网穿透 ,也叫 NAT 穿透 (NAT traversal)

这个需求的实现其实很简单,我们只需要把不同 LAN 当中的设备组到同一个虚拟网络当中就行—— Virtual Private Network 就是专业干这个的,我们可以使用 VPN 技术在设备间建立点对点的加密信道,从而将不同局域网当中的服务器连接起来,而若要为都处于内网的机器进行打洞,则通常需要有一台位于公网的服务器为他们进行中继:

现有的比较成熟的无需用户自行在公网架设服务器的异地组网服务有 tailscale、zerotier 等,但由于其在中国大陆内通常没有相应的中继服务器,从而导致在国内想要通过此类服务进行连接则往往带有一定的延迟(反正笔者是忍受不了)

如何解决这个问题?不难想到我们可以在公网 VPS 上自行搭建 tailscale 的 DERP 节点,或是自建 zerotier 的 Moon 节点,但既然都要自建节点了, 那么我们为什么不直接自建 VPN 呢 —— 诸如 IPSec、OpenVPN 等老牌 VPN 方案可谓是应有尽有

但相比于这些传统方案,笔者更愿意选择被 Linus 青睐甚至已经合并入 Linux kernel mainline 的 WireGuard —— 这是一个高性能的基于 UDP 的现代 VPN 协议,或许也是目前已知的最好的 VPN 解决方案

本篇博客主要讲述如何利用公网服务器搭建 WireGuard VPN 网络

0x01. 公网服务器基本配置

安装与配置 WireGuard

首先我们在公网服务器上安装 WireGuard,笔者这里是 Ubuntu 24.04

参考: WireGuard - Installation

1
2
3
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt-get install wireguard

完成安装之后我们会获得一个内核模块,以及相应的用户态 CLI 程序 wg

接下来使用 wg 生成一份私钥与对应的公钥,存放在 /etc/wireguard 目录下:

1
$ wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey

接下来创建 WireGuard 配置文件,这里我们放在 /etc/wireguard/wg0.conf ,写入如下内容:

1
2
3
4
5
6
[Interface]
PrivateKey = 私钥文件内容
ListenPort = 自定义的监听端口
Address = 自定义的内网地址,例如 10.0.0.1/24
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o 你的能访问公网的网卡名 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o 你的能访问公网的网卡名 -j MASQUERADE

这里的 PostUpPostDown 参数分别为接口启动时与关闭时执行的命令,其中 wg0 为我们后面要创建的虚拟网卡名,该命令主要用于流量转发,这里不再赘叙

对于可以访问公网的网卡的名称,可以简单通过如下命令进行获取:

1
$ ip -o -4 route show to default | awk '{print $5}'

接下来使用如下命令启动该接口,其会创建一个虚拟网卡 wg0

1
$ sudo wg-quick up wg0

最后,我们可以为 wg0 接口配置自启动:

1
$ sudo systemctl enable wg-quick@wg0

配置防火墙

为了让 iptables 规则正常工作,我们还需要修改防火墙以允许 IPV4 流量转发,首先编辑 /etc/sysctl.conf 文件,添加如下配置:

1
net.ipv4.ip_forward = 1

也可以在 WireGuard 配置文件中的 [Interface] 添加如下配置:

1
2
PostUp = sysctl -w net.ipv4.ip_forward=1
PostDown = sysctl -w net.ipv4.ip_forward=0

然后重新载入配置:

1
$ sudo sysctl -p

最后开启 wireguard 监听端口上的 UDP 连接:

1
$ sudo ufw allow 你的自定义端口/udp

如果你使用阿里云这样比较专业的云服务器提供商,你可能还需要在相应的控制台页面添加规则:

0x02. 客户端基本配置

Linux 客户端配置

基础配置

首先还是按惯例在本地安装 WireGuard,这里笔者的系统是 openSUSE Tumbleweed

1
$ sudo zypper in wireguard-tools

然后按惯例生成公钥与私钥:

1
$ wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey

接下来修改服务端配置文件, 添加 客户端配置如下:

每新增一个客户端就添加一份 [Peer] 配置

1
2
3
[Peer]
PublicKey = 客户端生成的公钥
AllowedIPs = 自定义的内网地址,例如 10.0.0.2/32

重启服务端 WireGuard:

1
$ sudo wg-quick down wg0 && sudo wg-quick up wg0

然后创建配置文件 /etc/wireguard/wg0.conf ,写入如下内容:

1
2
3
4
5
6
7
8
9
[Interface]
PrivateKey = 客户端生成的私钥
Address = 上面配置的客户端地址
MTU = 1280

[Peer]
PublicKey = 服务端的公钥
Endpoint = 服务端的公网地址或是域名:服务端的端口
AllowedIPs = 服务端网段,例如 10.0.0.0/24

如果你不止想要组网,还想要让 WireGuard 服务端代理客户端的全部流量,只需要配置客户端的 AllowedIPs = 0.0.0.0/0 即可

最后按惯例启动 WireGuard 即可:

1
$ sudo wg-quick up wg0

如果有需要,可以配置自启动:

1
$ sudo systemctl enable wg-quick@wg0

配置 DDNS

作为中继的公网服务器的 IP 不一定是恒定的,虽然说域名通常是可以不变的,但有的时候域名对应的 IP 可能会发生改变,而 WireGuard 不会重复进行解析,仅会使用第一次的结果,从而可能造成组网失败 :(

对于 Linux,WireGuard 官方已经提供了 一份自动重新解析 DNS 的脚本 ,我们只需要设置一个定时执行任务即可,例如使用 crontab

在 Ubuntu 下,该脚本似乎会默认在 /usr/share/doc/wireguard-tools/examples/reresolve-dns/reresolve-dns.sh 安装一份

1
1 * * * * 脚本所在目录/reresolve-dns.sh wg0 # 每小时执行一次

Windows 客户端配置

不用 Windows 👋

首先从官网下载对应架构的安装包并安装,选择新建空隧道:

其会自动生成一份私钥和公钥,我们只需要再按照前面的方式填写配置文件即可:

之后在服务端也添加相应配置并重启 WireGuard 即可

0x03. 利用 NGINX 反代 WireGuard(🕊🕊🕊)

懒得弄了,等👴什么时候有时间再看看

0x04. 可能遇到的问题

客户端能握手但无法连接其他终端,wg show 显示 allowed ips: (none)

在多设备连接到同一网络时可能出现,客户端无法连接到包括服务端在内的其他终端, 但是可能会有一个奇异现象是仅有一个客户端能正常连接

出现这种情况的原因可能是因为 IP 冲突 ,大概原因是客户端之间的 IP 相互 overlap 了,从而只有其中一个能够正常工作

最简单的解决方案是 把客户端的子网掩码 设为 /32 ,下面是一个示例配置文件:

1
2
3
4
5
6
7
8
9
10
[Interface]
PrivateKey = 客户端私钥
Address = 10.191.98.10/32
MTU = 1280

[Peer]
PublicKey = 服务端公钥
Endpoint = 服务端公网IP或域名:服务端配置的端口
AllowedIPs = 10.191.98.0/24
PersistentKeepalive = 25

对应的服务端配置:

1
2
3
4
5
6
7
8
9
10
[Interface]
Address = 10.191.98.1/24
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o 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 -t nat -D POSTROUTING -o eth0 -j MASQUERADE
ListenPort = 服务端配置的端口
PrivateKey = 服务端私钥

[Peer]
PublicKey = 客户端公钥
AllowedIPs = 10.191.98.10/32

配置 WireGuard 后无法解析域名,但可以通过 IP 进行访问

可能是 DNS 解析出现问题,在客户端配置文件当中你可能会额外配置如下:

1
2
[Interface]
DNS = 8.8.8.8

这会覆盖原有 DNS 配置,但 DNS 服务器未必是可达的( 原因懂的都懂 ),最简单的解决方案是删除 DNS 配置或是更改成其他的可用的 DNS 服务器(例如 114.114.114.114


【OPS.0x04】使用 Wireguard 进行异地组网
https://arttnba3.github.io/2024/10/23/OPS-0X04-WIREGUARD_VPN/
作者
arttnba3
发布于
2024年10月23日
许可协议