(1.3.3) Add HAProxy and 1.9-1.12 support to EaglerXBungee

This commit is contained in:
lax1dude
2024-11-16 17:17:11 -08:00
parent fa63fc3676
commit b80d5f6772
12 changed files with 120 additions and 62 deletions

View File

@ -64,7 +64,7 @@ import net.md_5.bungee.BungeeCord;
*/
public class EaglerXBungee extends Plugin {
public static final String NATIVE_BUNGEECORD_BUILD = "1.21-R0.1-SNAPSHOT:2593130:1878";
public static final String NATIVE_BUNGEECORD_BUILD = "1.21-R0.1-SNAPSHOT:4886c4b:1881";
public static final String NATIVE_WATERFALL_BUILD = "1.21-R0.1-SNAPSHOT:de8345a:579";
static {

View File

@ -76,12 +76,15 @@ public class EaglerListenerConfig extends ListenerInfo {
}
boolean allowMOTD = config.getBoolean("allow_motd", true);
boolean allowQuery = config.getBoolean("allow_query", true);
int minMCProtocol = config.getInt("min_minecraft_protocol", 47);
int maxMCProtocol = config.getInt("max_minecraft_protocol", 340);
boolean allowV3 = config.getBoolean("allow_protocol_v3", true);
boolean allowV4 = config.getBoolean("allow_protocol_v4", true);
if(!allowV3 && !allowV4) {
throw new IllegalArgumentException("Both v3 and v4 protocol are disabled!");
}
int defragSendDelay = config.getInt("protocol_v4_defrag_send_delay", 10);
boolean haproxyProtocol = config.getBoolean("use_haproxy_protocol", false);
int cacheTTL = 7200;
boolean cacheAnimation = false;
@ -157,8 +160,8 @@ public class EaglerListenerConfig extends ListenerInfo {
cacheTrending, cachePortfolios);
return new EaglerListenerConfig(hostv4, hostv6, maxPlayer, tabListType, defaultServer, forceDefaultServer,
forwardIp, forwardIpHeader, redirectLegacyClientsTo, serverIcon, serverMOTD, allowMOTD, allowQuery,
allowV3, allowV4, defragSendDelay, cacheConfig, httpServer, enableVoiceChat, ratelimitIp,
ratelimitLogin, ratelimitMOTD, ratelimitQuery);
minMCProtocol, maxMCProtocol, allowV3, allowV4, defragSendDelay, haproxyProtocol, cacheConfig,
httpServer, enableVoiceChat, ratelimitIp, ratelimitLogin, ratelimitMOTD, ratelimitQuery);
}
private final InetSocketAddress address;
@ -174,9 +177,12 @@ public class EaglerListenerConfig extends ListenerInfo {
private final List<String> serverMOTD;
private final boolean allowMOTD;
private final boolean allowQuery;
private final int minMCProtocol;
private final int maxMCProtocol;
private final boolean allowV3;
private final boolean allowV4;
private final int defragSendDelay;
private final boolean haproxyProtocol;
private final MOTDCacheConfiguration motdCacheConfig;
private final HttpWebServer webServer;
private boolean serverIconSet = false;
@ -190,10 +196,10 @@ public class EaglerListenerConfig extends ListenerInfo {
public EaglerListenerConfig(InetSocketAddress address, InetSocketAddress addressV6, int maxPlayer,
String tabListType, String defaultServer, boolean forceDefaultServer, boolean forwardIp,
String forwardIpHeader, String redirectLegacyClientsTo, String serverIcon, List<String> serverMOTD,
boolean allowMOTD, boolean allowQuery, boolean allowV3, boolean allowV4, int defragSendDelay,
MOTDCacheConfiguration motdCacheConfig, HttpWebServer webServer, boolean enableVoiceChat,
EaglerRateLimiter ratelimitIp, EaglerRateLimiter ratelimitLogin, EaglerRateLimiter ratelimitMOTD,
EaglerRateLimiter ratelimitQuery) {
boolean allowMOTD, boolean allowQuery, int minMCProtocol, int maxMCProtocol, boolean allowV3,
boolean allowV4, int defragSendDelay, boolean haproxyProtocol, MOTDCacheConfiguration motdCacheConfig,
HttpWebServer webServer, boolean enableVoiceChat, EaglerRateLimiter ratelimitIp,
EaglerRateLimiter ratelimitLogin, EaglerRateLimiter ratelimitMOTD, EaglerRateLimiter ratelimitQuery) {
super(address, String.join("\n", serverMOTD), maxPlayer, 60, Arrays.asList(defaultServer), forceDefaultServer,
Collections.emptyMap(), tabListType, false, false, 0, false, false);
this.address = address;
@ -209,9 +215,12 @@ public class EaglerListenerConfig extends ListenerInfo {
this.serverMOTD = serverMOTD;
this.allowMOTD = allowMOTD;
this.allowQuery = allowQuery;
this.minMCProtocol = minMCProtocol;
this.maxMCProtocol = maxMCProtocol;
this.allowV3 = allowV3;
this.allowV4 = allowV4;
this.defragSendDelay = defragSendDelay;
this.haproxyProtocol = haproxyProtocol;
this.motdCacheConfig = motdCacheConfig;
this.webServer = webServer;
this.enableVoiceChat = enableVoiceChat;
@ -287,6 +296,14 @@ public class EaglerListenerConfig extends ListenerInfo {
return allowQuery;
}
public int getMinMCProtocol() {
return minMCProtocol;
}
public int getMaxMCProtocol() {
return maxMCProtocol;
}
public boolean isAllowV3() {
return allowV3;
}
@ -299,6 +316,10 @@ public class EaglerListenerConfig extends ListenerInfo {
return defragSendDelay;
}
public boolean isHAProxyProtocol() {
return haproxyProtocol;
}
public HttpWebServer getWebServer() {
return webServer;
}

View File

@ -24,6 +24,7 @@ import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.WriteBufferWaterMark;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
@ -31,7 +32,6 @@ import io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensio
import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameServerExtensionHandshaker;
import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker;
import io.netty.util.AttributeKey;
import net.lax1dude.eaglercraft.v1_8.plugin.backend_rpc_protocol.EaglerBackendRPCProtocol;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.EaglerXBungeeAPIHelper;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerBungeeConfig;
@ -256,7 +256,11 @@ public class EaglerPipeline {
channel.config().setOption(ChannelOption.IP_TOS, 24);
} catch (ChannelException var3) {
}
EaglerListenerConfig listener = channel.attr(LISTENER).get();
ChannelPipeline pipeline = channel.pipeline();
if(listener.isHAProxyProtocol()) {
pipeline.addLast("HAProxyMessageDecoder", new HAProxyMessageDecoder());
}
pipeline.addLast("HttpServerCodec", new HttpServerCodec());
pipeline.addLast("HttpObjectAggregator", new HttpObjectAggregator(65535));
int compressionLevel = EaglerXBungee.getEagler().getConfig().getHttpWebsocketCompressionLevel();
@ -272,7 +276,7 @@ public class EaglerPipeline {
pipeline.addLast("HttpCompressionHandler", new WebSocketServerExtensionHandler(deflateExtensionHandshaker,
perMessageDeflateExtensionHandshaker));
}
pipeline.addLast("HttpHandshakeHandler", new HttpHandshakeHandler(channel.attr(LISTENER).get()));
pipeline.addLast("HttpHandshakeHandler", new HttpHandshakeHandler(listener));
channel.attr(CONNECTION_INSTANCE).set(new EaglerConnectionInstance(channel));
synchronized(openChannels) {
openChannels.add(channel);

View File

@ -12,6 +12,7 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
@ -52,6 +53,9 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
private static final byte[] error429Bytes = "<h3>429 Too Many Requests<br /><small>(Try again later)</small></h3>".getBytes(StandardCharsets.UTF_8);
private final EaglerListenerConfig conf;
private boolean logExceptions;
private boolean healthCheck;
private InetSocketAddress haproxyRemoteAddr;
public HttpHandshakeHandler(EaglerListenerConfig conf) {
this.conf = conf;
@ -60,6 +64,7 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
try {
if (msg instanceof HttpRequest) {
logExceptions = true;
EaglerConnectionInstance pingTracker = ctx.channel().attr(EaglerPipeline.CONNECTION_INSTANCE).get();
HttpRequest req = (HttpRequest) msg;
HttpHeaders headers = req.headers();
@ -73,11 +78,15 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
rateLimitHost = str.split(",", 2)[0];
try {
InetAddress inetAddr = InetAddress.getByName(rateLimitHost);
addr = ctx.channel().remoteAddress();
if(addr instanceof InetSocketAddress) {
addr = new InetSocketAddress(inetAddr, ((InetSocketAddress)addr).getPort());
if(haproxyRemoteAddr != null) {
addr = new InetSocketAddress(inetAddr, haproxyRemoteAddr.getPort());
}else {
addr = new InetSocketAddress(inetAddr, 0);
addr = ctx.channel().remoteAddress();
if(addr instanceof InetSocketAddress) {
addr = new InetSocketAddress(inetAddr, ((InetSocketAddress)addr).getPort());
}else {
addr = new InetSocketAddress(inetAddr, 0);
}
}
ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).set(inetAddr);
}catch(UnknownHostException ex) {
@ -90,6 +99,9 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
ctx.close();
return;
}
}else if(haproxyRemoteAddr != null) {
addr = haproxyRemoteAddr;
ctx.channel().attr(EaglerPipeline.REAL_ADDRESS).set(haproxyRemoteAddr.getAddress());
}else {
addr = ctx.channel().remoteAddress();
if(addr instanceof InetSocketAddress) {
@ -189,6 +201,19 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
.addListener(ChannelFutureListener.CLOSE);
}
}
}else if(msg instanceof HAProxyMessage) {
logExceptions = true;
HAProxyMessage proxy = (HAProxyMessage) msg;
if(proxy.sourceAddress() != null) {
if(!conf.isForwardIp()) {
try {
haproxyRemoteAddr = new InetSocketAddress(proxy.sourceAddress(), proxy.sourcePort());
}catch(IllegalArgumentException t) {
}
}
}else {
healthCheck = true;
}
}else {
ctx.close();
}
@ -199,15 +224,13 @@ public class HttpHandshakeHandler extends ChannelInboundHandlerAdapter {
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx.channel().isActive()) {
EaglerXBungee.logger().log(Level.WARNING, "[Pre][" + ctx.channel().remoteAddress() + "]: Exception Caught: " + cause.toString(), cause);
if(logExceptions && !healthCheck) {
EaglerXBungee.logger().log(Level.WARNING, "[Pre][" + ctx.channel().remoteAddress() + "]: Exception Caught: " + cause.toString(), cause);
}
ctx.close();
}
}
private static String formatAddressFor404(String str) {
return "<span style=\"font-family:monospace;font-weight:bold;background-color:#EEEEEE;padding:3px 4px;\">" + str.replace("<", "&lt;").replace(">", "&gt;") + "</span>";
}
public void channelInactive(ChannelHandlerContext ctx) {
EaglerPipeline.closeChannel(ctx.channel());
}

View File

@ -13,6 +13,7 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
@ -136,6 +137,8 @@ public abstract class HttpServerQueryHandler extends ChannelInboundHandlerAdapte
}else if(msg instanceof CloseWebSocketFrame) {
ctx.close();
}
}else if(msg instanceof HAProxyMessage) {
EaglerXBungee.logger().warning("[" + ctx.channel().remoteAddress() + "]: Ignoring HAProxyMessage because the WebSocket connection has already been established");
}else {
EaglerXBungee.logger().severe("Unexpected Packet: " + msg.getClass().getSimpleName());
}

View File

@ -30,6 +30,7 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
@ -150,6 +151,8 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
connectionClosed = true;
ctx.close();
}
}else if(msg instanceof HAProxyMessage) {
EaglerXBungee.logger().warning("[" + ctx.channel().remoteAddress() + "]: Ignoring HAProxyMessage because the WebSocket connection has already been established");
}else {
EaglerXBungee.logger().severe("Unexpected Packet: " + msg.getClass().getSimpleName());
}
@ -264,8 +267,6 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
EaglerXBungee eaglerXBungee = EaglerXBungee.getEagler();
EaglerAuthConfig authConfig = eaglerXBungee.getConfig().getAuthConfig();
final int minecraftProtocolVersion = 47;
int eaglerLegacyProtocolVersion = buffer.readUnsignedByte();
if(eaglerLegacyProtocolVersion == 1) {
@ -273,7 +274,8 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
sendErrorCode(ctx, HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE, "Please update your client to register on this server!")
.addListener(ChannelFutureListener.CLOSE);
return;
}else if(buffer.readUnsignedByte() != minecraftProtocolVersion || !conf.isAllowV3()) {
}else if(buffer.readUnsignedByte() != 47 || 47 < conf.getMinMCProtocol()
|| 47 > conf.getMaxMCProtocol() || !conf.isAllowV3()) {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
connectionClosed = true;
ByteBuf buf = ctx.alloc().buffer();
@ -294,8 +296,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
int maxServerSupported = conf.isAllowV4() ? 4 : 3;
int minAvailableProtVers = Integer.MAX_VALUE;
int maxAvailableProtVers = Integer.MIN_VALUE;
int minSupportedProtVers = Integer.MAX_VALUE;
int maxSupportedProtVers = Integer.MIN_VALUE;
int protVers = -1;
int cnt = buffer.readUnsignedShort();
for(int i = 0; i < cnt; ++i) {
@ -306,53 +307,52 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
if(j < minAvailableProtVers) {
minAvailableProtVers = j;
}
if(j >= minServerSupported && j <= maxServerSupported) {
if(j > maxSupportedProtVers) {
maxSupportedProtVers = j;
}
if(j < minSupportedProtVers) {
minSupportedProtVers = j;
}
if(j >= minServerSupported && j <= maxServerSupported && j > protVers) {
protVers = j;
}
}
int minGameVers = Integer.MAX_VALUE;
int maxGameVers = -1;
boolean has47InList = false;
int minGameVers = conf.getMinMCProtocol();
int maxGameVers = conf.getMaxMCProtocol();
int minAvailableGameVers = Integer.MAX_VALUE;
int maxAvailableGameVers = Integer.MIN_VALUE;
int gameVers = -1;
cnt = buffer.readUnsignedShort();
for(int i = 0; i < cnt; ++i) {
int j = buffer.readUnsignedShort();
if(j == minecraftProtocolVersion) {
has47InList = true;
if(j > maxAvailableGameVers) {
maxAvailableGameVers = j;
}
if(j > maxGameVers) {
maxGameVers = j;
if(j < minAvailableGameVers) {
minAvailableGameVers = j;
}
if(j < minGameVers) {
minGameVers = j;
if(j >= minGameVers && j <= maxGameVers && j > gameVers) {
gameVers = j;
}
}
if(maxAvailableProtVers == Integer.MIN_VALUE || maxGameVers == Integer.MIN_VALUE) {
if(maxAvailableProtVers == Integer.MIN_VALUE || maxAvailableGameVers == Integer.MIN_VALUE) {
throw new IOException();
}
boolean versMisMatch = false;
boolean isServerProbablyOutdated = false;
boolean isClientProbablyOutdated = false;
if(maxSupportedProtVers == Integer.MIN_VALUE) {
if(protVers == -1) {
clientProtocolVersion = maxAvailableProtVers < 3 ? 2 : 3;
versMisMatch = true;
isServerProbablyOutdated = minAvailableProtVers > maxServerSupported && maxAvailableProtVers > maxServerSupported;
isClientProbablyOutdated = minAvailableProtVers < minServerSupported && maxAvailableProtVers < minServerSupported;
}else if(!has47InList) {
clientProtocolVersion = 3;
versMisMatch = true;
isServerProbablyOutdated = minGameVers > minecraftProtocolVersion && maxGameVers > minecraftProtocolVersion;
isClientProbablyOutdated = minGameVers < minecraftProtocolVersion && maxGameVers < minecraftProtocolVersion;
}else {
clientProtocolVersion = maxSupportedProtVers;
clientProtocolVersion = protVers;
if(gameVers == -1) {
versMisMatch = true;
isServerProbablyOutdated = minAvailableGameVers > maxGameVers && maxAvailableGameVers > maxGameVers;
isClientProbablyOutdated = minAvailableGameVers < minGameVers && maxAvailableGameVers < minGameVers;
}else {
gameProtocolVersion = gameVers;
}
}
if(versMisMatch) {
@ -370,8 +370,9 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
buf.writeShort(4);
}
buf.writeShort(1);
buf.writeShort(minecraftProtocolVersion); // want game version 47
buf.writeShort(2);
buf.writeShort(minGameVers);
buf.writeShort(maxGameVers);
String str = isClientProbablyOutdated ? "Outdated Client" : (isServerProbablyOutdated ? "Outdated Server" : "Unsupported Client Version");
buf.writeByte(str.length());
@ -403,6 +404,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
boolean useSnapshotFallbackProtocol = false;
if(eaglerLegacyProtocolVersion == 1 && !authConfig.isEnableAuthentication()) {
clientProtocolVersion = 2;
gameProtocolVersion = 47;
useSnapshotFallbackProtocol = true;
clientAuth = false;
clientAuthUsername = null;
@ -434,7 +436,6 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
final boolean final_useSnapshotFallbackProtocol = useSnapshotFallbackProtocol;
Runnable continueThread = () -> {
clientLoginState = HandshakePacketTypes.STATE_CLIENT_VERSION;
gameProtocolVersion = 47;
clientBrandString = eaglerBrand;
clientVersionString = eaglerVersionString;
@ -445,7 +446,7 @@ public class HttpWebSocketHandler extends ChannelInboundHandlerAdapter {
buf.writeByte(1);
}else {
buf.writeShort(clientProtocolVersion);
buf.writeShort(minecraftProtocolVersion);
buf.writeShort(gameProtocolVersion);
}
String brandStr = eaglerXBungee.getDescription().getName();

View File

@ -4,10 +4,11 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.api.query.EaglerQuerySimpleHandler;
import net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.config.EaglerListenerConfig;
import net.md_5.bungee.api.ProxyServer;
/**
* Copyright (c) 2022-2023 lax1dude. All Rights Reserved.
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -27,20 +28,19 @@ public class VersionQueryHandler extends EaglerQuerySimpleHandler {
protected void begin(String queryType) {
JsonObject responseObj = new JsonObject();
JsonArray handshakeVersions = new JsonArray();
if(this.getListener().isAllowV3()) {
EaglerListenerConfig cfg = this.getListener();
if(cfg.isAllowV3()) {
handshakeVersions.add(2);
handshakeVersions.add(3);
}
if(this.getListener().isAllowV4()) {
if(cfg.isAllowV4()) {
handshakeVersions.add(4);
}
responseObj.add("handshakeVersions", handshakeVersions);
JsonArray protocolVersions = new JsonArray();
protocolVersions.add(47);
JsonObject protocolVersions = new JsonObject();
protocolVersions.addProperty("min", cfg.getMinMCProtocol());
protocolVersions.addProperty("max", cfg.getMaxMCProtocol());
responseObj.add("protocolVersions", protocolVersions);
JsonArray gameVersions = new JsonArray();
gameVersions.add("1.8");
responseObj.add("gameVersions", gameVersions);
JsonObject proxyInfo = new JsonObject();
proxyInfo.addProperty("brand", ProxyServer.getInstance().getName());
proxyInfo.addProperty("vers", ProxyServer.getInstance().getVersion());

View File

@ -13,9 +13,12 @@ listener_01:
- '&6An EaglercraftX server'
allow_motd: true
allow_query: true
min_minecraft_protocol: 47
max_minecraft_protocol: 340
allow_protocol_v3: true
allow_protocol_v4: true
protocol_v4_defrag_send_delay: 10
use_haproxy_protocol: false
allow_cookie_revoke_query: true
request_motd_cache:
cache_ttl: 7200

View File

@ -1,5 +1,5 @@
name: EaglercraftXBungee
main: net.lax1dude.eaglercraft.v1_8.plugin.gateway_bungeecord.EaglerXBungee
version: 1.3.2
version: 1.3.3
description: Plugin to allow EaglercraftX 1.8 players to join your network, or allow EaglercraftX 1.8 players to use your network as a proxy to join other networks
author: lax1dude