Maple

Maple

探索文明6的最佳组網技術——WireGuard + udp2raw

設計思路#

文明 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 外面的:

network

服務端部署方法#

前提條件:具有公網 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,就可以發現並加入房間了!

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。