mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2025-12-19 14:59:20 +00:00
Moved some common data handling logic to the common module
This commit is contained in:
@@ -79,7 +79,7 @@ public class BungeeDataAddon implements InjectorAddon {
|
|||||||
|
|
||||||
channel.pipeline().addBefore(
|
channel.pipeline().addBefore(
|
||||||
packetHandler, "floodgate_data_handler",
|
packetHandler, "floodgate_data_handler",
|
||||||
new BungeeProxyDataHandler(config, handshakeHandler, blocker, kickMessageAttribute)
|
new BungeeProxyDataHandler(handshakeHandler, config, kickMessageAttribute, blocker)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,30 +27,22 @@ package org.geysermc.floodgate.addon.data;
|
|||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import com.google.common.collect.Queues;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
||||||
import io.netty.util.AttributeKey;
|
import io.netty.util.AttributeKey;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import net.md_5.bungee.connection.InitialHandler;
|
import net.md_5.bungee.connection.InitialHandler;
|
||||||
import net.md_5.bungee.netty.ChannelWrapper;
|
import net.md_5.bungee.netty.ChannelWrapper;
|
||||||
import net.md_5.bungee.netty.HandlerBoss;
|
import net.md_5.bungee.netty.HandlerBoss;
|
||||||
import net.md_5.bungee.protocol.DefinedPacket;
|
import net.md_5.bungee.protocol.DefinedPacket;
|
||||||
import net.md_5.bungee.protocol.PacketWrapper;
|
import net.md_5.bungee.protocol.PacketWrapper;
|
||||||
import net.md_5.bungee.protocol.packet.Handshake;
|
import net.md_5.bungee.protocol.packet.Handshake;
|
||||||
import org.geysermc.floodgate.api.handshake.HandshakeData;
|
|
||||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||||
import org.geysermc.floodgate.util.Constants;
|
|
||||||
import org.geysermc.floodgate.util.ReflectionUtils;
|
import org.geysermc.floodgate.util.ReflectionUtils;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@RequiredArgsConstructor
|
public class BungeeProxyDataHandler extends CommonDataHandler {
|
||||||
public class BungeeProxyDataHandler extends ChannelInboundHandlerAdapter {
|
|
||||||
private static final Field HANDLER;
|
private static final Field HANDLER;
|
||||||
private static final Field CHANNEL_WRAPPER;
|
private static final Field CHANNEL_WRAPPER;
|
||||||
|
|
||||||
@@ -63,100 +55,45 @@ public class BungeeProxyDataHandler extends ChannelInboundHandlerAdapter {
|
|||||||
checkNotNull(CHANNEL_WRAPPER, "ChannelWrapper field cannot be null");
|
checkNotNull(CHANNEL_WRAPPER, "ChannelWrapper field cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ProxyFloodgateConfig config;
|
public BungeeProxyDataHandler(
|
||||||
private final FloodgateHandshakeHandler handler;
|
FloodgateHandshakeHandler handshakeHandler,
|
||||||
private final PacketBlocker blocker;
|
ProxyFloodgateConfig config,
|
||||||
private final AttributeKey<String> kickMessageAttribute;
|
AttributeKey<String> kickMessageAttribute,
|
||||||
|
PacketBlocker blocker) {
|
||||||
private final Queue<Object> packetQueue = Queues.newConcurrentLinkedQueue();
|
super(handshakeHandler, config, kickMessageAttribute, blocker);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
protected void setNewIp(Channel channel, InetSocketAddress newIp) {
|
||||||
// prevent other packets from being handled while we handle the handshake packet
|
HandlerBoss handlerBoss = ctx.pipeline().get(HandlerBoss.class);
|
||||||
if (!packetQueue.isEmpty()) {
|
// InitialHandler extends PacketHandler and implements PendingConnection
|
||||||
packetQueue.add(msg);
|
InitialHandler connection = ReflectionUtils.getCastedValue(handlerBoss, HANDLER);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ChannelWrapper channelWrapper = ReflectionUtils.getCastedValue(connection, CHANNEL_WRAPPER);
|
||||||
|
channelWrapper.setRemoteAddress(newIp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setHostname(Object wrapperWithHandshake, String hostname) {
|
||||||
|
PacketWrapper wrapper = (PacketWrapper) wrapperWithHandshake;
|
||||||
|
Handshake handshake = (Handshake) wrapper.packet;
|
||||||
|
handshake.setHost(hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean channelRead(Object msg) {
|
||||||
if (msg instanceof PacketWrapper) {
|
if (msg instanceof PacketWrapper) {
|
||||||
DefinedPacket packet = ((PacketWrapper) msg).packet;
|
DefinedPacket packet = ((PacketWrapper) msg).packet;
|
||||||
|
|
||||||
// we're only interested in the Handshake packet
|
// we're only interested in the Handshake packet
|
||||||
if (packet instanceof Handshake) {
|
if (packet instanceof Handshake) {
|
||||||
blocker.enable();
|
handle(msg, ((Handshake) packet).getHost());
|
||||||
packetQueue.add(msg);
|
|
||||||
|
|
||||||
handleHandshake(ctx, (Handshake) packet).thenRun(() -> {
|
// otherwise, it'll get read twice. once by the packet queue and once by this method
|
||||||
Object queuedPacket;
|
return false;
|
||||||
while ((queuedPacket = packetQueue.poll()) != null) {
|
|
||||||
ctx.fireChannelRead(queuedPacket);
|
|
||||||
}
|
|
||||||
ctx.pipeline().remove(this);
|
|
||||||
blocker.disable();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.fireChannelRead(msg);
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<Void> handleHandshake(ChannelHandlerContext ctx, Handshake packet) {
|
|
||||||
String data = packet.getHost();
|
|
||||||
|
|
||||||
return handler.handle(ctx.channel(), data).thenAccept(result -> {
|
|
||||||
HandshakeData handshakeData = result.getHandshakeData();
|
|
||||||
|
|
||||||
// we'll change the IP address from the proxy to the real IP of the client very early on
|
|
||||||
// so that almost every plugin will use the real IP of the client
|
|
||||||
InetSocketAddress newIp = result.getNewIp(ctx.channel());
|
|
||||||
if (newIp != null) {
|
|
||||||
HandlerBoss handlerBoss = ctx.pipeline().get(HandlerBoss.class);
|
|
||||||
// InitialHandler extends PacketHandler and implements PendingConnection
|
|
||||||
InitialHandler connection = ReflectionUtils.getCastedValue(handlerBoss, HANDLER);
|
|
||||||
|
|
||||||
ChannelWrapper channelWrapper =
|
|
||||||
ReflectionUtils.getCastedValue(connection, CHANNEL_WRAPPER);
|
|
||||||
|
|
||||||
channelWrapper.setRemoteAddress(newIp);
|
|
||||||
}
|
|
||||||
|
|
||||||
packet.setHost(handshakeData.getHostname());
|
|
||||||
|
|
||||||
if (handshakeData.getDisconnectReason() != null) {
|
|
||||||
ctx.channel().attr(kickMessageAttribute).set(handshakeData.getDisconnectReason());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (result.getResultType()) {
|
|
||||||
case EXCEPTION:
|
|
||||||
ctx.channel().attr(kickMessageAttribute)
|
|
||||||
.set(Constants.INTERNAL_ERROR_MESSAGE);
|
|
||||||
break;
|
|
||||||
case DECRYPT_ERROR:
|
|
||||||
ctx.channel().attr(kickMessageAttribute)
|
|
||||||
.set(config.getDisconnect().getInvalidKey());
|
|
||||||
break;
|
|
||||||
case INVALID_DATA_LENGTH:
|
|
||||||
ctx.channel().attr(kickMessageAttribute)
|
|
||||||
.set(config.getDisconnect().getInvalidArgumentsLength());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}).handle((v, error) -> {
|
|
||||||
if (error != null) {
|
|
||||||
error.printStackTrace();
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
||||||
super.exceptionCaught(ctx, cause);
|
|
||||||
if (config.isDebug()) {
|
|
||||||
cause.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Floodgate
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.floodgate.addon.data;
|
||||||
|
|
||||||
|
import com.google.common.collect.Queues;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import it.unimi.dsi.fastutil.Pair;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Queue;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.geysermc.floodgate.api.handshake.HandshakeData;
|
||||||
|
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||||
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||||
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
||||||
|
import org.geysermc.floodgate.util.Constants;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class CommonDataHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
protected final FloodgateHandshakeHandler handshakeHandler;
|
||||||
|
protected final FloodgateConfig config;
|
||||||
|
protected final AttributeKey<String> kickMessageAttribute;
|
||||||
|
protected final PacketBlocker blocker;
|
||||||
|
|
||||||
|
protected final Queue<Object> packetQueue = Queues.newConcurrentLinkedQueue();
|
||||||
|
protected ChannelHandlerContext ctx;
|
||||||
|
|
||||||
|
protected abstract void setNewIp(Channel channel, InetSocketAddress newIp);
|
||||||
|
|
||||||
|
protected abstract void setHostname(Object handshakePacket, String hostname);
|
||||||
|
|
||||||
|
protected abstract boolean channelRead(Object packet) throws Exception;
|
||||||
|
|
||||||
|
protected boolean shouldRemoveHandler(HandshakeResult result) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean shouldCallFireRead(Object queuedPacket) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handle(Object handshakePacket, String hostname) {
|
||||||
|
Pair<String, String> separated = handshakeHandler.separateHostname(hostname);
|
||||||
|
|
||||||
|
if (separated.left() == null) {
|
||||||
|
// not a Floodgate player
|
||||||
|
removeSelf();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocker.enable();
|
||||||
|
packetQueue.add(handshakePacket);
|
||||||
|
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
|
||||||
|
handshakeHandler
|
||||||
|
.handle(channel, separated.left(), separated.right())
|
||||||
|
.thenApply(result -> {
|
||||||
|
HandshakeData handshakeData = result.getHandshakeData();
|
||||||
|
|
||||||
|
// we'll change the IP address to the real IP of the client very early on
|
||||||
|
// so that almost every plugin will use the real IP of the client
|
||||||
|
InetSocketAddress newIp = result.getNewIp(channel);
|
||||||
|
if (newIp != null) {
|
||||||
|
setNewIp(channel, newIp);
|
||||||
|
}
|
||||||
|
|
||||||
|
setHostname(handshakePacket, handshakeData.getHostname());
|
||||||
|
|
||||||
|
if (handshakeData.shouldDisconnect()) {
|
||||||
|
setKickMessage(handshakeData.getDisconnectReason());
|
||||||
|
return shouldRemoveHandler(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (result.getResultType()) {
|
||||||
|
case EXCEPTION:
|
||||||
|
setKickMessage(Constants.INTERNAL_ERROR_MESSAGE);
|
||||||
|
break;
|
||||||
|
case DECRYPT_ERROR:
|
||||||
|
setKickMessage(config.getDisconnect().getInvalidKey());
|
||||||
|
break;
|
||||||
|
case INVALID_DATA_LENGTH:
|
||||||
|
setKickMessage(config.getDisconnect().getInvalidArgumentsLength());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return shouldRemoveHandler(result);
|
||||||
|
}).handle((shouldRemove, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
error.printStackTrace();
|
||||||
|
}
|
||||||
|
disablePacketQueue(shouldRemove);
|
||||||
|
return shouldRemove;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void disablePacketQueue(boolean removeSelf) {
|
||||||
|
Object queuedPacket;
|
||||||
|
while ((queuedPacket = packetQueue.poll()) != null) {
|
||||||
|
if (shouldCallFireRead(queuedPacket)) {
|
||||||
|
ctx.fireChannelRead(queuedPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removeSelf) {
|
||||||
|
removeSelf();
|
||||||
|
}
|
||||||
|
blocker.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void removeSelf() {
|
||||||
|
ctx.pipeline().remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void setKickMessage(String message) {
|
||||||
|
ctx.channel().attr(kickMessageAttribute).set(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final String getKickMessage() {
|
||||||
|
return ctx.channel().attr(kickMessageAttribute).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
super.handlerAdded(ctx);
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object packet) {
|
||||||
|
// prevent other packets from being handled while we handle the handshake packet
|
||||||
|
if (!packetQueue.isEmpty()) {
|
||||||
|
packetQueue.add(packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (channelRead(packet)) {
|
||||||
|
ctx.fireChannelRead(packet);
|
||||||
|
}
|
||||||
|
} catch (Exception exception) {
|
||||||
|
exception.printStackTrace();
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
|
super.exceptionCaught(ctx, cause);
|
||||||
|
if (config.isDebug()) {
|
||||||
|
cause.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,10 @@ public class PacketBlocker extends ChannelInboundHandlerAdapter {
|
|||||||
ctx.pipeline().remove(this);
|
ctx.pipeline().remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean enabled() {
|
||||||
|
return blockPackets;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||||
if (blockPackets || !packetQueue.isEmpty()) {
|
if (blockPackets || !packetQueue.isEmpty()) {
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ import static org.geysermc.floodgate.player.FloodgateHandshakeHandler.ResultType
|
|||||||
import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH;
|
import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.util.AttributeKey;
|
import io.netty.util.AttributeKey;
|
||||||
import it.unimi.dsi.fastutil.Pair;
|
import it.unimi.dsi.fastutil.Pair;
|
||||||
@@ -40,12 +38,10 @@ import java.net.InetSocketAddress;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.geysermc.floodgate.addon.data.HandshakeDataImpl;
|
import org.geysermc.floodgate.addon.data.HandshakeDataImpl;
|
||||||
import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl;
|
import org.geysermc.floodgate.addon.data.HandshakeHandlersImpl;
|
||||||
import org.geysermc.floodgate.api.SimpleFloodgateApi;
|
import org.geysermc.floodgate.api.SimpleFloodgateApi;
|
||||||
@@ -56,15 +52,11 @@ import org.geysermc.floodgate.api.player.PropertyKey;
|
|||||||
import org.geysermc.floodgate.config.FloodgateConfigHolder;
|
import org.geysermc.floodgate.config.FloodgateConfigHolder;
|
||||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||||
import org.geysermc.floodgate.skin.SkinUploadManager;
|
import org.geysermc.floodgate.skin.SkinUploadManager;
|
||||||
import org.geysermc.floodgate.time.TimeSyncer;
|
|
||||||
import org.geysermc.floodgate.util.BedrockData;
|
import org.geysermc.floodgate.util.BedrockData;
|
||||||
import org.geysermc.floodgate.util.Constants;
|
|
||||||
import org.geysermc.floodgate.util.InvalidFormatException;
|
import org.geysermc.floodgate.util.InvalidFormatException;
|
||||||
import org.geysermc.floodgate.util.LinkedPlayer;
|
import org.geysermc.floodgate.util.LinkedPlayer;
|
||||||
import org.geysermc.floodgate.util.TimeSyncerHolder;
|
|
||||||
import org.geysermc.floodgate.util.Utils;
|
import org.geysermc.floodgate.util.Utils;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public final class FloodgateHandshakeHandler {
|
public final class FloodgateHandshakeHandler {
|
||||||
private final HandshakeHandlersImpl handshakeHandlers;
|
private final HandshakeHandlersImpl handshakeHandlers;
|
||||||
private final SimpleFloodgateApi api;
|
private final SimpleFloodgateApi api;
|
||||||
@@ -74,36 +66,79 @@ public final class FloodgateHandshakeHandler {
|
|||||||
private final AttributeKey<FloodgatePlayer> playerAttribute;
|
private final AttributeKey<FloodgatePlayer> playerAttribute;
|
||||||
private final FloodgateLogger logger;
|
private final FloodgateLogger logger;
|
||||||
|
|
||||||
public CompletableFuture<HandshakeResult> handle(
|
public FloodgateHandshakeHandler(
|
||||||
@NonNull Channel channel,
|
HandshakeHandlersImpl handshakeHandlers,
|
||||||
@NonNull String originalHostname) {
|
SimpleFloodgateApi api,
|
||||||
|
FloodgateCipher cipher,
|
||||||
|
FloodgateConfigHolder configHolder,
|
||||||
|
SkinUploadManager skinUploadManager,
|
||||||
|
AttributeKey<FloodgatePlayer> playerAttribute,
|
||||||
|
FloodgateLogger logger) {
|
||||||
|
|
||||||
String[] split = originalHostname.split("\0");
|
this.handshakeHandlers = handshakeHandlers;
|
||||||
String data = null;
|
this.api = api;
|
||||||
|
this.cipher = cipher;
|
||||||
|
this.configHolder = configHolder;
|
||||||
|
this.skinUploadManager = skinUploadManager;
|
||||||
|
this.playerAttribute = playerAttribute;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separates the Floodgate data from the hostname
|
||||||
|
*
|
||||||
|
* @param hostname the string to look in
|
||||||
|
* @return The first string is the Floodgate data (or null if there isn't) and the second string
|
||||||
|
* is the hostname without the Floodgate.
|
||||||
|
*/
|
||||||
|
public Pair<String, String> separateHostname(@NonNull String hostname) {
|
||||||
|
String[] hostnameItems = hostname.split("\0");
|
||||||
|
String floodgateData = null;
|
||||||
|
|
||||||
StringBuilder hostnameBuilder = new StringBuilder();
|
StringBuilder hostnameBuilder = new StringBuilder();
|
||||||
for (String value : split) {
|
for (String value : hostnameItems) {
|
||||||
if (data == null && FloodgateCipher.hasHeader(value)) {
|
if (floodgateData == null && FloodgateCipher.hasHeader(value)) {
|
||||||
data = value;
|
floodgateData = value;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
hostnameBuilder.append(value).append('\0');
|
hostnameBuilder.append(value).append('\0');
|
||||||
}
|
}
|
||||||
// hostname now doesn't have Floodgate data anymore if it had
|
// hostname now doesn't have Floodgate data anymore if it had
|
||||||
String hostname = hostnameBuilder.toString();
|
return Pair.of(floodgateData, hostnameBuilder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
if (data == null) {
|
public CompletableFuture<HandshakeResult> handle(
|
||||||
return CompletableFuture.completedFuture(
|
@NonNull Channel channel,
|
||||||
callHandlerAndReturnResult(NOT_FLOODGATE_DATA, channel, null, hostname)
|
@NonNull String floodgateDataString,
|
||||||
);
|
@NonNull String hostname) {
|
||||||
}
|
|
||||||
|
|
||||||
byte[] floodgateData = data.getBytes(Charsets.UTF_8);
|
byte[] floodgateData = floodgateDataString.getBytes(Charsets.UTF_8);
|
||||||
|
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
|
||||||
|
String decrypted;
|
||||||
|
try {
|
||||||
|
// the actual decryption of the data
|
||||||
|
decrypted = cipher.decryptToString(floodgateData);
|
||||||
|
} catch (InvalidFormatException e) {
|
||||||
|
// when the Floodgate format couldn't be found
|
||||||
|
throw callHandlerAndReturnResult(
|
||||||
|
NOT_FLOODGATE_DATA,
|
||||||
|
channel, null, hostname
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// all the other exceptions are caused by invalid/tempered Floodgate data
|
||||||
|
if (configHolder.get().isDebug()) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw callHandlerAndReturnResult(
|
||||||
|
ResultType.DECRYPT_ERROR,
|
||||||
|
channel, null, hostname
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// actual decryption
|
|
||||||
String decrypted = cipher.decryptToString(floodgateData);
|
|
||||||
BedrockData bedrockData = BedrockData.fromString(decrypted);
|
BedrockData bedrockData = BedrockData.fromString(decrypted);
|
||||||
|
|
||||||
if (bedrockData.getDataLength() != EXPECTED_LENGTH) {
|
if (bedrockData.getDataLength() != EXPECTED_LENGTH) {
|
||||||
@@ -122,22 +157,6 @@ public final class FloodgateHandshakeHandler {
|
|||||||
// let's check if there is a link
|
// let's check if there is a link
|
||||||
return bedrockData;
|
return bedrockData;
|
||||||
|
|
||||||
} catch (InvalidFormatException formatException) {
|
|
||||||
// only header exceptions should return 'not floodgate data',
|
|
||||||
// all the other format exceptions are because of invalid/tempered Floodgate data
|
|
||||||
if (formatException.isHeader()) {
|
|
||||||
throw callHandlerAndReturnResult(
|
|
||||||
NOT_FLOODGATE_DATA,
|
|
||||||
channel, null, hostname
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
formatException.printStackTrace();
|
|
||||||
|
|
||||||
throw callHandlerAndReturnResult(
|
|
||||||
ResultType.DECRYPT_ERROR,
|
|
||||||
channel, null, hostname
|
|
||||||
);
|
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
if (exception instanceof HandshakeResult) {
|
if (exception instanceof HandshakeResult) {
|
||||||
throw (HandshakeResult) exception;
|
throw (HandshakeResult) exception;
|
||||||
@@ -174,7 +193,12 @@ public final class FloodgateHandshakeHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private HandshakeResult handlePart2(Channel channel, String hostname, BedrockData bedrockData, LinkedPlayer linkedPlayer) {
|
private HandshakeResult handlePart2(
|
||||||
|
Channel channel,
|
||||||
|
String hostname,
|
||||||
|
BedrockData bedrockData,
|
||||||
|
LinkedPlayer linkedPlayer) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HandshakeData handshakeData = new HandshakeDataImpl(
|
HandshakeData handshakeData = new HandshakeDataImpl(
|
||||||
channel, true, bedrockData.clone(), configHolder.get(),
|
channel, true, bedrockData.clone(), configHolder.get(),
|
||||||
@@ -252,7 +276,8 @@ public final class FloodgateHandshakeHandler {
|
|||||||
.thenApply(link -> new ObjectObjectImmutablePair<>(data, link))
|
.thenApply(link -> new ObjectObjectImmutablePair<>(data, link))
|
||||||
.handle((result, error) -> {
|
.handle((result, error) -> {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
logger.error("The player linking implementation returned an error", error.getCause());
|
logger.error("The player linking implementation returned an error",
|
||||||
|
error.getCause());
|
||||||
return new ObjectObjectImmutablePair<>(data, null);
|
return new ObjectObjectImmutablePair<>(data, null);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -42,26 +42,24 @@ public final class SpigotDataAddon implements InjectorAddon {
|
|||||||
@Inject private SimpleFloodgateApi api;
|
@Inject private SimpleFloodgateApi api;
|
||||||
@Inject private FloodgateLogger logger;
|
@Inject private FloodgateLogger logger;
|
||||||
|
|
||||||
@Inject
|
|
||||||
@Named("packetDecoder")
|
|
||||||
private String packetDecoder;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Named("packetHandler")
|
@Named("packetHandler")
|
||||||
private String packetHandlerName;
|
private String packetHandlerName;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Named("kickMessageAttribute")
|
||||||
|
private AttributeKey<String> kickMessageAttribute;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Named("playerAttribute")
|
@Named("playerAttribute")
|
||||||
private AttributeKey<FloodgatePlayer> playerAttribute;
|
private AttributeKey<FloodgatePlayer> playerAttribute;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInject(Channel channel, boolean toServer) {
|
public void onInject(Channel channel, boolean toServer) {
|
||||||
PacketBlocker blocker = new PacketBlocker();
|
// we have to add the packet blocker in the data handler, otherwise ProtocolSupport breaks
|
||||||
channel.pipeline().addBefore(packetDecoder, "floodgate_packet_blocker", blocker);
|
|
||||||
|
|
||||||
channel.pipeline().addBefore(
|
channel.pipeline().addBefore(
|
||||||
packetHandlerName, "floodgate_data_handler",
|
packetHandlerName, "floodgate_data_handler",
|
||||||
new SpigotDataHandler(config, handshakeHandler, blocker, logger)
|
new SpigotDataHandler(handshakeHandler, config, kickMessageAttribute)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,131 +28,109 @@ package org.geysermc.floodgate.addon.data;
|
|||||||
import static org.geysermc.floodgate.util.ReflectionUtils.getCastedValue;
|
import static org.geysermc.floodgate.util.ReflectionUtils.getCastedValue;
|
||||||
import static org.geysermc.floodgate.util.ReflectionUtils.setValue;
|
import static org.geysermc.floodgate.util.ReflectionUtils.setValue;
|
||||||
|
|
||||||
import com.google.common.collect.Queues;
|
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
import io.netty.util.AttributeKey;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Queue;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.geysermc.floodgate.api.handshake.HandshakeData;
|
|
||||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
|
||||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||||
import org.geysermc.floodgate.util.BedrockData;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
||||||
import org.geysermc.floodgate.util.ClassNames;
|
import org.geysermc.floodgate.util.ClassNames;
|
||||||
import org.geysermc.floodgate.util.Constants;
|
|
||||||
import org.geysermc.floodgate.util.ProxyUtils;
|
import org.geysermc.floodgate.util.ProxyUtils;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
public final class SpigotDataHandler extends CommonDataHandler {
|
||||||
public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
|
|
||||||
private final FloodgateConfig config;
|
|
||||||
private final FloodgateHandshakeHandler handshakeHandler;
|
|
||||||
private final PacketBlocker blocker;
|
|
||||||
private final FloodgateLogger logger;
|
|
||||||
|
|
||||||
private final Queue<Object> packetQueue = Queues.newConcurrentLinkedQueue();
|
|
||||||
|
|
||||||
private Object networkManager;
|
private Object networkManager;
|
||||||
private FloodgatePlayer player;
|
private FloodgatePlayer player;
|
||||||
|
|
||||||
@Override
|
public SpigotDataHandler(
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception {
|
FloodgateHandshakeHandler handshakeHandler,
|
||||||
// prevent other packets from being handled while we handle the handshake packet
|
FloodgateConfig config,
|
||||||
if (!packetQueue.isEmpty()) {
|
AttributeKey<String> kickMessageAttribute) {
|
||||||
packetQueue.add(packet);
|
super(handshakeHandler, config, kickMessageAttribute, new PacketBlocker());
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ClassNames.HANDSHAKE_PACKET.isInstance(packet)) {
|
|
||||||
blocker.enable();
|
|
||||||
packetQueue.add(packet);
|
|
||||||
|
|
||||||
networkManager = ctx.channel().pipeline().get("packet_handler");
|
|
||||||
String handshakeValue = getCastedValue(packet, ClassNames.HANDSHAKE_HOST);
|
|
||||||
|
|
||||||
handshakeHandler.handle(ctx.channel(), handshakeValue).thenApply(result -> {
|
|
||||||
HandshakeData handshakeData = result.getHandshakeData();
|
|
||||||
|
|
||||||
setValue(packet, ClassNames.HANDSHAKE_HOST, handshakeData.getHostname());
|
|
||||||
|
|
||||||
InetSocketAddress newIp = result.getNewIp(ctx.channel());
|
|
||||||
if (newIp != null) {
|
|
||||||
setValue(networkManager, ClassNames.SOCKET_ADDRESS, newIp);
|
|
||||||
//todo the socket address will be overridden when bungeeData is true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handshakeData.getDisconnectReason() != null) {
|
|
||||||
ctx.close(); //todo disconnect with message
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo use kickMessageAttribute and let this be common logic
|
|
||||||
|
|
||||||
switch (result.getResultType()) {
|
|
||||||
case SUCCESS:
|
|
||||||
break;
|
|
||||||
case EXCEPTION:
|
|
||||||
logger.info(Constants.INTERNAL_ERROR_MESSAGE);
|
|
||||||
ctx.close();
|
|
||||||
return true;
|
|
||||||
case DECRYPT_ERROR:
|
|
||||||
logger.info(config.getDisconnect().getInvalidKey());
|
|
||||||
ctx.close();
|
|
||||||
return true;
|
|
||||||
case INVALID_DATA_LENGTH:
|
|
||||||
int dataLength = result.getBedrockData().getDataLength();
|
|
||||||
logger.info(
|
|
||||||
config.getDisconnect().getInvalidArgumentsLength(),
|
|
||||||
BedrockData.EXPECTED_LENGTH, dataLength
|
|
||||||
);
|
|
||||||
ctx.close();
|
|
||||||
return true;
|
|
||||||
default: // only continue when SUCCESS
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
player = result.getFloodgatePlayer();
|
|
||||||
boolean bungeeData = ProxyUtils.isProxyData();
|
|
||||||
|
|
||||||
if (!bungeeData) {
|
|
||||||
// Use a spoofedUUID for initUUID (just like Bungeecord)
|
|
||||||
setValue(networkManager, "spoofedUUID", player.getCorrectUniqueId());
|
|
||||||
}
|
|
||||||
return bungeeData || player == null;
|
|
||||||
}).thenAccept(shouldRemove -> {
|
|
||||||
Object queuedPacket;
|
|
||||||
while ((queuedPacket = packetQueue.poll()) != null) {
|
|
||||||
try {
|
|
||||||
if (checkLogin(ctx, packet)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
ctx.fireChannelRead(queuedPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldRemove) {
|
|
||||||
ctx.pipeline().remove(SpigotDataHandler.this);
|
|
||||||
}
|
|
||||||
blocker.disable();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkLogin(ctx, packet)) {
|
|
||||||
ctx.fireChannelRead(packet);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkLogin(ChannelHandlerContext ctx, Object packet) throws Exception {
|
@Override
|
||||||
|
protected void setNewIp(Channel channel, InetSocketAddress newIp) {
|
||||||
|
//todo the socket address will be overridden when bungeeData is true
|
||||||
|
setValue(networkManager, ClassNames.SOCKET_ADDRESS, newIp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setHostname(Object handshakePacket, String hostname) {
|
||||||
|
setValue(handshakePacket, ClassNames.HANDSHAKE_HOST, hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldRemoveHandler(HandshakeResult result) {
|
||||||
|
player = result.getFloodgatePlayer();
|
||||||
|
|
||||||
|
if (getKickMessage() != null) {
|
||||||
|
// we also have to keep this handler if we want to kick then with a disconnect message
|
||||||
|
return false;
|
||||||
|
} else if (player == null) {
|
||||||
|
// player is not a Floodgate player
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the server will do all the work if BungeeCord mode is enabled,
|
||||||
|
// otherwise we have to help the server.
|
||||||
|
boolean bungeeData = ProxyUtils.isProxyData();
|
||||||
|
|
||||||
|
if (!bungeeData) {
|
||||||
|
// Use a spoofedUUID for initUUID (just like Bungeecord)
|
||||||
|
setValue(networkManager, "spoofedUUID", player.getCorrectUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return bungeeData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldCallFireRead(Object queuedPacket) {
|
||||||
|
// we have to ignore the 'login start' packet if BungeeCord mode is disabled,
|
||||||
|
// otherwise the server might ask the user to login
|
||||||
|
try {
|
||||||
|
if (checkAndHandleLogin(queuedPacket)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception exception) {
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean channelRead(Object packet) throws Exception {
|
||||||
|
if (ClassNames.HANDSHAKE_PACKET.isInstance(packet)) {
|
||||||
|
// ProtocolSupport would break if we added this during the creation of this handler
|
||||||
|
ctx.pipeline().addAfter("splitter", "floodgate_packet_blocker", blocker);
|
||||||
|
|
||||||
|
networkManager = ctx.channel().pipeline().get("packet_handler");
|
||||||
|
|
||||||
|
handle(packet, getCastedValue(packet, ClassNames.HANDSHAKE_HOST));
|
||||||
|
// otherwise, it'll get read twice. once by the packet queue and once by this method
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !checkAndHandleLogin(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkAndHandleLogin(Object packet) throws Exception {
|
||||||
if (ClassNames.LOGIN_START_PACKET.isInstance(packet)) {
|
if (ClassNames.LOGIN_START_PACKET.isInstance(packet)) {
|
||||||
// we have to fake the offline player (login) cycle
|
Object packetListener = ClassNames.PACKET_LISTENER.get(networkManager);
|
||||||
Object loginListener = ClassNames.PACKET_LISTENER.get(networkManager);
|
|
||||||
|
String kickMessage = getKickMessage();
|
||||||
|
if (kickMessage != null) {
|
||||||
|
disconnect(packetListener, kickMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// check if the server is actually in the Login state
|
// check if the server is actually in the Login state
|
||||||
if (!ClassNames.LOGIN_LISTENER.isInstance(loginListener)) {
|
if (!ClassNames.LOGIN_LISTENER.isInstance(packetListener)) {
|
||||||
// player is not in the login state, abort
|
// player is not in the login state, abort
|
||||||
|
|
||||||
|
// I would've liked to close the channel for security reasons, but our big friend
|
||||||
|
// ProtocolSupport, who likes to break things, doesn't work otherwise
|
||||||
ctx.pipeline().remove(this);
|
ctx.pipeline().remove(this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -161,8 +139,9 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
|
|||||||
GameProfile gameProfile = new GameProfile(
|
GameProfile gameProfile = new GameProfile(
|
||||||
player.getCorrectUniqueId(), player.getCorrectUsername()
|
player.getCorrectUniqueId(), player.getCorrectUsername()
|
||||||
);
|
);
|
||||||
setValue(loginListener, ClassNames.LOGIN_PROFILE, gameProfile);
|
setValue(packetListener, ClassNames.LOGIN_PROFILE, gameProfile);
|
||||||
|
|
||||||
|
// we have to fake the offline player (login) cycle
|
||||||
// just like on Spigot:
|
// just like on Spigot:
|
||||||
|
|
||||||
// LoginListener#initUUID
|
// LoginListener#initUUID
|
||||||
@@ -170,10 +149,10 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
|
|||||||
|
|
||||||
// and the tick of LoginListener will do the rest
|
// and the tick of LoginListener will do the rest
|
||||||
|
|
||||||
ClassNames.INIT_UUID.invoke(loginListener);
|
ClassNames.INIT_UUID.invoke(packetListener);
|
||||||
|
|
||||||
Object loginHandler =
|
Object loginHandler =
|
||||||
ClassNames.LOGIN_HANDLER_CONSTRUCTOR.newInstance(loginListener);
|
ClassNames.LOGIN_HANDLER_CONSTRUCTOR.newInstance(packetListener);
|
||||||
ClassNames.FIRE_LOGIN_EVENTS.invoke(loginHandler);
|
ClassNames.FIRE_LOGIN_EVENTS.invoke(loginHandler);
|
||||||
|
|
||||||
ctx.pipeline().remove(this);
|
ctx.pipeline().remove(this);
|
||||||
@@ -182,11 +161,16 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void disconnect(Object packetListener, String kickMessage) throws Exception {
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
// both versions close the channel for us
|
||||||
super.exceptionCaught(ctx, cause);
|
if (ClassNames.LOGIN_LISTENER.isInstance(packetListener)) {
|
||||||
if (config.isDebug()) {
|
ClassNames.LOGIN_DISCONNECT.invoke(packetListener, kickMessage);
|
||||||
cause.printStackTrace();
|
} else {
|
||||||
|
// ProtocolSupport for example has their own PacketLoginInListener implementation
|
||||||
|
ClassNames.NETWORK_EXCEPTION_CAUGHT.invoke(
|
||||||
|
networkManager,
|
||||||
|
ctx, new IllegalStateException(kickMessage)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import static org.geysermc.floodgate.util.ReflectionUtils.makeAccessible;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -65,6 +66,8 @@ public class ClassNames {
|
|||||||
public static final Method IS_WHITELISTED;
|
public static final Method IS_WHITELISTED;
|
||||||
public static final Method ADD_WHITELIST_ENTRY;
|
public static final Method ADD_WHITELIST_ENTRY;
|
||||||
public static final Method REMOVE_WHITELIST_ENTRY;
|
public static final Method REMOVE_WHITELIST_ENTRY;
|
||||||
|
public static final Method LOGIN_DISCONNECT;
|
||||||
|
public static final Method NETWORK_EXCEPTION_CAUGHT;
|
||||||
public static final Method INIT_UUID;
|
public static final Method INIT_UUID;
|
||||||
public static final Method FIRE_LOGIN_EVENTS;
|
public static final Method FIRE_LOGIN_EVENTS;
|
||||||
|
|
||||||
@@ -166,6 +169,15 @@ public class ClassNames {
|
|||||||
LOGIN_PROFILE = getFieldOfType(LOGIN_LISTENER, GameProfile.class);
|
LOGIN_PROFILE = getFieldOfType(LOGIN_LISTENER, GameProfile.class);
|
||||||
checkNotNull(LOGIN_PROFILE, "Profile from LoginListener");
|
checkNotNull(LOGIN_PROFILE, "Profile from LoginListener");
|
||||||
|
|
||||||
|
LOGIN_DISCONNECT = getMethod(LOGIN_LISTENER, "disconnect", String.class);
|
||||||
|
checkNotNull(LOGIN_DISCONNECT, "LoginListener's disconnect method");
|
||||||
|
|
||||||
|
NETWORK_EXCEPTION_CAUGHT = getMethod(
|
||||||
|
networkManager,
|
||||||
|
"exceptionCaught",
|
||||||
|
ChannelHandlerContext.class, Throwable.class
|
||||||
|
);
|
||||||
|
|
||||||
// there are multiple no-arg void methods
|
// there are multiple no-arg void methods
|
||||||
INIT_UUID = getMethod(LOGIN_LISTENER, "initUUID");
|
INIT_UUID = getMethod(LOGIN_LISTENER, "initUUID");
|
||||||
checkNotNull(INIT_UUID, "initUUID from LoginListener");
|
checkNotNull(INIT_UUID, "initUUID from LoginListener");
|
||||||
|
|||||||
@@ -31,24 +31,18 @@ import static org.geysermc.floodgate.util.ReflectionUtils.getField;
|
|||||||
import static org.geysermc.floodgate.util.ReflectionUtils.getPrefixedClass;
|
import static org.geysermc.floodgate.util.ReflectionUtils.getPrefixedClass;
|
||||||
import static org.geysermc.floodgate.util.ReflectionUtils.setValue;
|
import static org.geysermc.floodgate.util.ReflectionUtils.setValue;
|
||||||
|
|
||||||
import com.google.common.collect.Queues;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
|
||||||
import io.netty.util.AttributeKey;
|
import io.netty.util.AttributeKey;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.geysermc.floodgate.api.handshake.HandshakeData;
|
|
||||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||||
import org.geysermc.floodgate.util.Constants;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
||||||
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.ResultType;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
public final class VelocityProxyDataHandler extends CommonDataHandler {
|
||||||
public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter {
|
|
||||||
private static final Field HANDSHAKE;
|
private static final Field HANDSHAKE;
|
||||||
private static final Class<?> HANDSHAKE_PACKET;
|
private static final Class<?> HANDSHAKE_PACKET;
|
||||||
private static final Field HANDSHAKE_SERVER_ADDRESS;
|
private static final Field HANDSHAKE_SERVER_ADDRESS;
|
||||||
@@ -71,97 +65,47 @@ public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter
|
|||||||
REMOTE_ADDRESS = getField(minecraftConnection, "remoteAddress");
|
REMOTE_ADDRESS = getField(minecraftConnection, "remoteAddress");
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ProxyFloodgateConfig config;
|
|
||||||
private final FloodgateHandshakeHandler handshakeHandler;
|
|
||||||
private final PacketBlocker blocker;
|
|
||||||
private final AttributeKey<String> kickMessageAttribute;
|
|
||||||
private final FloodgateLogger logger;
|
private final FloodgateLogger logger;
|
||||||
|
|
||||||
private final Queue<Object> packetQueue = Queues.newConcurrentLinkedQueue();
|
public VelocityProxyDataHandler(
|
||||||
|
FloodgateConfig config,
|
||||||
@Override
|
FloodgateHandshakeHandler handshakeHandler,
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
PacketBlocker blocker,
|
||||||
// prevent other packets from being handled while we handle the handshake packet
|
AttributeKey<String> kickMessageAttribute,
|
||||||
if (!packetQueue.isEmpty()) {
|
FloodgateLogger logger) {
|
||||||
packetQueue.add(msg);
|
super(handshakeHandler, config, kickMessageAttribute, blocker);
|
||||||
return;
|
this.logger = logger;
|
||||||
}
|
|
||||||
|
|
||||||
// we're only interested in the Handshake packet.
|
|
||||||
// it should be the first packet but you never know
|
|
||||||
if (HANDSHAKE_PACKET.isInstance(msg)) {
|
|
||||||
blocker.enable();
|
|
||||||
packetQueue.add(msg);
|
|
||||||
|
|
||||||
handleClientToProxy(ctx, msg).thenRun(() -> {
|
|
||||||
Object packet;
|
|
||||||
while ((packet = packetQueue.poll()) != null) {
|
|
||||||
ctx.fireChannelRead(packet);
|
|
||||||
}
|
|
||||||
ctx.pipeline().remove(this);
|
|
||||||
blocker.disable();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fireChannelRead(msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompletableFuture<Void> handleClientToProxy(ChannelHandlerContext ctx, Object packet) {
|
@Override
|
||||||
String address = getCastedValue(packet, HANDSHAKE_SERVER_ADDRESS);
|
protected void setNewIp(Channel channel, InetSocketAddress newIp) {
|
||||||
|
setValue(channel.pipeline().get("handler"), REMOTE_ADDRESS, newIp);
|
||||||
|
}
|
||||||
|
|
||||||
return handshakeHandler.handle(ctx.channel(), address).thenAccept(result -> {
|
@Override
|
||||||
HandshakeData handshakeData = result.getHandshakeData();
|
protected void setHostname(Object handshakePacket, String hostname) {
|
||||||
|
setValue(handshakePacket, HANDSHAKE_SERVER_ADDRESS, hostname);
|
||||||
InetSocketAddress newIp = result.getNewIp(ctx.channel());
|
}
|
||||||
if (newIp != null) {
|
|
||||||
Object connection = ctx.pipeline().get("handler");
|
|
||||||
setValue(connection, REMOTE_ADDRESS, newIp);
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(packet, HANDSHAKE_SERVER_ADDRESS, handshakeData.getHostname());
|
|
||||||
|
|
||||||
if (handshakeData.getDisconnectReason() != null) {
|
|
||||||
ctx.channel().attr(kickMessageAttribute).set(handshakeData.getDisconnectReason());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (result.getResultType()) {
|
|
||||||
case SUCCESS:
|
|
||||||
break;
|
|
||||||
case EXCEPTION:
|
|
||||||
ctx.channel().attr(kickMessageAttribute)
|
|
||||||
.set(Constants.INTERNAL_ERROR_MESSAGE);
|
|
||||||
return;
|
|
||||||
case DECRYPT_ERROR:
|
|
||||||
ctx.channel().attr(kickMessageAttribute)
|
|
||||||
.set(config.getDisconnect().getInvalidKey());
|
|
||||||
return;
|
|
||||||
case INVALID_DATA_LENGTH:
|
|
||||||
ctx.channel().attr(kickMessageAttribute)
|
|
||||||
.set(config.getDisconnect().getInvalidArgumentsLength());
|
|
||||||
return;
|
|
||||||
default: // only continue when SUCCESS
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldRemoveHandler(HandshakeResult result) {
|
||||||
|
if (result.getResultType() == ResultType.SUCCESS) {
|
||||||
FloodgatePlayer player = result.getFloodgatePlayer();
|
FloodgatePlayer player = result.getFloodgatePlayer();
|
||||||
|
|
||||||
logger.info("Floodgate player who is logged in as {} {} joined",
|
logger.info("Floodgate player who is logged in as {} {} joined",
|
||||||
player.getCorrectUsername(), player.getCorrectUniqueId());
|
player.getCorrectUsername(), player.getCorrectUniqueId());
|
||||||
}).handle((v, error) -> {
|
}
|
||||||
if (error != null) {
|
return true;
|
||||||
error.printStackTrace();
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
public boolean channelRead(Object packet) {
|
||||||
super.exceptionCaught(ctx, cause);
|
// we're only interested in the Handshake packet.
|
||||||
if (config.isDebug()) {
|
// it should be the first packet but you never know
|
||||||
cause.printStackTrace();
|
if (HANDSHAKE_PACKET.isInstance(packet)) {
|
||||||
|
handle(packet, getCastedValue(packet, HANDSHAKE_SERVER_ADDRESS));
|
||||||
|
// otherwise, it'll get read twice. once by the packet queue and once by this method
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user