diff --git a/src/main/java/ru/nanit/limbo/connection/ClientConnection.java b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java index 078c360..8747ca0 100644 --- a/src/main/java/ru/nanit/limbo/connection/ClientConnection.java +++ b/src/main/java/ru/nanit/limbo/connection/ClientConnection.java @@ -6,6 +6,8 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import ru.nanit.limbo.LimboConstants; +import ru.nanit.limbo.protocol.ByteMessage; +import ru.nanit.limbo.protocol.PreRenderedPacket; import ru.nanit.limbo.protocol.packets.login.*; import ru.nanit.limbo.protocol.packets.play.*; import ru.nanit.limbo.protocol.pipeline.PacketDecoder; @@ -19,18 +21,29 @@ import ru.nanit.limbo.protocol.registry.Version; import ru.nanit.limbo.server.LimboServer; import ru.nanit.limbo.util.Logger; import ru.nanit.limbo.util.UuidUtil; -import ru.nanit.limbo.util.VelocityUtil; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; public class ClientConnection extends ChannelInboundHandlerAdapter { + private static PreRenderedPacket PACKET_LOGIN_SUCCESS; + private static PreRenderedPacket PACKET_JOIN_GAME; + private static PreRenderedPacket PACKET_PLAYER_ABILITIES; + private static PreRenderedPacket PACKET_PLAYER_INFO; + private static PreRenderedPacket PACKET_PLAYER_POS; + private static PreRenderedPacket PACKET_JOIN_MESSAGE; + private static PreRenderedPacket PACKET_BOSS_BAR; + private final LimboServer server; private final Channel channel; - private final GameProfile profile; + private final GameProfile gameProfile; private State state; private Version clientVersion; @@ -42,26 +55,25 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { this.server = server; this.channel = channel; this.address = channel.remoteAddress(); - this.profile = new GameProfile(); + this.gameProfile = new GameProfile(); } public UUID getUuid() { - return profile.getUuid(); + return gameProfile.getUuid(); } - public String getUsername() { - return profile.getUsername(); + public String getUsername(){ + return gameProfile.getUsername(); } - private void setAddress(String host){ - this.address = new InetSocketAddress(host, ((InetSocketAddress)this.address).getPort()); + public SocketAddress getAddress() { + return address; } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (state.equals(State.PLAY)){ server.getConnections().removeConnection(this); - Logger.info("Player %s disconnected", getUsername()); } super.channelInactive(ctx); } @@ -90,29 +102,32 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { if (split.length == 3 || split.length == 4){ setAddress(split[1]); - profile.setUuid(UuidUtil.fromString(split[2])); + gameProfile.setUuid(UuidUtil.fromString(split[2])); } else { - disconnect("You've enabled player info forwarding. You need to connect with proxy"); + disconnectLogin("You've enabled player info forwarding. You need to connect with proxy"); } } + return; } if (packet instanceof PacketStatusRequest){ sendPacket(new PacketStatusResponse(server)); + return; } if (packet instanceof PacketStatusPing){ sendPacketAndClose(packet); + return; } if (packet instanceof PacketLoginStart){ if (server.getConnections().getCount() >= server.getConfig().getMaxPlayers()){ - disconnect("Too many players connected"); + disconnectLogin("Too many players connected"); return; } if (!clientVersion.equals(Version.getCurrentSupported())){ - disconnect("Incompatible client version"); + disconnectLogin("Incompatible client version"); return; } @@ -127,13 +142,12 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { } if (!server.getConfig().getInfoForwarding().isModern()){ - profile.setUsername(((PacketLoginStart) packet).getUsername()); - - if (profile.getUuid() == null) - profile.setUuid(UuidUtil.getOfflineModeUuid(getUsername())); + gameProfile.setUsername(((PacketLoginStart)packet).getUsername()); + gameProfile.setUuid(UuidUtil.getOfflineModeUuid(getUsername())); } fireLoginSuccess(); + return; } if (packet instanceof PacketLoginPluginResponse){ @@ -143,18 +157,19 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { && response.getMessageId() == velocityLoginMessageId){ if (!response.isSuccessful() || response.getData() == null){ - disconnect("You need to connect with Velocity"); + disconnectLogin("You need to connect with Velocity"); return; } - if (!VelocityUtil.checkIntegrity(response.getData())) { - disconnect("Can't verify forwarded player info"); + if (!checkVelocityKeyIntegrity(response.getData())) { + disconnectLogin("Can't verify forwarded player info"); return; } + // Order is important setAddress(response.getData().readString()); - profile.setUuid(response.getData().readUuid()); - profile.setUsername(response.getData().readString()); + gameProfile.setUuid(response.getData().readUuid()); + gameProfile.setUsername(response.getData().readString()); fireLoginSuccess(); } @@ -163,79 +178,30 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { private void fireLoginSuccess(){ if (server.getConfig().getInfoForwarding().isModern() && velocityLoginMessageId == -1){ - disconnect("You need to connect with Velocity"); + disconnectLogin("You need to connect with Velocity"); return; } - PacketLoginSuccess loginSuccess = new PacketLoginSuccess(); - loginSuccess.setUuid(UuidUtil.getOfflineModeUuid(getUsername())); - loginSuccess.setUsername(getUsername()); - sendPacket(loginSuccess); - + sendPacket(PACKET_LOGIN_SUCCESS); updateState(State.PLAY); + server.getConnections().addConnection(this); - Logger.info("Player %s connected (%s)", getUsername(), address); - - sendJoinPackets(); - } - - private void sendJoinPackets(){ - PacketJoinGame joinGame = new PacketJoinGame(); - - joinGame.setEntityId(0); - joinGame.setEnableRespawnScreen(true); - joinGame.setFlat(false); - joinGame.setGameMode(server.getConfig().getGameMode()); - joinGame.setHardcore(false); - joinGame.setMaxPlayers(server.getConfig().getMaxPlayers()); - joinGame.setPreviousGameMode(-1); - joinGame.setReducedDebugInfo(false); - joinGame.setDebug(false); - joinGame.setViewDistance(2); - joinGame.setWorldName("minecraft:world"); - joinGame.setWorldNames("minecraft:world"); - joinGame.setHashedSeed(0); - joinGame.setDimensionCodec(server.getDimensionRegistry().getCodec()); - joinGame.setDimension(server.getDimensionRegistry().getDefaultDimension()); - - PacketPlayerAbilities abilities = new PacketPlayerAbilities(); - - abilities.setFlyingSpeed(0.0F); - abilities.setFlags(0x02); - abilities.setFieldOfView(0.1F); - - PacketPlayerPositionAndLook positionAndLook = new PacketPlayerPositionAndLook(); - - positionAndLook.setX(server.getConfig().getSpawnPosition().getX()); - positionAndLook.setY(server.getConfig().getSpawnPosition().getY()); - positionAndLook.setZ(server.getConfig().getSpawnPosition().getZ()); - positionAndLook.setYaw(server.getConfig().getSpawnPosition().getYaw()); - positionAndLook.setPitch(server.getConfig().getSpawnPosition().getPitch()); - positionAndLook.setTeleportId(ThreadLocalRandom.current().nextInt()); - - PacketPlayerInfo info = new PacketPlayerInfo(); - - info.setConnection(this); - info.setGameMode(server.getConfig().getGameMode()); - - sendPacket(joinGame); - sendPacket(abilities); - sendPacket(positionAndLook); - sendPacket(info); + sendPacket(PACKET_JOIN_GAME); + sendPacket(PACKET_PLAYER_ABILITIES); + sendPacket(PACKET_PLAYER_POS); + sendPacket(PACKET_PLAYER_INFO); sendKeepAlive(); - if (server.getJoinMessage() != null){ - sendPacket(server.getJoinMessage()); - } + if (PACKET_BOSS_BAR != null) + sendPacket(PACKET_BOSS_BAR); - if (server.getJoinBossBar() != null){ - sendPacket(server.getJoinBossBar()); - } + if (PACKET_JOIN_MESSAGE != null) + sendPacket(PACKET_JOIN_MESSAGE); } - public void disconnect(String reason){ + public void disconnectLogin(String reason){ if (isConnected() && state == State.LOGIN){ PacketDisconnect disconnect = new PacketDisconnect(); disconnect.setReason(reason); @@ -265,10 +231,98 @@ public class ClientConnection extends ChannelInboundHandlerAdapter { return channel.isActive(); } - public void updateState(State state){ + private void updateState(State state){ this.state = state; - channel.pipeline().get(PacketDecoder.class).updateState(state); channel.pipeline().get(PacketEncoder.class).updateState(state); } + + private void setAddress(String host){ + this.address = new InetSocketAddress(host, ((InetSocketAddress)this.address).getPort()); + } + + private boolean checkVelocityKeyIntegrity(ByteMessage buf) { + byte[] signature = new byte[32]; + buf.readBytes(signature); + byte[] data = new byte[buf.readableBytes()]; + buf.getBytes(buf.readerIndex(), data); + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(server.getConfig().getInfoForwarding().getSecretKey(), "HmacSHA256")); + byte[] mySignature = mac.doFinal(data); + if (!MessageDigest.isEqual(signature, mySignature)) + return false; + } catch (InvalidKeyException |java.security.NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + int version = buf.readVarInt(); + if (version != 1) + throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + '\001'); + return true; + } + + public static void preInitPackets(LimboServer server){ + final String username = server.getConfig().getPingData().getVersion(); + final UUID uuid = UuidUtil.getOfflineModeUuid(username); + + PacketLoginSuccess loginSuccess = new PacketLoginSuccess(); + loginSuccess.setUsername(username); + loginSuccess.setUuid(uuid); + + PacketJoinGame joinGame = new PacketJoinGame(); + joinGame.setEntityId(0); + joinGame.setEnableRespawnScreen(true); + joinGame.setFlat(false); + joinGame.setGameMode(server.getConfig().getGameMode()); + joinGame.setHardcore(false); + joinGame.setMaxPlayers(server.getConfig().getMaxPlayers()); + joinGame.setPreviousGameMode(-1); + joinGame.setReducedDebugInfo(true); + joinGame.setDebug(false); + joinGame.setViewDistance(2); + joinGame.setWorldName("minecraft:world"); + joinGame.setWorldNames("minecraft:world"); + joinGame.setHashedSeed(0); + joinGame.setDimensionCodec(server.getDimensionRegistry().getCodec()); + joinGame.setDimension(server.getDimensionRegistry().getDefaultDimension()); + + PacketPlayerAbilities playerAbilities = new PacketPlayerAbilities(); + playerAbilities.setFlyingSpeed(0.0F); + playerAbilities.setFlags(0x02); + playerAbilities.setFieldOfView(0.1F); + + PacketPlayerPositionAndLook positionAndLook = new PacketPlayerPositionAndLook(); + positionAndLook.setX(server.getConfig().getSpawnPosition().getX()); + positionAndLook.setY(server.getConfig().getSpawnPosition().getY()); + positionAndLook.setZ(server.getConfig().getSpawnPosition().getZ()); + positionAndLook.setYaw(server.getConfig().getSpawnPosition().getYaw()); + positionAndLook.setPitch(server.getConfig().getSpawnPosition().getPitch()); + positionAndLook.setTeleportId(ThreadLocalRandom.current().nextInt()); + + PacketPlayerInfo info = new PacketPlayerInfo(); + info.setUsername(username); + info.setGameMode(server.getConfig().getGameMode()); + info.setUuid(uuid); + + PACKET_LOGIN_SUCCESS = PreRenderedPacket.of(loginSuccess); + PACKET_JOIN_GAME = PreRenderedPacket.of(joinGame); + PACKET_PLAYER_ABILITIES = PreRenderedPacket.of(playerAbilities); + PACKET_PLAYER_POS = PreRenderedPacket.of(positionAndLook); + PACKET_PLAYER_INFO = PreRenderedPacket.of(info); + + if (server.getConfig().isUseJoinMessage()){ + PacketChatMessage joinMessage = new PacketChatMessage(); + joinMessage.setJsonData(server.getConfig().getJoinMessage()); + joinMessage.setPosition(PacketChatMessage.Position.CHAT); + joinMessage.setSender(UUID.randomUUID()); + PACKET_JOIN_MESSAGE = PreRenderedPacket.of(joinMessage); + } + + if (server.getConfig().isUseBossBar()){ + PacketBossBar bossBar = new PacketBossBar(); + bossBar.setBossBar(server.getConfig().getBossBar()); + bossBar.setUuid(UUID.randomUUID()); + PACKET_BOSS_BAR = PreRenderedPacket.of(bossBar); + } + } } diff --git a/src/main/java/ru/nanit/limbo/connection/GameProfile.java b/src/main/java/ru/nanit/limbo/connection/GameProfile.java index 39da0f3..69e4834 100644 --- a/src/main/java/ru/nanit/limbo/connection/GameProfile.java +++ b/src/main/java/ru/nanit/limbo/connection/GameProfile.java @@ -4,22 +4,22 @@ import java.util.UUID; public class GameProfile { - private String username; private UUID uuid; - - public String getUsername() { - return username; - } + private String username; public UUID getUuid() { return uuid; } - public void setUsername(String username) { - this.username = username; - } - public void setUuid(UUID uuid) { this.uuid = uuid; } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } } diff --git a/src/main/java/ru/nanit/limbo/protocol/PreRenderedPacket.java b/src/main/java/ru/nanit/limbo/protocol/PreRenderedPacket.java new file mode 100644 index 0000000..c53753f --- /dev/null +++ b/src/main/java/ru/nanit/limbo/protocol/PreRenderedPacket.java @@ -0,0 +1,31 @@ +package ru.nanit.limbo.protocol; + +public class PreRenderedPacket implements PacketOut { + + private final PacketOut packet; + private byte[] message; + + public PreRenderedPacket(PacketOut packet){ + this.packet = packet; + } + + public PacketOut getWrappedPacket(){ + return packet; + } + + public PreRenderedPacket render(){ + ByteMessage renderedMessage = ByteMessage.create(); + packet.encode(renderedMessage); + this.message = renderedMessage.toByteArray(); + return this; + } + + @Override + public void encode(ByteMessage msg) { + msg.writeBytes(message); + } + + public static PreRenderedPacket of(PacketOut packet){ + return new PreRenderedPacket(packet).render(); + } +} diff --git a/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketPlayerInfo.java b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketPlayerInfo.java index e7b0856..caf06c3 100644 --- a/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketPlayerInfo.java +++ b/src/main/java/ru/nanit/limbo/protocol/packets/play/PacketPlayerInfo.java @@ -1,31 +1,37 @@ package ru.nanit.limbo.protocol.packets.play; -import ru.nanit.limbo.connection.ClientConnection; import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.PacketOut; +import java.util.UUID; + /** - * This packet wrapper used only for ADD_PLAYER action + * This packet was very simplified and using only for ADD_PLAYER action */ public class PacketPlayerInfo implements PacketOut { - private int gameMode; - private ClientConnection connection; - - public void setConnection(ClientConnection connection) { - this.connection = connection; - } + private int gameMode = 3; + private String username = ""; + private UUID uuid; public void setGameMode(int gameMode) { this.gameMode = gameMode; } + public void setUsername(String username){ + this.username = username; + } + + public void setUuid(UUID uuid){ + this.uuid = uuid; + } + @Override public void encode(ByteMessage msg) { msg.writeVarInt(0); msg.writeVarInt(1); - msg.writeUuid(connection.getUuid()); - msg.writeString(connection.getUsername()); + msg.writeUuid(uuid); + msg.writeString(username); msg.writeVarInt(0); msg.writeVarInt(gameMode); msg.writeVarInt(60); diff --git a/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketEncoder.java b/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketEncoder.java index 1c35b2c..3680ce0 100644 --- a/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketEncoder.java +++ b/src/main/java/ru/nanit/limbo/protocol/pipeline/PacketEncoder.java @@ -5,6 +5,7 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import ru.nanit.limbo.protocol.ByteMessage; import ru.nanit.limbo.protocol.Packet; +import ru.nanit.limbo.protocol.PreRenderedPacket; import ru.nanit.limbo.protocol.registry.State; import ru.nanit.limbo.util.Logger; @@ -21,7 +22,13 @@ public class PacketEncoder extends MessageToByteEncoder { if (registry == null) return; ByteMessage msg = new ByteMessage(out); - int packetId = registry.getPacketId(packet.getClass()); + int packetId; + + if (packet instanceof PreRenderedPacket){ + packetId = registry.getPacketId(((PreRenderedPacket)packet).getWrappedPacket().getClass()); + } else { + packetId = registry.getPacketId(packet.getClass()); + } if (packetId == -1){ Logger.warning("Undefined packet class: %s", packet.getClass().getName()); diff --git a/src/main/java/ru/nanit/limbo/server/Connections.java b/src/main/java/ru/nanit/limbo/server/Connections.java index c4fa340..c2bee63 100644 --- a/src/main/java/ru/nanit/limbo/server/Connections.java +++ b/src/main/java/ru/nanit/limbo/server/Connections.java @@ -1,6 +1,7 @@ package ru.nanit.limbo.server; import ru.nanit.limbo.connection.ClientConnection; +import ru.nanit.limbo.util.Logger; import java.util.Collection; import java.util.Collections; @@ -26,9 +27,11 @@ public final class Connections { public void addConnection(ClientConnection connection){ connections.put(connection.getUuid(), connection); + Logger.info("Player %s connected (%s)", connection.getUsername(), connection.getAddress()); } public void removeConnection(ClientConnection connection){ connections.remove(connection.getUuid()); + Logger.info("Player %s disconnected", connection.getUsername()); } } diff --git a/src/main/java/ru/nanit/limbo/server/LimboServer.java b/src/main/java/ru/nanit/limbo/server/LimboServer.java index 2ee69b1..81b6fe7 100644 --- a/src/main/java/ru/nanit/limbo/server/LimboServer.java +++ b/src/main/java/ru/nanit/limbo/server/LimboServer.java @@ -8,16 +8,12 @@ import ru.nanit.limbo.configuration.LimboConfig; import ru.nanit.limbo.configuration.SocketAddressSerializer; import ru.nanit.limbo.connection.ClientChannelInitializer; import ru.nanit.limbo.connection.ClientConnection; -import ru.nanit.limbo.protocol.packets.play.PacketBossBar; -import ru.nanit.limbo.protocol.packets.play.PacketChatMessage; import ru.nanit.limbo.server.data.*; import ru.nanit.limbo.util.Logger; -import ru.nanit.limbo.util.VelocityUtil; import ru.nanit.limbo.world.DimensionRegistry; import java.net.SocketAddress; import java.nio.file.Paths; -import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -28,9 +24,6 @@ public final class LimboServer { private Connections connections; private DimensionRegistry dimensionRegistry; - private PacketChatMessage joinMessage; - private PacketBossBar joinBossBar; - public LimboConfig getConfig(){ return config; } @@ -43,14 +36,6 @@ public final class LimboServer { return dimensionRegistry; } - public PacketChatMessage getJoinMessage() { - return joinMessage; - } - - public PacketBossBar getJoinBossBar() { - return joinBossBar; - } - public void start() throws Exception { Logger.info("Starting server..."); @@ -65,16 +50,11 @@ public final class LimboServer { Logger.setLevel(config.getDebugLevel()); - if (config.getInfoForwarding().isModern()){ - VelocityUtil.init(config); - } - dimensionRegistry = new DimensionRegistry(); dimensionRegistry.load(config.getDimensionType()); - connections = new Connections(); - initInGameData(); + ClientConnection.preInitPackets(this); ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate(this::broadcastKeepAlive, 0L, 5L, TimeUnit.SECONDS); @@ -89,21 +69,6 @@ public final class LimboServer { Logger.info("Server started on %s", config.getAddress()); } - private void initInGameData(){ - if (config.isUseJoinMessage()){ - joinMessage = new PacketChatMessage(); - joinMessage.setJsonData(config.getJoinMessage()); - joinMessage.setPosition(PacketChatMessage.Position.CHAT); - joinMessage.setSender(UUID.randomUUID()); - } - - if (config.isUseBossBar()){ - joinBossBar = new PacketBossBar(); - joinBossBar.setBossBar(config.getBossBar()); - joinBossBar.setUuid(UUID.randomUUID()); - } - } - private void broadcastKeepAlive(){ connections.getAllConnections().forEach(ClientConnection::sendKeepAlive); } diff --git a/src/main/java/ru/nanit/limbo/util/VelocityUtil.java b/src/main/java/ru/nanit/limbo/util/VelocityUtil.java deleted file mode 100644 index c40a30b..0000000 --- a/src/main/java/ru/nanit/limbo/util/VelocityUtil.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.nanit.limbo.util; - -import io.netty.buffer.ByteBuf; -import ru.nanit.limbo.configuration.LimboConfig; -import ru.nanit.limbo.protocol.ByteMessage; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.security.InvalidKeyException; -import java.security.MessageDigest; - -public final class VelocityUtil { - - private static byte[] secretKey; - - private VelocityUtil(){} - - public static void init(LimboConfig config){ - secretKey = config.getInfoForwarding().getSecretKey(); - } - - public static boolean checkIntegrity(ByteMessage buf) { - byte[] signature = new byte[32]; - buf.readBytes(signature); - byte[] data = new byte[buf.readableBytes()]; - buf.getBytes(buf.readerIndex(), data); - try { - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(secretKey, "HmacSHA256")); - byte[] mySignature = mac.doFinal(data); - if (!MessageDigest.isEqual(signature, mySignature)) - return false; - } catch (InvalidKeyException |java.security.NoSuchAlgorithmException e) { - throw new AssertionError(e); - } - int version = buf.readVarInt(); - if (version != 1) - throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + '\001'); - return true; - } - -}