通过MMPROXY让任意服务端”支持“ProxyProtocol

引子

经常运行游戏服务器的你可能会面临一种困境:云端资源昂贵,而且相对有限,尤其是在 MC 的大型整合包往往常需要超过 20GB 的内存。本地资源虽然较为廉价(如:不需要用的电脑组建的 “服务器”),但通常无法获得公网 IPv4 地址。在这种情况下,我们只能通过类似 Zerotier 或 FRP 代理,或者搭建虚拟局域网来实现联机游戏。

然而,Zerotier 和虚拟局域网方案无法获取用户的真实 IP 地址,而且需要安装额外的软件,不太方便。FRP 方案支持 ProxyProtocol,但要求被代理的服务(例如游戏服务器)也支持 ProxyProtocol 才能生效。如果无法获取用户的真实 IP 地址,这也会妨碍对一个半公开服务器的日常管理。在经过大量搜索后,我终于找到了一个名叫 MMPROXY 的项目,其作用是让任何程序都能支持 ProxyProtocol,以获取四层反向代理后用户的真实 IP 地址。

项目介绍:什么是 MMPROXY?

MMPROXY 是 Cloudflare 团队开源的一个轻量级 TCP 代理,旨在解决四层代理后客户端源 IP 地址丢失的问题。ProxyProtocol 是一种解决方案,它可以在数据包内部携带客户端源 IP 地址。然而,许多服务端并未支持 ProxyProtocol,因此有了 MMPROXY。它可以部署在服务端一台机子上,流量会先经过 MMPROXY 以解析 ProxyProtocol,随后 “欺骗” 服务端。这样在服务端侧就可以看到客户端源 IP 地址,让本不支持 ProxyProtocol 的服务端间接支持 ProxyProtocol。

但是 MMPROXY 项目在 2023 年 10 月看来,已经有五年未动了,且项目也不是很稳定。不过目前 path.net (比较著名的抗 D 服务供应商) 使用 GO 语言重写了一份,已经在 github 上公布,项目名叫做 “go-mmproxy”。

实现的功能差不多,性能 / 稳定性均有提升,本文也会着重介绍使用 go-mmproxy。但是需要注意:**go-mmproxy 或 mmproxy 需要部署在被代理服务端同机上,且不支持 Windows,需要修改路由表,必须以 root 权限运行。因此建议每个项目和 mmproxy 单独运行在容器 / 虚拟机内,且不建议运行在有较高稳定性需求的服务上。**我这边是将 go-mmproxy 和不支持 ProxyProtocol 的 Forge 服务端单独运行在一个本地的虚拟机上。

构建 GO-MMPROXY

GO-MMPROXY 的 github repo 上并未公布二进制文件,需要自行编译,且在 windows 下无法编译。故第一件事是搭建 go 环境,需要注意的是 go 版本至少要为 1.21.1。

wget https://mirrors.ustc.edu.cn/golang/go1.21.3.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.3.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version

上述命令从中科大镜像下载 golang(如果架构不对,可以自行去中科大镜像源寻找对应架构,此处不再叙述)解压,随后加入 path。

go install github.com/path-network/go-mmproxy@latest

随后安装 mmproxy,如果服务端位于境内,可以在 “github.com” 前加个”ghproxy.com/” 以使用反向代理服务加速。

完成安装后,可以在当前用户目录下看到 go 文件夹,里面就有 go-mmproxy 程序。

使用 mmproxy

在使用之前,需要先对路由表做修改(将所有来自环回的流量路由回去),可以使用以下指令完成:

ip rule add from 127.0.0.1/8 iif lo table 123
ip route add local 0.0.0.0/0 dev lo table 123

ip -6 rule add from ::1/128 iif lo table 123
ip -6 route add local ::/0 dev lo table 123

随后使用 nano 指令新建”./path-prefixes.txt” 以设置白名单,输入你四层代理的 IP 地址,如:192.0.0.0/24,一行一个。随后可以使用下面的命令启用 mmproxy。

sudo ./go-mmproxy -l 0.0.0.0:25577 -4 127.0.0.1:25578 -6 [::1]:25578 --allowed-subnets ./path-prefixes.txt

它会监听 25577 端口,将 IPV4 的流量转发到本地 25578 端口,v6 流量同理,随后就能在程序上看到真实 IP 的访问。

注:如果对应服务是 UDP 服务,必须要将 UDP 服务绑定到本地地址上,否则会出大问题,详细可以查看 git 项目。

实践的几个路子

  1. FRP:可以通过开启 FRP 的 ProxyProtocol 实现 FRP 背后服务能够获取到客户源 IP 地址。
  2. Haproxy:通过打开 ProxyProtocol 选项也可以达到这个效果。个人使用的方案是在就近的服务器上搭建 WireGuard+Haproxy,Haproxy 通过 WireGuard 将流量转发到” 高性能服务器” 的 go-mmproxy 上,最后实现目标。

结语

一套使用下来,MMPROXY 可以实现既定目标。但是,MMPROXY 必须以 ROOT 权限运行,且修改了路由表。对比 MMPROXY,我们更应该优先选择直接在服务端上实现 ProxyProtocol 的方案。MMPROXY 仅仅是一个 “迫不得已的选择”。即使使用,也应仅限于某个容器 / 虚拟机内,和对应服务单独搭配使用。

如:对于 ModMC 服务器来说,有一个 ProxyProtocol Mod(目前主要支持 Fabric,也支持 1.19 的 Forge);如果是使用 paperspigot 服务端,可以在配置文件中直接开启对应的支持选项。