設計思路#
文明 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
,就可以發現並加入房間了!