新增可选 websocket 配置段,在原有 TCP listen 之外再开一个 WebSocket 监听, 供经 CDN 接入的客户端使用;两种入口共用会话、选服、转服和 maxConnections。 - core.websocket:升级握手(可选 TLS)、二进制消息与 Minecraft 字节流互转、 可信代理真实 IP(X-Forwarded-For 从右取第一个不可信地址 / CF-Connecting-IP 等) - SessionChannels.clientAddress 统一玩家地址,用于两类准入事件、Mojang 验证 IP 与 Bungee 转发 - ConnectionGate 支持升级后才加入管线;登录等待守卫放在 WebSocket 适配器之后 - 未升级连接计入 maxConnections,并受 handshakeTimeoutSeconds 约束 - 测试:真实 socket 上 TCP 与 WebSocket 并存登录与转发、分片/合并/Ping、 在线模式加密与转发 IP、不可信来源、404/文本帧、名额归还、wss 证书加载 - 文档:docs/websocket.md,并更新运行配置、插件、架构与默认配置示例 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGL9BudzWm4dUQgBsQw4Qr
7.5 KiB
WebSocket 接入
MoonBridge 可以在 TCP listen 之外再开一个 WebSocket 玩家监听。这样玩家流量可以经 CDN(如 Cloudflare)或 HTTP 反向代理转发,从而换一条线路。两种入口同时工作:原版客户端照常连 TCP 端口;装了 MoonBridge WebSocket 客户端 mod 的客户端填 ws:// 或 wss:// 地址走 WebSocket。两种连接进入同一套会话、选服、转服和插件逻辑,共用 maxConnections。
CDN 能否降低延迟或丢包,取决于玩家到 CDN 边缘、以及边缘到源站这两段线路。本仓库只验证了协议正确性,没有做延迟或容量测量;是否值得启用应在实际线路上测量。
协议约定
客户端 mod 与代理按以下约定实现,其他客户端或网关也可以照此接入:
- 标准 RFC 6455 WebSocket。客户端向配置的
path发送 HTTP/1.1GET升级请求,不需要子协议,也不协商扩展(不压缩)。 - 升级完成后,每条消息都是二进制帧,载荷是原始 Minecraft TCP 字节流的一段。这些字节与直连 TCP 时完全相同:带 VarInt 长度前缀,在线模式下还经过 Minecraft 自己的 AES/CFB8 加密。
- 消息边界没有含义。一条消息可以只含半个 Minecraft 帧,也可以含多个帧;接收方必须按字节流拼接。代理对每次写出发送一条消息,单条消息载荷上限为一个 1.7.10 帧的最大值(2 MiB + 3 字节)。
- 收到文本帧视为协议错误,代理直接关闭连接。Ping 由代理回复 Pong;关闭帧关闭连接。代理关闭会话时先发送关闭帧。
- 请求其他路径、非升级请求返回
404并关闭。连接建立后,若在handshakeTimeoutSeconds内没有完成升级,代理关闭连接;尚未升级的连接同样占用maxConnections名额。
配置
listen: "0.0.0.0:25565" # 原生 TCP,保持不变
authentication: ONLINE_BUNGEE
websocket:
listen: "127.0.0.1:8080" # WebSocket 监听;通常只对本机反向代理开放
path: "/mc" # 默认 "/"
# tls: { certificateChain: "certs/fullchain.pem", privateKey: "certs/privkey.pem" }
trustedProxies: ["127.0.0.1"] # 只有这些来源可以提供真实 IP 头
realIpHeader: "X-Forwarded-For" # 默认值;也可用 CF-Connecting-IP、X-Real-IP
handshakeTimeoutSeconds: 10 # 1–60,默认 10
| 键 | 说明 |
|---|---|
listen |
必填,host:port,端口须大于 0。OFFLINE 模式下与 TCP listen 相同,必须是字面量回环 IP,除非设置 allowOfflinePublicAccess: true。 |
path |
升级请求的路径,须以 / 开头,不含查询串和空白。 |
tls |
可选。配置后监听端直接提供 wss://;PEM 证书链与未加密的 PKCS#8 私钥,路径相对于配置文件。PKCS#1 私钥(BEGIN RSA PRIVATE KEY)可用 openssl pkcs8 -topk8 -nocrypt -in key.pem -out key.pk8.pem 转换。不配置时为明文 ws://,由 CDN 或反向代理终结 TLS。 |
trustedProxies |
IP 或 CIDR 列表。仅当 TCP 对端属于该列表时才读取真实 IP 头;为空时始终使用 TCP 对端地址。 |
realIpHeader |
真实 IP 头名称,大小写不敏感。X-Forwarded-For 从右往左取第一个不属于 trustedProxies 的地址(全部可信时取最左项);其他头必须恰好出现一次且是 IP 字面量。可信来源发送了格式错误的头时拒绝连接,不会回退。 |
handshakeTimeoutSeconds |
从建连到升级完成的期限。 |
--validate-config 同样会加载并校验 TLS 证书和 trustedProxies。启动日志会打印 WebSocket 监听的地址与路径。
玩家地址
代理按上表确定的地址就是该玩家的地址,用于 ConnectionAdmissionEvent、PlayerAdmissionEvent、Mojang hasJoined 的 ip 参数和 Bungee 旧版转发写给后端的 IP。来源不可信时使用 TCP 对端地址,即 CDN 边缘或反向代理的地址,这时按 IP 封禁会作用于整个边缘节点。经 CDN 接入时必须正确配置 trustedProxies,同时不能把源站端口暴露给 CDN 以外的来源:任何能直连源站、且来源在 trustedProxies 内的人都可以伪造真实 IP 头。
经 WebSocket 接入的连接在升级完成后才触发 ConnectionAdmissionEvent,这时已经确定了真实地址。
部署示例
Cloudflare + 本机 nginx
server {
listen 443 ssl;
server_name mc.example.com;
# ssl_certificate / ssl_certificate_key:源站证书(Cloudflare Origin CA 或公网证书)
location /mc {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_buffering off;
proxy_read_timeout 1h;
proxy_send_timeout 1h;
}
}
对应的 MoonBridge 配置:websocket.listen: "127.0.0.1:8080"、path: "/mc"、realIpHeader: "X-Forwarded-For"。trustedProxies 同时列出 127.0.0.1 和 Cloudflare 公布的 IP 段:Cloudflare 把客户端地址追加到 X-Forwarded-For,nginx 再追加 Cloudflare 边缘地址,代理从右往左跳过这两类可信地址后得到玩家 IP。nginx 本身也应只接受 Cloudflare IP 段的连接。
Cloudflare 侧:
- DNS 记录开启代理(橙色云朵),在 Network 设置中开启 WebSockets。
- 只有 Cloudflare 支持的 HTTP/HTTPS 端口能经它代理,例如 HTTPS 的 443、2053、2083、2087、2096、8443 和 HTTP 的 80、8080、8880、2052、2082、2086、2095;Minecraft 默认的 25565 不在其中。
- SSL/TLS 模式建议用 Full (strict),回源也走 TLS。
- Cloudflare 会在边缘维护或升级时断开长连接,玩家会掉线后需要重连;这是 CDN 行为,代理无法保持连接。具体套餐对 WebSocket 时长和流量的限制以 Cloudflare 当前文档和条款为准。
玩家在客户端填 wss://mc.example.com/mc。
不经 nginx,Cloudflare 直接回源
websocket.listen 绑定公网地址,并开启 tls(Cloudflare Origin CA 证书即可,Cloudflare 选 Full (strict));trustedProxies 填 Cloudflare IP 段,realIpHeader: "CF-Connecting-IP"。防火墙只放行 Cloudflare IP 段访问该端口。
直连 WebSocket(无 CDN)
开发或测试时可以把 websocket.listen 设为可直连的地址并留空 trustedProxies,客户端填 ws://host:port/path。
客户端
客户端 mod 的安装、地址格式和排错见 client-mod/README.md。服务器列表 ping 和加入服务器都会走 WebSocket;普通 host:port 地址行为与原版一致,未安装 mod 的玩家不受影响。
验证范围
自动化测试(WebSocketListenerTest、WebSocketFrameAdapterTest、ClientAddressPolicyTest)在真实 socket 上覆盖:
- TCP 与 WebSocket 玩家同时登录同一代理并转发 PLAY 帧;
- 握手跨消息拆分、多帧合并为一条消息、PLAY 帧跨消息拆分且中间夹 Ping;
- 在线模式在 WebSocket 内的加密登录,Mojang 验证 IP 与 Bungee 转发 IP 取自可信头;
- 不可信来源不能伪造地址;
- 错误路径返回 404、文本帧关闭连接;
- 未升级连接占用并归还
maxConnections名额,以及升级期限; - 由配置加载 PEM 证书后提供
wss://。
没有覆盖:真实 CDN 链路、真实客户端游戏内体验、经 WebSocket 的延迟与吞吐。