From 7867c496ec2e064083ae6551b292655e5267e37d Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 24 Jul 2018 20:11:37 -0400 Subject: [PATCH] Initial client connection pipeline, this isn't done yet. --- .../minecraft/velocity/data/ServerInfo.java | 44 +++++++ .../velocity/protocol/StateRegistry.java | 4 +- .../protocol/netty/MinecraftDecoder.java | 4 +- .../protocol/netty/MinecraftEncoder.java | 3 + .../netty/MinecraftPipelineUtils.java | 11 +- .../netty/MinecraftVarintFrameDecoder.java | 1 - .../velocity/proxy/ConnectedPlayer.java | 18 ++- .../proxy/InboundMinecraftConnection.java | 13 +- .../velocity/proxy/ServerConnection.java | 114 ++++++++++++++++++ .../velocity/proxy/VelocityServer.java | 11 +- .../proxy/handler/LoginSessionHandler.java | 16 ++- .../proxy/server/ServerConnection.java | 19 --- 12 files changed, 225 insertions(+), 33 deletions(-) create mode 100644 src/main/java/io/minimum/minecraft/velocity/data/ServerInfo.java create mode 100644 src/main/java/io/minimum/minecraft/velocity/proxy/ServerConnection.java delete mode 100644 src/main/java/io/minimum/minecraft/velocity/proxy/server/ServerConnection.java diff --git a/src/main/java/io/minimum/minecraft/velocity/data/ServerInfo.java b/src/main/java/io/minimum/minecraft/velocity/data/ServerInfo.java new file mode 100644 index 00000000..aa3f5c33 --- /dev/null +++ b/src/main/java/io/minimum/minecraft/velocity/data/ServerInfo.java @@ -0,0 +1,44 @@ +package io.minimum.minecraft.velocity.data; + +import java.net.InetSocketAddress; +import java.util.Objects; + +public class ServerInfo { + private final String name; + private final InetSocketAddress address; + + public ServerInfo(String name, InetSocketAddress address) { + this.name = name; + this.address = address; + } + + public String getName() { + return name; + } + + public InetSocketAddress getAddress() { + return address; + } + + @Override + public String toString() { + return "ServerInfo{" + + "name='" + name + '\'' + + ", address=" + address + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServerInfo that = (ServerInfo) o; + return Objects.equals(name, that.name) && + Objects.equals(address, that.address); + } + + @Override + public int hashCode() { + return Objects.hash(name, address); + } +} diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java b/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java index 7b7e657e..60f10ee2 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java @@ -22,7 +22,9 @@ public enum StateRegistry { } }, PLAY { - + { + TO_CLIENT.register(0x1A, Disconnect.class, Disconnect::new); + } }, LOGIN { { diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftDecoder.java b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftDecoder.java index aecfeb72..8f7d5340 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftDecoder.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftDecoder.java @@ -30,9 +30,7 @@ public class MinecraftDecoder extends MessageToMessageDecoder { int packetId = ProtocolUtils.readVarInt(msg); StateRegistry.ProtocolMappings mappings = direction == ProtocolConstants.Direction.TO_CLIENT ? state.TO_CLIENT : state.TO_SERVER; MinecraftPacket packet = mappings.createPacket(packetId); - System.out.println("Decode!"); - System.out.println("packet ID: " + packetId); - System.out.println("packet hexdump: " + ByteBufUtil.hexDump(slice)); + System.out.println(direction + " <- " + ByteBufUtil.hexDump(slice)); if (packet == null) { msg.skipBytes(msg.readableBytes()); out.add(new PacketWrapper(null, slice)); diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftEncoder.java b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftEncoder.java index 9d665b58..b87cad3a 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftEncoder.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftEncoder.java @@ -3,6 +3,7 @@ package io.minimum.minecraft.velocity.protocol.netty; import com.google.common.base.Preconditions; import io.minimum.minecraft.velocity.protocol.*; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; @@ -22,6 +23,8 @@ public class MinecraftEncoder extends MessageToByteEncoder { int packetId = mappings.getId(msg); ProtocolUtils.writeVarInt(out, packetId); msg.encode(out, direction, protocolVersion); + + System.out.println(direction + " -> " + ByteBufUtil.hexDump(out)); } public int getProtocolVersion() { diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftPipelineUtils.java b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftPipelineUtils.java index 4b5c16b2..8832b593 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftPipelineUtils.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftPipelineUtils.java @@ -4,7 +4,7 @@ import io.minimum.minecraft.velocity.protocol.ProtocolConstants; import io.netty.channel.Channel; public class MinecraftPipelineUtils { - public static void strapPipeline(Channel ch) { + public static void strapPipelineForServer(Channel ch) { ch.pipeline().addLast("legacy-ping-decode", new LegacyPingDecoder()); ch.pipeline().addLast("frame-decoder", new MinecraftVarintFrameDecoder()); ch.pipeline().addLast("legacy-ping-encode", LegacyPingEncoder.INSTANCE); @@ -12,4 +12,13 @@ public class MinecraftPipelineUtils { ch.pipeline().addLast("minecraft-decoder", new MinecraftDecoder(ProtocolConstants.Direction.TO_SERVER)); ch.pipeline().addLast("minecraft-encoder", new MinecraftEncoder(ProtocolConstants.Direction.TO_CLIENT)); } + + public static void strapPipelineForProxy(Channel ch) { + ch.pipeline().addLast("legacy-ping-decode", new LegacyPingDecoder()); + ch.pipeline().addLast("frame-decoder", new MinecraftVarintFrameDecoder()); + ch.pipeline().addLast("legacy-ping-encode", LegacyPingEncoder.INSTANCE); + ch.pipeline().addLast("frame-encoder", MinecraftVarintLengthEncoder.INSTANCE); + ch.pipeline().addLast("minecraft-decoder", new MinecraftDecoder(ProtocolConstants.Direction.TO_CLIENT)); + ch.pipeline().addLast("minecraft-encoder", new MinecraftEncoder(ProtocolConstants.Direction.TO_SERVER)); + } } diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftVarintFrameDecoder.java b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftVarintFrameDecoder.java index 964a94c3..b8a2c6dd 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftVarintFrameDecoder.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftVarintFrameDecoder.java @@ -21,7 +21,6 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { return; } - System.out.println("Got a varint-prefixed packet length " + packetLength); out.add(in.slice(in.readerIndex(), packetLength).retain()); in.skipBytes(packetLength); } diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java b/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java index 1c4d63b6..c7ca0a5a 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java @@ -1,6 +1,9 @@ package io.minimum.minecraft.velocity.proxy; -import io.minimum.minecraft.velocity.proxy.server.ServerConnection; +import io.minimum.minecraft.velocity.protocol.packets.Disconnect; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; +import net.kyori.text.serializer.ComponentSerializers; import java.util.UUID; @@ -31,4 +34,17 @@ public class ConnectedPlayer { public ServerConnection getConnectedServer() { return connectedServer; } + + public void handleConnectionException(Throwable throwable) { + String error = "Exception: " + throwable.getClass().getName() + ": " + throwable.getMessage(); + + if (connectedServer == null) { + // The player isn't yet connected to a server - we should disconnect them. + Disconnect disconnect = new Disconnect(); + disconnect.setReason(ComponentSerializers.JSON.serialize(TextComponent.of(error, TextColor.RED))); + connection.closeWith(disconnect); + } else { + // TODO + } + } } diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java b/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java index 05a6e60d..07b3e9e1 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java @@ -13,6 +13,8 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.util.AttributeKey; +import java.util.Optional; + public class InboundMinecraftConnection { public static final AttributeKey CONNECTION = AttributeKey.newInstance("velocity-connection"); @@ -21,6 +23,7 @@ public class InboundMinecraftConnection { private Handshake handshake; private StateRegistry state; private MinecraftSessionHandler sessionHandler; + private ConnectedPlayer connectedPlayer; public InboundMinecraftConnection(Channel channel) { this.channel = channel; @@ -79,7 +82,7 @@ public class InboundMinecraftConnection { Preconditions.checkState(!closed, "Connection is closed."); } - private void setStatus(StateRegistry state) { + public void setStatus(StateRegistry state) { Preconditions.checkNotNull(state, "state"); this.state = state; channel.pipeline().get(MinecraftEncoder.class).setState(state); @@ -89,4 +92,12 @@ public class InboundMinecraftConnection { public void teardown() { closed = true; } + + public boolean isClosed() { + return closed; + } + + public Optional getConnectedPlayer() { + return Optional.ofNullable(connectedPlayer); + } } diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/ServerConnection.java b/src/main/java/io/minimum/minecraft/velocity/proxy/ServerConnection.java new file mode 100644 index 00000000..c7612373 --- /dev/null +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/ServerConnection.java @@ -0,0 +1,114 @@ +package io.minimum.minecraft.velocity.proxy; + +import io.minimum.minecraft.velocity.protocol.MinecraftPacket; +import io.minimum.minecraft.velocity.protocol.PacketWrapper; +import io.minimum.minecraft.velocity.protocol.ProtocolConstants; +import io.minimum.minecraft.velocity.protocol.StateRegistry; +import io.minimum.minecraft.velocity.data.ServerInfo; +import io.minimum.minecraft.velocity.protocol.netty.MinecraftDecoder; +import io.minimum.minecraft.velocity.protocol.netty.MinecraftEncoder; +import io.minimum.minecraft.velocity.protocol.netty.MinecraftPipelineUtils; +import io.minimum.minecraft.velocity.protocol.packets.Disconnect; +import io.minimum.minecraft.velocity.protocol.packets.Handshake; +import io.minimum.minecraft.velocity.protocol.packets.ServerLogin; +import io.minimum.minecraft.velocity.protocol.packets.ServerLoginSuccess; +import io.netty.channel.*; +import net.kyori.text.serializer.ComponentSerializers; + +import java.io.IOException; + +public class ServerConnection { + private Channel channel; + private final ServerInfo info; + private final ConnectedPlayer proxyPlayer; + private StateRegistry registry; + private final VelocityServer server; + + public ServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) { + this.info = target; + this.proxyPlayer = proxyPlayer; + this.server = server; + this.registry = StateRegistry.HANDSHAKE; + } + + public void connect() { + server.initializeGenericBootstrap() + .handler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + MinecraftPipelineUtils.strapPipelineForProxy(ch); + ch.pipeline().addLast("state-based-interceptor", new StateBasedInterceptor()); + } + }) + .connect(info.getAddress()) + .addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + channel = future.channel(); + } + }); + } + + private class StateBasedInterceptor extends ChannelInboundHandlerAdapter { + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + // Initiate a handshake. + Handshake handshake = new Handshake(); + handshake.setNextStatus(2); // login + handshake.setProtocolVersion(ProtocolConstants.MINECRAFT_1_12); // TODO: Expose client version + handshake.setServerAddress(info.getAddress().getHostString()); + handshake.setPort(info.getAddress().getPort()); + ctx.writeAndFlush(handshake, ctx.voidPromise()); + + setRegistry(StateRegistry.LOGIN); + + // Login + ServerLogin login = new ServerLogin(); + login.setUsername(proxyPlayer.getUsername()); + ctx.writeAndFlush(login, ctx.voidPromise()); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof PacketWrapper) { + PacketWrapper pw = (PacketWrapper) msg; + try { + switch (registry) { + case LOGIN: + onLogin(ctx, pw); + break; + default: + throw new UnsupportedOperationException("Unsupported state " + registry); + } + } finally { + ((PacketWrapper) msg).getBuffer().release(); + } + } + } + + private void onLogin(ChannelHandlerContext ctx, PacketWrapper wrapper) { + MinecraftPacket packet = wrapper.getPacket(); + if (packet instanceof Disconnect) { + Disconnect disconnect = (Disconnect) packet; + ctx.close(); + proxyPlayer.handleConnectionException(new IOException("Disconnected from target: " + jsonToPlain(disconnect.getReason()))); + } + + if (packet instanceof ServerLoginSuccess) { + System.out.println("got it"); + } + } + } + + private void setRegistry(StateRegistry registry) { + this.registry = registry; + this.channel.pipeline().get(MinecraftEncoder.class).setState(registry); + this.channel.pipeline().get(MinecraftDecoder.class).setState(registry); + } + + private static String jsonToPlain(String j) { + return ComponentSerializers.LEGACY.serialize( + ComponentSerializers.JSON.deserialize(j) + ).replaceAll("\\u00A7[a-z0-9]", ""); + } +} diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/VelocityServer.java b/src/main/java/io/minimum/minecraft/velocity/proxy/VelocityServer.java index 944f38fd..b6ff4f77 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/VelocityServer.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/VelocityServer.java @@ -1,8 +1,6 @@ package io.minimum.minecraft.velocity.proxy; -import io.minimum.minecraft.velocity.protocol.ProtocolConstants; import io.minimum.minecraft.velocity.protocol.netty.*; -import io.minimum.minecraft.velocity.proxy.server.ServerConnection; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; @@ -11,6 +9,8 @@ import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; public class VelocityServer { + private static VelocityServer server; + private EventLoopGroup bossGroup; private EventLoopGroup childGroup; @@ -18,9 +18,14 @@ public class VelocityServer { } + public static VelocityServer getServer() { + return server; + } + public void initialize() { bossGroup = new NioEventLoopGroup(); childGroup = new NioEventLoopGroup(); + server = this; new ServerBootstrap() .channel(NioServerSocketChannel.class) .group(bossGroup, childGroup) @@ -28,7 +33,7 @@ public class VelocityServer { @Override protected void initChannel(Channel ch) throws Exception { ch.attr(InboundMinecraftConnection.CONNECTION).set(new InboundMinecraftConnection(ch)); - MinecraftPipelineUtils.strapPipeline(ch); + MinecraftPipelineUtils.strapPipelineForServer(ch); ch.pipeline().addLast("handler", new MinecraftClientSessionHandler()); } }) diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/handler/LoginSessionHandler.java b/src/main/java/io/minimum/minecraft/velocity/proxy/handler/LoginSessionHandler.java index f2ae5a6b..906995dc 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/handler/LoginSessionHandler.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/handler/LoginSessionHandler.java @@ -1,12 +1,14 @@ package io.minimum.minecraft.velocity.proxy.handler; import com.google.common.base.Preconditions; +import io.minimum.minecraft.velocity.data.ServerInfo; import io.minimum.minecraft.velocity.protocol.MinecraftPacket; +import io.minimum.minecraft.velocity.protocol.StateRegistry; import io.minimum.minecraft.velocity.protocol.packets.ServerLogin; import io.minimum.minecraft.velocity.protocol.packets.ServerLoginSuccess; -import io.minimum.minecraft.velocity.proxy.InboundMinecraftConnection; -import io.minimum.minecraft.velocity.proxy.MinecraftSessionHandler; +import io.minimum.minecraft.velocity.proxy.*; +import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.UUID; @@ -21,12 +23,20 @@ public class LoginSessionHandler implements MinecraftSessionHandler { public void handle(MinecraftPacket packet) { Preconditions.checkArgument(packet instanceof ServerLogin, "Expected a ServerLogin packet, not " + packet.getClass().getName()); + // TODO: Encryption and compression String username = ((ServerLogin) packet).getUsername(); ServerLoginSuccess success = new ServerLoginSuccess(); success.setUsername(username); success.setUuid(generateOfflinePlayerUuid(username)); - connection.write(success); + + connection.setStatus(StateRegistry.PLAY); + + ConnectedPlayer player = new ConnectedPlayer(username, generateOfflinePlayerUuid(username), connection); + + ServerInfo info = new ServerInfo("test", new InetSocketAddress("127.0.0.1", 25565)); + ServerConnection connection = new ServerConnection(info, player, VelocityServer.getServer()); + connection.connect(); } private static UUID generateOfflinePlayerUuid(String username) { diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/server/ServerConnection.java b/src/main/java/io/minimum/minecraft/velocity/proxy/server/ServerConnection.java deleted file mode 100644 index 5c93ebb0..00000000 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/server/ServerConnection.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.minimum.minecraft.velocity.proxy.server; - -import io.minimum.minecraft.velocity.protocol.StateRegistry; -import io.minimum.minecraft.velocity.proxy.ConnectedPlayer; -import io.netty.channel.Channel; - -public class ServerConnection { - private final Channel remoteServer; - private final ConnectedPlayer proxyPlayer; - private StateRegistry registry; - - public ServerConnection(Channel remoteServer, ConnectedPlayer proxyPlayer) { - this.remoteServer = remoteServer; - this.proxyPlayer = proxyPlayer; - this.registry = StateRegistry.HANDSHAKE; - } - - -}