设计思路
文明6(Civilization VI)这款游戏的多人联机一直为人诟病。
官方的“互联网”联机模式无疑对中国玩家并不友好,因为不稳定的国际链路加上防火墙的偶尔骚扰,造成延迟奇高、频繁掉线。由于文明联机是基于 UDP 协议,于是很多玩家都想到可以用 Zerotier/easyN2N 等工具组一个虚拟局域网,直接 p2p 打洞,配合使用 WinIPBroadcast 将文明 255.255.255.255
的房间搜索广播转发至虚拟网卡,或是使用一个更高级的工具——injciv6。它通过 hook 文明的发送、接收等操作,把原本的广播直接改为单播(这样就能正确地按路由表 route 到虚拟网卡了)。但实际游玩时又暴露出了新问题:运营商对 UDP 流量采取了很不友好的 QoS 策略,经常过一段时间就发生丢包,而一旦关键的数据包丢失了,文明就会把玩家暂时踢出房间进行重新加载,显示为“玩家的数据不同步”,通常耗时近半分钟,非常影响游戏进展。(我们6人几乎每两回合都掉线至少一个人,偶尔会全掉线)
为了应对这一点,我们可以把 UDP 包通过一些狡猾的手段伪装成 TCP 包,从而得到运营商的信任,减少被丢包的发生频率。如果所有人都用 linux 机玩文明,那么选用性能更佳的 phantun 很不错;然而这不太现实,所以我们就选用支持多平台的 udp2raw。
把 udp2raw 放在每个玩家组网工具的出口,又要使每个 guest 玩家都能连接到 host 玩家,再使用 p2p 组网就不太可行啦。我们需要一个由 supernode 中继的虚拟局域网,才能保证节点间的数据是双向可达的。所以我们采用 WireGuard(基于 UDP 的通信隧道工具)作为组网方案,将 WireGuard 服务端和 udp2raw(以 server 模式)部署到一个公网上的服务器,然后将 WireGuard 客户端和 udp2raw(以 client 模式)部署到每个玩家的个人电脑,就能实现我们的需求了!
设计的拓扑图大致如下,udp2raw 是套在 WireGuard 外面的:
服务端部署方法
前提条件:具有国内公网 IP 地址。系统以 Debian 11 发行版为例。
安装软件
sudo apt update
sudo apt install wireguard
# wget GitHub 上的 udp2raw release, 装到 /usr/local/bin/ 并确保 PATH 包含它
# 测试
udp2raw --help
开启系统的端口转发
在 /etc/sysctl.conf
里 uncomment/add 以下内容:
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
执行以下指令使设置生效:
sudo sysctl -p
生成密钥对
cd /etc/wireguard
# 调整目录下文件的默认权限,即默认600
umask 077
wg genkey | tee server.key | wg pubkey > server.key.pub
这将在当前目录下创建服务端私钥 server.key
文件和公钥 server.key.pub
文件。
然后逐一生成每个客户端的密钥对:
# 修改“10”数字为n, 生成(n-1)数量的密钥对, 序号从2开始
seq 2 10 | xargs -I{} sh -c 'wg genkey | tee client{}.key | wg pubkey > client{}.key.pub'
编写 WireGuard 配置文件
sudo su
echo "
[Interface]
PrivateKey = $(cat server.key)
Address = 10.8.0.1/24
DNS = 8.8.8.8
MTU = 1280
ListenPort = 4321
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE;
PostUp = rm -f /var/log/udp2raw.log
PostUp = nohup udp2raw -s -l 0.0.0.0:54321 -r 127.0.0.1:4321 -a -k 'testpasswd' --raw-mode faketcp &> /var/log/udp2raw.log &
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp1s0 -j MASQUERADE
PostDown = killall udp2raw || true
" > wg0.conf
# Loop through each client public key file and append corresponding [Peer] entries
for client_key in client*.key.pub; do
public_key=$(cat "$client_key")
peer_number=$(echo "$client_key" | grep -o '[0-9]\+')
# Calculate AllowedIPs based on the peer number (must NOT have client1)
allowed_ip="10.8.0.$(peer_number)/32"
echo "
[Peer]
PublicKey = $public_key
AllowedIPs = $allowed_ip
" >> wg0.conf
done
注意到我们在 WireGuard 启动时连带着静默启动了 udp2raw,并将其日志输出至 /var/log/udp2raw.log
。
启动服务,配置防火墙
sudo systemctl enable wg-quick@wg0 --now
sudo ufw allow 54321/udp
客户端配置方法
首先将服务器上生成的客户端密钥对通过安全的渠道传递过来。
安装和配置 WireGuard
在官网下载 WireGuard。软件下载后正常安装,然后新建配置。(以 client3 为例,里面的 Address 需要自己改)
[Interface]
PrivateKey = ..... # client3.key
Address = 10.8.0.3/24 # your private IP
DNS = 8.8.8.8
MTU = 1280
[Peer]
PublicKey = ..... # server.key.pub
Endpoint = 127.0.0.1:3333
AllowedIPs = 10.8.0.0/24
PersistentKeepalive = 25
安装和配置 udp2raw
先从 GitHub 上下载 udp2raw_multiplatform,将可执行文件放到环境变量 PATH 的某个目录里。
Windows 系统上的 udp2raw faketcp 稍微麻烦一点,需要手动配防火墙。确保你的 Windows 原生防火墙是启用状态,然后在 administrative shell 里执行这条命令。-g
表示它不会启动任何服务,而是会输出两条你需要手动执行的命令,用于调整防火墙设置。【注意:如果后续公网 IP 变了,这一步需要重新做】
# 把 123.xxx.xx.x 改为实际的公网IP
udp2raw -c -l 0.0.0.0:3333 -r 123.xxx.xx.x:54321 -k "testpasswd" --raw-mode faketcp -g
这一步可能会提示你缺少 Windows 某个系统的网络模块,上网搜索去安装一下即可。如果执行成功,把它给你的最后两条命令逐条复制并执行。最后,在普通 shell 里执行去掉 -g
的命令,并始终不要关闭这个 shell。
# 把 123.xxx.xx.x 改为实际的公网IP
udp2raw -c -l 0.0.0.0:3333 -r 123.xxx.xx.x:54321 -k "testpasswd" --raw-mode faketcp
使用方法
保持 udp2raw 前台运行,启动 WireGuard 隧道,拿 easyN2N 等 UDP 测试工具测试一下客户端之间的连通性。
然后启动文明6,在每个 guest 电脑上用 injciv6 以客户端模式注入,地址填写 host 的虚拟局域网 IP 10.8.0.2
,就可以发现并加入房间了!