diff --git a/bukkit/src/main/java/org/geysermc/floodgate/PacketHandler.java b/bukkit/src/main/java/org/geysermc/floodgate/PacketHandler.java index e0f0b4fa..48503617 100644 --- a/bukkit/src/main/java/org/geysermc/floodgate/PacketHandler.java +++ b/bukkit/src/main/java/org/geysermc/floodgate/PacketHandler.java @@ -2,7 +2,7 @@ package org.geysermc.floodgate; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToMessageDecoder; +import io.netty.channel.SimpleChannelInboundHandler; import lombok.RequiredArgsConstructor; import org.geysermc.floodgate.HandshakeHandler.HandshakeResult; import org.geysermc.floodgate.injector.BukkitInjector; @@ -14,13 +14,12 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.List; import java.util.UUID; import static org.geysermc.floodgate.util.ReflectionUtil.*; @RequiredArgsConstructor -public class PacketHandler extends MessageToMessageDecoder { +public class PacketHandler extends SimpleChannelInboundHandler { private static BukkitPlugin plugin = BukkitPlugin.getInstance(); private static HandshakeHandler handshakeHandler; @@ -52,7 +51,7 @@ public class PacketHandler extends MessageToMessageDecoder { private boolean bungee; @Override - protected void decode(ChannelHandlerContext ctx, Object packet, List out) throws Exception { + protected void channelRead0(ChannelHandlerContext ctx, Object packet) throws Exception { boolean isHandhake = handshakePacketClass.isInstance(packet); boolean isLogin = loginStartPacketClass.isInstance(packet); @@ -94,7 +93,6 @@ public class PacketHandler extends MessageToMessageDecoder { setValue(networkManager, getFieldOfType(networkManagerClass, SocketAddress.class, false), newAddress); } plugin.getLogger().info("Added " + fPlayer.getJavaUsername() + " " + fPlayer.getJavaUniqueId()); - out.add(packet); } else if (isLogin) { if (!bungee) { // we have to fake the offline player cycle @@ -109,9 +107,12 @@ public class PacketHandler extends MessageToMessageDecoder { setValue(loginListener, protocolStateField, readyToAcceptState); // LoginLister#protocolState = READY_TO_ACCEPT // The tick of LoginListener will do the rest } - // out.add(packet); don't let this packet through as we want to skip the login cycle } } finally { + // don't let the packet through if the packet is the login packet + // because we want to skip the login cycle + if (!isLogin) ctx.fireChannelRead(packet); + if (isHandhake && bungee || isLogin && !bungee || fPlayer == null) { // remove the injection of the client because we're finished BukkitInjector.removeInjectedClient(future, ctx.channel()); diff --git a/bungee/pom.xml b/bungee/pom.xml index 243e4e2a..6546d15d 100644 --- a/bungee/pom.xml +++ b/bungee/pom.xml @@ -25,6 +25,12 @@ ${bungee-version} provided + + net.md-5 + bungeecord-protocol + 1.15-SNAPSHOT + provided + org.geysermc floodgate-common diff --git a/bungee/src/main/java/org/geysermc/floodgate/BungeeDebugger.java b/bungee/src/main/java/org/geysermc/floodgate/BungeeDebugger.java new file mode 100644 index 00000000..b0e9faad --- /dev/null +++ b/bungee/src/main/java/org/geysermc/floodgate/BungeeDebugger.java @@ -0,0 +1,108 @@ +package org.geysermc.floodgate; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.*; +import io.netty.channel.socket.ServerSocketChannel; +import io.netty.handler.codec.MessageToByteEncoder; +import lombok.RequiredArgsConstructor; +import net.md_5.bungee.protocol.MinecraftEncoder; +import net.md_5.bungee.protocol.Varint21LengthFieldPrepender; +import org.geysermc.floodgate.util.ReflectionUtil; + +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +public class BungeeDebugger { + public BungeeDebugger() { + init(); + } + + private void init() { + Class pipelineUtils = ReflectionUtil.getPrefixedClass("netty.PipelineUtils"); + Field framePrepender = ReflectionUtil.getField(pipelineUtils, "framePrepender"); + ReflectionUtil.setFinalValue(null, framePrepender, new CustomVarint21LengthFieldPrepender()); + } + + @ChannelHandler.Sharable + private static class CustomVarint21LengthFieldPrepender extends Varint21LengthFieldPrepender { + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + // we're getting called before the encoder and decoder are added, + // so we have to wait with a nice while loop :D + ctx.executor().execute(() -> { + System.out.println("Channel: " + ctx.channel().isActive() + " " + ctx.channel().isOpen() + " " + ctx.channel().isRegistered()); + while (ctx.channel().isOpen()) { + System.out.println("Trying to find decoder for " + getHostString(ctx, true) + " " + getParentName(ctx, true)); + if (ctx.channel().pipeline().get(MinecraftEncoder.class) != null) { + System.out.println("Found decoder for " + getHostString(ctx, true)); + ctx.channel().pipeline().addLast( + "floodgate-debug-init", + new BungeeChannelInitializer( + ctx.channel().parent() instanceof ServerSocketChannel + ) + ); + break; + } + } + }); + } + } + + public static String getHostString(ChannelHandlerContext ctx, boolean alwaysString) { + SocketAddress address = ctx.channel().remoteAddress(); + return address != null ? ((InetSocketAddress) address).getHostString() : (alwaysString ? "null" : null); + } + + public static String getParentName(ChannelHandlerContext ctx, boolean alwaysString) { + Channel parent = ctx.channel().parent(); + return parent != null ? parent.getClass().getSimpleName() : (alwaysString ? "null" : null); + } + + public static int executorsCount(ChannelHandlerContext ctx) { + return ((MultithreadEventLoopGroup) ctx.channel().eventLoop().parent()).executorCount(); + } + + @RequiredArgsConstructor + private static class BungeeChannelInitializer extends ChannelInitializer { + private final boolean player; + + @Override + protected void initChannel(Channel channel) throws Exception { + System.out.println("Init " + (player ? "Bungee" : "Server")); + // can't add our debugger when the inbound-boss is missing + if (channel.pipeline().get("packet-encoder") == null) return; + if (channel.pipeline().get("packet-decoder") == null) return; + channel.pipeline() + .addBefore("packet-decoder", "floodgate-debug-in", new BungeeInPacketHandler(player)) + .addBefore("packet-encoder", "floodgate-debug-out", new BungeeOutPacketHandler(player)); + } + } + + @RequiredArgsConstructor + private static class BungeeInPacketHandler extends SimpleChannelInboundHandler { + private final boolean player; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { + int index = msg.readerIndex(); + System.out.println((player ? "Player -> Bungee" : "Server -> Bungee") + ":\n" + ByteBufUtil.prettyHexDump(msg)); + msg.readerIndex(index); + ctx.fireChannelRead(msg.retain()); + } + } + + @RequiredArgsConstructor + private static class BungeeOutPacketHandler extends MessageToByteEncoder { + private final boolean player; + + @Override + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { + int index = msg.readerIndex(); + System.out.println((player ? "Bungee -> Player" : "Bungee -> Server") + ":\n" + ByteBufUtil.prettyHexDump(msg)); + msg.readerIndex(index); + out.writeBytes(msg); + } + } +} diff --git a/bungee/src/main/java/org/geysermc/floodgate/BungeePlugin.java b/bungee/src/main/java/org/geysermc/floodgate/BungeePlugin.java index 3e412500..b15f2bbe 100644 --- a/bungee/src/main/java/org/geysermc/floodgate/BungeePlugin.java +++ b/bungee/src/main/java/org/geysermc/floodgate/BungeePlugin.java @@ -25,6 +25,7 @@ public class BungeePlugin extends Plugin implements Listener { private static Field extraHandshakeData; @Getter private BungeeFloodgateConfig config; + private BungeeDebugger debugger; private HandshakeHandler handshakeHandler; @Override @@ -40,6 +41,16 @@ public class BungeePlugin extends Plugin implements Listener { @Override public void onEnable() { getProxy().getPluginManager().registerListener(this, this); + if (config.isDebug()) { + debugger = new BungeeDebugger(); + } + } + + @Override + public void onDisable() { + if (config.isDebug()) { + getLogger().warning("Please note that it is not possible to reload this plugin when debug mode is enabled. At least for now"); + } } @EventHandler(priority = EventPriority.LOW) @@ -93,15 +104,15 @@ public class BungeePlugin extends Plugin implements Listener { SocketAddress remoteAddress = ReflectionUtil.getCastedValue(channelWrapper, "remoteAddress", SocketAddress.class); if (!(remoteAddress instanceof InetSocketAddress)) { getLogger().info( - "Player " + player.getUsername() + " doesn't use a InetSocketAddress. " + + "Player " + player.getUsername() + " doesn't use an InetSocketAddress. " + "It uses " + remoteAddress.getClass().getSimpleName() + ". Ignoring the player, I guess." ); - return; + } else { + ReflectionUtil.setValue( + channelWrapper, "remoteAddress", + new InetSocketAddress(result.getBedrockData().getIp(), ((InetSocketAddress) remoteAddress).getPort()) + ); } - ReflectionUtil.setValue( - channelWrapper, "remoteAddress", - new InetSocketAddress(result.getBedrockData().getIp(), ((InetSocketAddress) remoteAddress).getPort()) - ); event.completeIntent(this); }); } @@ -117,7 +128,8 @@ public class BungeePlugin extends Plugin implements Listener { } static { - Class initial_handler = ReflectionUtil.getClass("net.md_5.bungee.connection.InitialHandler"); + ReflectionUtil.setPrefix("net.md_5.bungee"); + Class initial_handler = ReflectionUtil.getPrefixedClass("connection.InitialHandler"); extraHandshakeData = ReflectionUtil.getField(initial_handler, "extraDataInHandshake"); } } diff --git a/common/src/main/java/org/geysermc/floodgate/FloodgateConfig.java b/common/src/main/java/org/geysermc/floodgate/FloodgateConfig.java index 00d67995..09b8af3f 100644 --- a/common/src/main/java/org/geysermc/floodgate/FloodgateConfig.java +++ b/common/src/main/java/org/geysermc/floodgate/FloodgateConfig.java @@ -26,6 +26,9 @@ public class FloodgateConfig { @JsonProperty(value = "disconnect") private DisconnectMessages messages; + @JsonProperty + private boolean debug; + @JsonIgnore PrivateKey privateKey = null; diff --git a/common/src/main/java/org/geysermc/floodgate/HandshakeHandler.java b/common/src/main/java/org/geysermc/floodgate/HandshakeHandler.java index f1e13661..2cb71ebe 100644 --- a/common/src/main/java/org/geysermc/floodgate/HandshakeHandler.java +++ b/common/src/main/java/org/geysermc/floodgate/HandshakeHandler.java @@ -1,9 +1,6 @@ package org.geysermc.floodgate; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NonNull; +import lombok.*; import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; @@ -53,7 +50,7 @@ public class HandshakeHandler { } @AllArgsConstructor(access = AccessLevel.PROTECTED) - @Getter + @Getter @ToString public static class HandshakeResult { private ResultType resultType; private String[] handshakeData; diff --git a/common/src/main/java/org/geysermc/floodgate/util/ReflectionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/ReflectionUtil.java index 2fe36927..4f13270f 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/ReflectionUtil.java +++ b/common/src/main/java/org/geysermc/floodgate/util/ReflectionUtil.java @@ -3,10 +3,7 @@ package org.geysermc.floodgate.util; import lombok.Getter; import lombok.Setter; -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.lang.reflect.*; public class ReflectionUtil { /** @@ -94,6 +91,39 @@ public class ReflectionUtil { } } + public static boolean setFinalValue(Object instance, Field field, Object value) { + try { + makeAccessible(field); + + Field modifiersField = null; + int modifiers = field.getModifiers(); + if (Modifier.isFinal(modifiers)) { + try { + modifiersField = Field.class.getDeclaredField("modifiers"); + } catch (NoSuchFieldException e) { + // Java 12 compatibility, thanks to https://github.com/powermock/powermock/pull/1010 + Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + makeAccessible(getDeclaredFields0); + Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false); + for (Field classField : fields) { + if ("modifiers".equals(classField.getName())) { + modifiersField = classField; + break; + } + } + } + assert modifiersField != null; + makeAccessible(modifiersField); + modifiersField.setInt(field, modifiers & ~Modifier.FINAL); + } + setValue(instance, field, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + public static Method getMethod(Class clazz, String method, Class... args) { try { return clazz.getMethod(method, args);