diff --git a/src/main/java/gg/playit/minecraft/PlayitTcpTunnel.java b/src/main/java/gg/playit/minecraft/PlayitTcpTunnel.java index 0c8adcd..7330ee3 100644 --- a/src/main/java/gg/playit/minecraft/PlayitTcpTunnel.java +++ b/src/main/java/gg/playit/minecraft/PlayitTcpTunnel.java @@ -6,10 +6,11 @@ import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.util.AttributeKey; import org.bukkit.Server; import java.net.InetSocketAddress; +import java.util.Map; import java.util.logging.Logger; public class PlayitTcpTunnel { @@ -180,6 +181,11 @@ private boolean addChannelToMinecraftServer() { ReflectionHelper reflect = new ReflectionHelper(); log.info("Reflect: " + reflect); + if (!reflect.setRemoteAddress(tunnelChannel, trueIp)) { + log.warning("failed to set remote address to " + trueIp); + return false; + } + Object minecraftServer = reflect.getMinecraftServer(server); if (minecraftServer == null) { log.info("failed to get Minecraft server from Bukkit.getServer()"); @@ -192,97 +198,45 @@ private boolean addChannelToMinecraftServer() { return false; } - Object legacyPingHandler = reflect.newLegacyPingHandler(serverConnection); - if (legacyPingHandler == null) { - log.info("legacyPingHandler is null"); - return false; - } - - Object packetSplitter = reflect.newPacketSplitter(); - if (packetSplitter == null) { - log.info("packetSplitter is null"); + ServerChannel serverChannel = reflect.findServerChannel(serverConnection); + if (serverChannel == null) { + log.info("serverChannel is null"); return false; } - Object packetDecoder = reflect.newServerBoundPacketDecoder(); - if (packetDecoder == null) { - log.info("packetDecoder is null"); + ChannelHandler serverHandler = reflect.findServerHandler(serverChannel); + if (serverHandler == null) { + log.info("serverHandler is null"); return false; } - Object packetPrepender = reflect.newPacketPrepender(); - if (packetPrepender == null) { - log.info("packetPrepender is null"); + ChannelHandler handler = reflect.findChildHandler(serverHandler); + if (handler == null) { + log.info("handler is null"); return false; } - Object packetEncoder = reflect.newClientBoundPacketEncoder(); - if (packetEncoder == null) { - log.info("packetEncoder is null"); - return false; - } - - Integer rateLimitNullable = reflect.getRateLimitFromMCServer(minecraftServer); - if (rateLimitNullable == null) { - rateLimitNullable = 0; - } - - int rateLimit = rateLimitNullable; - - Object networkManager; - if (rateLimit > 0) { - networkManager = reflect.newNetworkManagerServer(rateLimit); - } else { - networkManager = reflect.newServerNetworkManager(); - } - - if (networkManager == null) { - log.info("networkManager is null"); - return false; - } - - Object handshakeListener = reflect.newHandshakeListener(minecraftServer, networkManager); - if (handshakeListener == null) { - log.info("handshakeListener is null"); - return false; - } - - if (!reflect.networkManagerSetListener(networkManager, handshakeListener)) { - log.info("failed to set handshake listener on network manager"); - return false; + Map.Entry, Object>[] options = reflect.findChildOptions(serverHandler); + if (options != null) { + for (Map.Entry, Object> option : options) { + tunnelChannel.config().setOption(option.getKey(), option.getValue()); + } } - if (!reflect.setRemoteAddress(tunnelChannel, trueIp)) { - log.warning("failed to set remote address to " + trueIp); + Map.Entry, Object>[] attrs = reflect.findChildAttrs(serverHandler); + if (attrs != null) { + for (Map.Entry, Object> attr : attrs) { + tunnelChannel.attr(attr.getKey()).set(attr.getValue()); + } } - var channel = tunnelChannel.pipeline().removeLast(); - tunnelChannel.pipeline() - .addLast("timeout", new ReadTimeoutHandler(connectionTimeoutSeconds)) - .addLast("legacy_query", (ChannelHandler) legacyPingHandler) - .addLast("splitter", (ChannelHandler) packetSplitter) - .addLast("decoder", (ChannelHandler) packetDecoder) - .addLast("prepender", (ChannelHandler) packetPrepender) - .addLast("encoder", (ChannelHandler) packetEncoder) - .addLast("packet_handler", (ChannelHandler) networkManager); - - if (!reflect.addToServerConnections(serverConnection, networkManager)) { - log.info("failed to add to server connections"); - - tunnelChannel.pipeline().remove("timeout"); - tunnelChannel.pipeline().remove("legacy_query"); - tunnelChannel.pipeline().remove("splitter"); - tunnelChannel.pipeline().remove("decoder"); - tunnelChannel.pipeline().remove("prepender"); - tunnelChannel.pipeline().remove("encoder"); - tunnelChannel.pipeline().remove("packet_handler"); - - tunnelChannel.pipeline().addLast(channel); + ChannelPipeline pipeline = tunnelChannel.pipeline(); - return false; - } + pipeline.remove(this); + pipeline.addLast(handler); - tunnelChannel.pipeline().fireChannelActive(); + pipeline.fireChannelRegistered(); + pipeline.fireChannelActive(); return true; } } diff --git a/src/main/java/gg/playit/minecraft/ReflectionHelper.java b/src/main/java/gg/playit/minecraft/ReflectionHelper.java index 1e81801..2f3a837 100644 --- a/src/main/java/gg/playit/minecraft/ReflectionHelper.java +++ b/src/main/java/gg/playit/minecraft/ReflectionHelper.java @@ -1,19 +1,25 @@ package gg.playit.minecraft; -import io.netty.channel.AbstractChannel; -import io.netty.channel.Channel; +import io.netty.channel.*; +import io.netty.util.AttributeKey; +import org.bukkit.Bukkit; import org.bukkit.Server; import java.lang.reflect.*; import java.net.SocketAddress; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.logging.Logger; public class ReflectionHelper { static Logger log = Logger.getLogger(ReflectionHelper.class.getName()); + private final Class ServerBootstrapAcceptor; + private final Field SBA_childHandler; + private final Field SBA_childOptions; + private final Field SBA_childAttrs; + private final Class ServerConnection; private final Class LegacyPingHandler; private final Class MinecraftServer; @@ -31,6 +37,19 @@ public class ReflectionHelper { private final Class CraftServer; public ReflectionHelper() { + ServerBootstrapAcceptor = (Class) cls( + "io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor" + ); + try { + SBA_childHandler = searchForFieldByName(ServerBootstrapAcceptor, "childHandler"); + SBA_childHandler.setAccessible(true); + SBA_childOptions = searchForFieldByName(ServerBootstrapAcceptor, "childOptions"); + SBA_childOptions.setAccessible(true); + SBA_childAttrs = searchForFieldByName(ServerBootstrapAcceptor, "childAttrs"); + SBA_childAttrs.setAccessible(true); + } catch (Throwable throwable) { + throw new IllegalStateException("failed to get fields", throwable); + } ServerConnection = cls("net.minecraft.server.network.ServerConnection"); LegacyPingHandler = cls("net.minecraft.server.network.LegacyPingHandler"); MinecraftServer = cls("net.minecraft.server.MinecraftServer"); @@ -67,113 +86,6 @@ static Class cls(String... classNames) { return null; } - public boolean networkManagerSetListener(Object networkManager, Object listener) { - if (NetworkManager == null || PacketListener == null) { - return false; - } - - try { - Method method = searchMethod(NetworkManager, "setListener", PacketListener); - method.setAccessible(true); - method.invoke(networkManager, listener); - return true; - } catch (Exception e) { - log.warning("failed to call setListener: " + e); - } - - try { - var field = searchForFieldByName(NetworkManager, "packetListener"); - field.setAccessible(true); - field.set(networkManager, listener); - return true; - } catch (Exception e) { - log.warning("failed to set packetListener" + e); - } - - var options = searchForFieldByType(NetworkManager, PacketListener); - if (options.size() == 1) { - try { - options.get(0).setAccessible(true); - options.get(0).set(networkManager, listener); - return true; - } catch (Exception e) { - log.warning("failed to set packetListener directly to type" + options.get(0) + ", error: " + e); - } - } else { - log.warning("got multiple options for packet listener field: " + options); - } - - return false; - } - - public boolean addToServerConnections(Object serverConnection, Object networkManager) { - if (ServerConnection == null || NetworkManager == null) { - return false; - } - - try { - Field field = searchForFieldByName(ServerConnection, "connections"); - field.setAccessible(true); - - var list = (List) field.get(serverConnection); - list.add(networkManager); - - return true; - } catch (Exception e) { - log.warning("failed set field connections, error: " + e); - } - - HashSet potentialFieldObjects = new HashSet<>(); - - var search = ServerConnection; - while (search != null) { - for (var field : ServerConnection.getDeclaredFields()) { - if (List.class.isAssignableFrom(field.getType())) { - if (field.getGenericType() instanceof ParameterizedType parameterizedType) { - var type = parameterizedType.getActualTypeArguments()[0]; - var typeClass = cls(type.getTypeName()); - - if (typeClass != null && NetworkManager.isAssignableFrom(typeClass)) { - try { - field.setAccessible(true); - potentialFieldObjects.add(field.get(serverConnection)); - } catch (Exception ignore) { - } - } - } - } - } - search = search.getSuperclass(); - } - - if (potentialFieldObjects.size() == 1) { - var found = potentialFieldObjects.toArray()[0]; - try { - var list = (List) found; - list.add(networkManager); - return true; - } catch (Exception e) { - log.warning("failed to add connection to " + found + ", error: " + e); - } - } else { - log.warning("multiple connection lists: " + potentialFieldObjects); - } - - return false; - } - - public Object newHandshakeListener(Object minecraftServer, Object networkManager) { - if (HandshakeListener == null) { - return null; - } - - try { - return HandshakeListener.getConstructor(MinecraftServer, NetworkManager).newInstance(minecraftServer, networkManager); - } catch (Exception e) { - return null; - } - } - public boolean setRemoteAddress(Channel channel, SocketAddress address) { try { Field field = AbstractChannel.class.getDeclaredField("remoteAddress"); @@ -186,124 +98,51 @@ public boolean setRemoteAddress(Channel channel, SocketAddress address) { } } - public Integer getRateLimitFromMCServer(Object server) { - if (MinecraftServer == null) { - return null; - } - - try { - return (Integer) searchMethod(MinecraftServer, "getRateLimitPacketsPerSecond").invoke(server); - } catch (Exception e) { - return null; - } - } - - public Object newLegacyPingHandler(Object serverConnection) { - if (LegacyPingHandler == null || ServerConnection == null) { - return null; - } - - try { - return LegacyPingHandler.getConstructor(ServerConnection).newInstance(serverConnection); - } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | - IllegalAccessException e) { - return null; - } - } - - public Object newPacketSplitter() { - if (PacketSplitter == null) { - return null; - } - - try { - return PacketSplitter.getConstructor().newInstance(); - } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | - IllegalAccessException e) { - return null; - } - } - - public Object newServerBoundPacketDecoder() { - if (PacketDecoder == null) { - return null; - } - - try { - return PacketDecoder.getConstructor(EnumProtocolDirection).newInstance(serverBound()); - } catch (Exception e) { - return null; - } - } - - public Object newClientBoundPacketEncoder() { - if (PacketEncoder == null) { - return null; - } - - try { - return PacketEncoder.getConstructor(EnumProtocolDirection).newInstance(clientBound()); - } catch (Exception e) { - return null; - } - } - - public Object newPacketPrepender() { - if (PacketPrepender == null) { - return null; + public ServerChannel findServerChannel(Object serverConnection) { + for (Field field : searchForFieldByType(serverConnection.getClass(), List.class)) { + try { + field.setAccessible(true); + List list = (List) field.get(serverConnection); + if (list != null && list.size() > 0 && list.get(0) instanceof ChannelFuture future && + future.isSuccess() && future.channel() instanceof ServerChannel) { + return (ServerChannel) future.channel(); + } + } catch (IllegalAccessException e) { + throw new IllegalStateException("should not reach here", e); + } } - try { - return PacketPrepender.getConstructor().newInstance(); - } catch (Exception e) { - return null; - } + return null; } - public Object newNetworkManagerServer(int rateLimit) { - if (NetworkManagerServer == null) { - return null; - } - try { - return NetworkManagerServer.getConstructor(Integer.class).newInstance(rateLimit); - } catch (Exception e) { - return null; - } + public ChannelHandler findServerHandler(ServerChannel serverChannel) { + return serverChannel.pipeline().get(ServerBootstrapAcceptor); } - public Object newServerNetworkManager() { - if (NetworkManager == null) { - return null; - } - + public ChannelHandler findChildHandler(ChannelHandler serverAcceptor) { try { - return NetworkManager.getConstructor(EnumProtocolDirection).newInstance(serverBound()); - } catch (Exception e) { + return (ChannelHandler) SBA_childHandler.get(serverAcceptor); + } catch (IllegalAccessException error) { + log.warning("failed to get childHandler, error: " + error); return null; } } - private Object serverBound() { - if (EnumProtocolDirection == null) { - return null; - } - + public Map.Entry, Object>[] findChildOptions(ChannelHandler serverAcceptor) { try { - return Enum.valueOf((Class) EnumProtocolDirection, "SERVERBOUND"); - } catch (Exception e) { + return (Map.Entry, Object>[]) SBA_childOptions.get(serverAcceptor); + } catch (IllegalAccessException error) { + log.warning("failed to get childOptions, error: " + error); return null; } } - private Object clientBound() { - if (EnumProtocolDirection == null) { - return null; - } - + public Map.Entry, Object>[] findChildAttrs(ChannelHandler serverAcceptor) { try { - return Enum.valueOf((Class) EnumProtocolDirection, "CLIENTBOUND"); - } catch (Exception e) { + return (Map.Entry, Object>[]) SBA_childAttrs.get(serverAcceptor); + } catch (IllegalAccessException error) { + log.warning("failed to get childAttrs, error: " + error); return null; } } @@ -472,7 +311,11 @@ public List searchForFieldByType(Class subject, Class type) { @Override public String toString() { return "ReflectionHelper{" + - "ServerConnection=" + ServerConnection + + "ServerBootstrapAcceptor=" + ServerBootstrapAcceptor + + ", SBA_childHandler=" + SBA_childHandler + + ", SBA_childOptions=" + SBA_childOptions + + ", SBA_childAttrs=" + SBA_childAttrs + + ", ServerConnection=" + ServerConnection + ", LegacyPingHandler=" + LegacyPingHandler + ", MinecraftServer=" + MinecraftServer + ", PacketSplitter=" + PacketSplitter +