前置条件

  • 有一台能够运行内网穿透服务的公网服务器(如 阿里云ECS 等),具有独立的公网 IP ,并配置相应域名解析,作为此处的 服务端
  • 有一台能够运行在个人内网的服务器,其能够运行 Minecraft JAVA 服务端,作为此处的 客户端
    • 服务端与客户端的系统都默认为 Debian(其余Linux系统同理)
    • Minecraft 服务端推荐为 PaperMC

网络拓扑

开始部署

服务端配置

第一步,安装部署FRPS。访问 FRP Github Release 发布页,按照服务端的系统架构选择相应的压缩包下载并上传至服务器(或使用以下命令直接下载至服务器)

wget https://github.com/fatedier/frp/releases/download/v0.54.0/frp_0.54.0_linux_amd64.tar.gz

解压至所期望的安装位置,并设置软链接至如 /usr/bin 的PATH路径下。软件包内包含有:frps(服务端可执行文件)、frpc(客户端可执行文件)、一些示例配置文件等等,此处主要关注frps

tar --gzip -xvf frp_0.54.0_linux_amd64.tar.gz
sudo mv frp_0.54.0_linux_amd64 /opt
sudo ln -s /opt/frp_0.54.0_linux_amd64/frps /usr/bin/frps

输入以下命令确保链接路径正确指向FRPS可执行程序

frps -v

若无误应当得到当前所下载的FRP版本信息:0.54.0

第二步,创建FRPS配置目录以及配置文件。按照惯例,我们将配置创建至系统 /etc 目录下

sudo mkdir /etc/frp
sudo touch /etc/frp/frps.toml

作为此处的示例,在创建的frps.toml配置文件中填入以下配置内容(其中的__your_token_here__请修改为自己设置的密钥)

# 设置服务端监听连接的地址
bindAddr = "0.0.0.0"

# 设置服务端用于接收TCP连接的端口
bindPort = 5443

# 设置服务端用于接收KCP(UDP)连接的端口
kcpBindPort = 5443

# 设置服务端用于接收QUIC(UDP)连接的端口
quicBindPort = 5445

# QUIC协议的相关配置(默认)
transport.quic.keepalivePeriod = 10
transport.quic.maxIdleTimeout = 30
transport.quic.maxIncomingStreams = 100000

# 设置连接池用于减少多用户访问时的连接建立时间
transport.maxPoolCount = 5

# 设置是否强制在FRPS服务端和FRPC客户端之间的通讯启用TLS加密
transport.tls.force = false

# 设置HTTP/HTTPS代理端口
vhostHTTPPort = 80
vhostHTTPSPort = 443

# 设置客户端鉴权方法(默认为token)
auth.method = "token"

# 设置鉴权密码(token)
auth.token = "__your_token_here__"

# 设置每个客户端所能使用的最大端口数目,0代表没有限制
maxPortsPerClient = 0

# 设置服务端根域名,用于在客户端中设置subdomian参数(如,服务端subDomainHost为i2cy.tech,客户端设置subdomain为mc,则访问mc.i2cy.tech即可代理到客户端的对应服务)
subDomainHost = "i2cy.tech"

第三步(可选),将frps设置为随系统启动,并由systemd托管。创建一个名为 frps.service 的文件,并填入以下内容

[Unit]
Description=Frp Server Service
After=network.target

[Service]
Type=simple
User=root
Group=root
Restart=always
RestartSec=5s
ExecStart=/usr/bin/frps -c /etc/frp/frps.toml
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target

将文件设置为可执行,并移动至 /etc/systemd/system 目录下

chmod +x frps.service
sudo mv frps.service /etc/systemd/system

启用此服务并启动

sudo systemctl enable frps.service
sudo service frps start

查看frps运行状态,若输出日志正常,则部署成功

sudo service frps status
● frps.service - Frp Server Service
Loaded: loaded (/etc/systemd/system/frps.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2024-02-04 00:24:56 CST; 3 days ago
Main PID: 957668 (frps)
Tasks: 6 (limit: 463)
Memory: 27.1M
CPU: 1h 6min 1.031s
CGroup: /system.slice/frps.service
└─957668 /usr/bin/frps -c /etc/frp/frps.toml

客户端配置

第一步,与服务端一样,将FRP可执行文件链接到 /usr/bin 下。此处主要关注frpc(客户端)

wget https://github.com/fatedier/frp/releases/download/v0.54.0/frp_0.54.0_linux_amd64.tar.gz
tar --gzip -xvf frp_0.54.0_linux_amd64.tar.gz
sudo mv frp_0.54.0_linux_amd64 /opt
sudo ln -s /opt/frp_0.54.0_linux_amd64/frpc /usr/bin/frpc

第二步,与服务端一样,创建FRPS配置目录以及配置文件。按照惯例,我们将配置创建至系统 /etc 目录下

sudo mkdir /etc/frp
sudo touch /etc/frp/frpc.toml

编写frpc配置文件。作为此处的实例,在创建的frpc.toml配置文件中填入以下配置内容(其中的__your_token_here__请修改为自己先前设置的密钥,与frps.toml中的相同)

# 服务端的IP地址和端口,这里选择5445(QUIC/UDP)端口,QUIC协议相较于KCP和TCP更快更省流量
serverAddr = "i2cy.tech"
serverPort = 5445

# 设置传输协议,此处为QUIC
transport.protocol = "quic"

# 设置验证方式,与服务端相同
auth.method = "token"

# 设置密钥,与服务端相同
auth.token = "__your_token_here__"

# 设置连接池
transport.poolCount = 5

# QUIC协议相关设置,此处默认
transport.quic.keepalivePeriod = 10
transport.quic.maxIdleTimeout = 30
transport.quic.maxIncomingStreams = 100000

# 启用TLS
transport.tls.enable = true

# MC服务器穿透
[[proxies]]
name = "mc"
type = "tcp"
localIP = "AppServer"
localPort = 25500
remotePort = 30010
healthCheck.type = "tcp"
healthCheck.timeoutSeconds = 3
healthCheck.maxFailed = 3
healthCheck.intervalSeconds = 10
transport.proxyProtocolVersion = "v2"

# Dynmap地图插件Web页面穿透
[[proxies]]
name = "dynmap-mc"
type = "http"
# 此处指向本地Nginx服务器
localIP = "127.0.0.1"
localPort = 8080
subdomain = "mc"
hostHeaderRewrite = "mc.i2cy.tech"
requestHeaders.set.x-from-where = "frp"
transport.proxyProtocolVersion = "v2"

# -*- 若你没有SSL证书以及相关配置需求,此条注释以下的内容请不要加入配置 -*-
# Dynmap地图插件Web页面穿透,SSL
[[proxies]]
name = "dynmap-mc-ssl"
type = "https"
# 此处指向本地Nginx服务器
localIP = "127.0.0.1"
localPort = 8443
subdomain = "mc"
transport.proxyProtocolVersion = "v2"

*注意,请添加相应的子域名解析记录。按此处配置文件为例,设置的subdomain为mc,根域名为i2cy.tech,请在你的域名提供服务商控制台处添加mc.i2cy.tech到服务端IP的域名解析

第三步(可选),将frpc设置为随系统启动,并由systemd托管。创建一个名为 frpc.service 的文件,并填入以下内容

[Unit]
Description=Frp Client Service
After=network.target

[Service]
User=root
Group=root
Type=simple
DynamicUser=true
Restart=always
RestartSec=5s
ExecStart=frpc -c /etc/frp/frpc.toml
ExecReload=frpc reload -c /etc/frp/frpc.toml
LimitNOFILE=1048576
CPUSchedulingPolicy=rr
CPUSchedulingPriority=3

[Install]
WantedBy=multi-user.target
Alias=frpc.service

将文件设置为可执行,并移动至 /etc/systemd/system 目录下,随后设为启用并启动服务

chmod +x frpc.service
sudo mv frpc.service /etc/systemd/system
sudo systemctl enable frpc.service
sudo service frpc start

第四步,修改你的Minecraft服务端设置,启用PaperMC核心内置的proxy_protocol解析,修改位于Minecraft服务器运行目录(核心.jar文件所在位置)下的 config/paper-global.yml,将proxy条目内的proxy-protocol选项修改为true,片段截取如下:

proxies:
  bungee-cord:
    online-mode: true
  proxy-protocol: true
  velocity:
    enabled: false
    online-mode: false
    secret: ''

修改完成后重启MC服务器。至此,你的Minecraft服务器已经能够正确获得玩家真实IP地址了,终端输出的玩家登录内容正确显示IP如下所示

但是,到此为止Dynmap世界地图插件依然不能正确获得Web端访客的IP地址,于是继续

第五步,安装Nginx并设置反向代理。此处所用的系统是Debian系列(Ubuntu同理),可用APT包管理器安装,其它系统安装方式在此处不另作讨论

sudo apt update && sudo apt install nginx

启用Nginx系统服务并启动

sudo systemctl enable nginx
sudo service nginx start

创建Nginx配置文件并编辑,按照规范不直接编辑Nginx默认配置,而在 /etc/nginx/conf.d 下新建一配置文件,此处命名为dynmap.conf。打开并修改 /etc/nginx/conf.d/dynmap.conf,填入以下内容

server {
	# 监听端口,与frpc.toml中设置相同
	listen 8080 proxy_protocol;
	# 注意此处的server_name一定要与frp设置的相同
	# 例如你的根域名为i2cy.tech,frpc设置的subdomain为mc,则此处应写为mc.i2cy.tech
	server_name mc.i2cy.tech;
	
	# 设置访问日志储存位置
	access_log /var/log/nginx/mc.i2cy.tech.non-ssl.access.log;
	error_log /var/log/nginx/mc.i2cy.tech.non-ssl.error.log;

	# proxy_protocol相关设置
	set_real_ip_from 0.0.0.0/0;
	real_ip_header proxy_protocol;

	location / {
		proxy_set_header Host $host;
		proxy_set_header X-Forwarded-For $proxy_protocol_addr;
		proxy_set_header X-Real-IP $proxy_protocol_addr;
		# 此处定向到你的dynmap插件Web地址,默认为8123端口
		proxy_pass http://127.0.0.1:8123/;
	}
}

# -*- 若你没有SSL证书以及相关配置需求,此条注释以下的内容请不要加入配置 -*-
server {
	# 监听端口,与frpc.toml中设置相同
	listen 8443 ssl proxy_protocol;
	# 注意此处的server_name一定要与frp设置的相同
	# 例如你的根域名为i2cy.tech,frpc设置的subdomain为mc,则此处应写为mc.i2cy.tech
	server_name mc.i2cy.tech;
	
	# 设置访问日志储存位置
	access_log /var/log/nginx/mc.i2cy.tech.access.log;
	error_log /var/log/nginx/mc.i2cy.tech.error.log;

	# SSL证书设置
	ssl_certificate /etc/letsencrypt/live/mc.i2cy.tech/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/mc.i2cy.tech/privkey.pem;
	
	# SSL验证相关设置
	ssl_session_timeout 5m;					#cache durations
	ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;	#crypto
	ssl_protocols TLSv1.1 TLSv1.2;			#avaliable TLS protocols
	ssl_prefer_server_ciphers on;				#use prefered server ciphers

	# proxy_protocol相关设置
	set_real_ip_from 0.0.0.0/0;
	real_ip_header proxy_protocol;

	location / {
		proxy_set_header Host $host;
		proxy_set_header X-Forwarded-For $proxy_protocol_addr;
		proxy_set_header X-Real-IP $proxy_protocol_addr;
		# 此处定向到你的dynmap插件Web地址,默认为8123端口
		proxy_pass http://127.0.0.1:8123/;
	}
}

保存后重启Ngnix服务

service nginx restart

在自己的浏览器中访问Dynmap地址,此处为http://mc.i2cy.tech/,随后观察nginx访问日志

tail -f /var/log/nginx/mc.i2cy.tech.non-ssl.access.log

可以看到访客的IP地址被正确解析到了每行开头

对于大多数人讲,此刻已经完成所有配置了,但如果你的Nginx服务与Minecraft服务并非运行在同一服务器上,则另外需要修改dynmap插件配置文件添加对Nginx服务所在服务器内网IP地址的信任,见下一步

第六步(补充),修改dynmap插件配置文件添加对Nginx服务所在服务器内网IP地址的信任。修改位于Minecraft服务器运行目录(核心.jar文件所在位置)下的 plugins/dynmap/configuration.txt ,找到 trusted-proxies 条目并在其下方添加Ngnix服务器的内网IP地址(例如MC服务器内网IP为:10.0.0.101,Nginx服务器内网IP为:10.0.0.102,则添加一行 – “10.0.0.102”,紧跟 – “0:0:0:0:0:0:0:1” 之后,注意缩进)。修改后的配置片段截取如下

# Trusted proxies for web server - which proxy addresses are trusted to supply valid X-Forwarded-For fields
trusted-proxies:
  - "127.0.0.1"
  - "0:0:0:0:0:0:0:1"
  - "10.0.0.102"

大功告成!现在登陆你的MC服务器,用同一台电脑再打开浏览器访问你的Dynmap Web地址,若聊天功能启用(默认启用),则当你在Web左下角的聊天框输入内容并发送时,会自动附上你的游戏内名称,如下所示

游戏内玩家看到的内容也会显示玩家名称,如下所示

By i2cy

One thought on “使用FRP内网穿透 + Nginx 实现内网PaperMC服务器获取访客真实IP地址,并使Dynmap插件正确解析Web用户真实IP地址”
  1. Dynmap网页端同步显示玩家名称的原理概述大致如下:
    1. 玩家登陆后,dynmap插件从PaperMC核心底层获取玩家IP地址,并创建IP-玩家的映射关系,映射信息储存在plugins/dynmap/ids-by-ip.txt文件中
    2. 根据先前创建的映射关系,若有与该玩家IP相同的浏览器用户访问到Dynmap的Web页面,则dynmap插件自动将该浏览器与对应玩家绑定在一起,在浏览器中的操作将被视为是该玩家的操作

发表回复