Yatopia/patches/server/0068-Port-krypton.patch
2021-01-23 15:53:39 -06:00

523 lines
22 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Andrew Steinborn <git@steinborn.me>
Date: Thu, 21 Jan 2021 00:40:24 +0100
Subject: [PATCH] Port krypton
Co-authored-by: Hugo Planque <hookwood01@gmail.com>
diff --git a/src/main/java/me/steinborn/krypton/mod/shared/network/VarintByteDecoder.java b/src/main/java/me/steinborn/krypton/mod/shared/network/VarintByteDecoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..cdf5a3b1f7ec27171f3825f89cfb8d398fb3fd79
--- /dev/null
+++ b/src/main/java/me/steinborn/krypton/mod/shared/network/VarintByteDecoder.java
@@ -0,0 +1,47 @@
+package me.steinborn.krypton.mod.shared.network;
+
+import io.netty.util.ByteProcessor;
+
+public class VarintByteDecoder implements ByteProcessor {
+ private int readVarint;
+ private int bytesRead;
+ private DecodeResult result = DecodeResult.TOO_SHORT;
+
+ @Override
+ public boolean process(byte k) {
+ readVarint |= (k & 0x7F) << bytesRead++ * 7;
+ if (bytesRead > 3) {
+ result = DecodeResult.TOO_BIG;
+ return false;
+ }
+ if ((k & 0x80) != 128) {
+ result = DecodeResult.SUCCESS;
+ return false;
+ }
+ return true;
+ }
+
+ public int readVarint() {
+ return readVarint;
+ }
+
+ public int varintBytes() {
+ return bytesRead;
+ }
+
+ public DecodeResult getResult() {
+ return result;
+ }
+
+ public void reset() {
+ readVarint = 0;
+ bytesRead = 0;
+ result = DecodeResult.TOO_SHORT;
+ }
+
+ public enum DecodeResult {
+ SUCCESS,
+ TOO_SHORT,
+ TOO_BIG
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/steinborn/krypton/mod/shared/network/compression/MinecraftCompressDecoder.java b/src/main/java/me/steinborn/krypton/mod/shared/network/compression/MinecraftCompressDecoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9a51c71e136be14ebe8a240c4b21205079fc71b
--- /dev/null
+++ b/src/main/java/me/steinborn/krypton/mod/shared/network/compression/MinecraftCompressDecoder.java
@@ -0,0 +1,68 @@
+package me.steinborn.krypton.mod.shared.network.compression;
+
+import com.velocitypowered.natives.compression.VelocityCompressor;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.CorruptedFrameException;
+import io.netty.handler.codec.MessageToMessageDecoder;
+import net.minecraft.server.PacketDataSerializer;
+
+import java.util.List;
+
+import static com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible;
+import static com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer;
+
+public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
+
+ private static final int VANILLA_MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB
+ private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 16 * 1024 * 1024; // 16MiB
+
+ private static final int UNCOMPRESSED_CAP =
+ Boolean.getBoolean("velocity.increased-compression-cap")
+ ? HARD_MAXIMUM_UNCOMPRESSED_SIZE : VANILLA_MAXIMUM_UNCOMPRESSED_SIZE;
+
+ private final int threshold;
+ private final VelocityCompressor compressor;
+
+ public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor) {
+ this.threshold = threshold;
+ this.compressor = compressor;
+ }
+
+ @Override
+ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+ PacketDataSerializer wrappedBuf = new PacketDataSerializer(in);
+ int claimedUncompressedSize = wrappedBuf.readVarInt();
+ if (claimedUncompressedSize == 0) {
+ // This message is not compressed.
+ out.add(in.retainedSlice());
+ return;
+ }
+
+ if (claimedUncompressedSize < threshold) {
+ throw new CorruptedFrameException("Uncompressed size " + claimedUncompressedSize + " is less than"
+ + " threshold " + threshold);
+ }
+ if (claimedUncompressedSize > UNCOMPRESSED_CAP) {
+ throw new CorruptedFrameException("Uncompressed size " + claimedUncompressedSize + " exceeds hard " +
+ "threshold of " + UNCOMPRESSED_CAP);
+ }
+
+ ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
+ ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize);
+ try {
+ compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
+ out.add(uncompressed);
+ } catch (Exception e) {
+ uncompressed.release();
+ throw e;
+ } finally {
+ compatibleIn.release();
+ }
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ compressor.close();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/steinborn/krypton/mod/shared/network/compression/MinecraftCompressEncoder.java b/src/main/java/me/steinborn/krypton/mod/shared/network/compression/MinecraftCompressEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..1114e1db59476353ad609a519b71479438db7b0c
--- /dev/null
+++ b/src/main/java/me/steinborn/krypton/mod/shared/network/compression/MinecraftCompressEncoder.java
@@ -0,0 +1,59 @@
+package me.steinborn.krypton.mod.shared.network.compression;
+
+
+import com.velocitypowered.natives.compression.VelocityCompressor;
+import com.velocitypowered.natives.util.MoreByteBufUtils;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToByteEncoder;
+import net.minecraft.server.PacketDataSerializer;
+
+public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
+
+ private final int threshold;
+ private final VelocityCompressor compressor;
+
+ public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) {
+ this.threshold = threshold;
+ this.compressor = compressor;
+ }
+
+ @Override
+ protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
+ PacketDataSerializer wrappedBuf = new PacketDataSerializer(out);
+ int uncompressed = msg.readableBytes();
+ if (uncompressed < threshold) {
+ // Under the threshold, there is nothing to do.
+ wrappedBuf.writeVarInt(0);
+ out.writeBytes(msg);
+ } else {
+ wrappedBuf.writeVarInt(uncompressed);
+ ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg);
+ try {
+ compressor.deflate(compatibleIn, out);
+ } finally {
+ compatibleIn.release();
+ }
+ }
+ }
+
+ @Override
+ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect)
+ throws Exception {
+ // We allocate bytes to be compressed plus 1 byte. This covers two cases:
+ //
+ // - Compression
+ // According to https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103,
+ // if the data compresses well (and we do not have some pathological case) then the maximum
+ // size the compressed size will ever be is the input size minus one.
+ // - Uncompressed
+ // This is fairly obvious - we will then have one more than the uncompressed size.
+ int initialBufferSize = msg.readableBytes() + 1;
+ return MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, initialBufferSize);
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ compressor.close();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/me/steinborn/krypton/mod/shared/network/pipeline/MinecraftCipherDecoder.java b/src/main/java/me/steinborn/krypton/mod/shared/network/pipeline/MinecraftCipherDecoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..2612c350446b172629b8030602ed812fa69f24a4
--- /dev/null
+++ b/src/main/java/me/steinborn/krypton/mod/shared/network/pipeline/MinecraftCipherDecoder.java
@@ -0,0 +1,36 @@
+package me.steinborn.krypton.mod.shared.network.pipeline;
+
+import com.google.common.base.Preconditions;
+import com.velocitypowered.natives.encryption.VelocityCipher;
+import com.velocitypowered.natives.util.MoreByteBufUtils;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToMessageDecoder;
+
+import java.util.List;
+
+public class MinecraftCipherDecoder extends MessageToMessageDecoder<ByteBuf> {
+
+ private final VelocityCipher cipher;
+
+ public MinecraftCipherDecoder(VelocityCipher cipher) {
+ this.cipher = Preconditions.checkNotNull(cipher, "cipher");
+ }
+
+ @Override
+ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+ ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, in).slice();
+ try {
+ cipher.process(compatible);
+ out.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ cipher.close();
+ }
+}
diff --git a/src/main/java/me/steinborn/krypton/mod/shared/network/pipeline/MinecraftCipherEncoder.java b/src/main/java/me/steinborn/krypton/mod/shared/network/pipeline/MinecraftCipherEncoder.java
new file mode 100644
index 0000000000000000000000000000000000000000..068b0d51daf11045a5054cddd53897ecdd117a37
--- /dev/null
+++ b/src/main/java/me/steinborn/krypton/mod/shared/network/pipeline/MinecraftCipherEncoder.java
@@ -0,0 +1,36 @@
+package me.steinborn.krypton.mod.shared.network.pipeline;
+
+import com.google.common.base.Preconditions;
+import com.velocitypowered.natives.encryption.VelocityCipher;
+import com.velocitypowered.natives.util.MoreByteBufUtils;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToMessageEncoder;
+
+import java.util.List;
+
+public class MinecraftCipherEncoder extends MessageToMessageEncoder<ByteBuf> {
+
+ private final VelocityCipher cipher;
+
+ public MinecraftCipherEncoder(VelocityCipher cipher) {
+ this.cipher = Preconditions.checkNotNull(cipher, "cipher");
+ }
+
+ @Override
+ protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
+ ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, msg);
+ try {
+ cipher.process(compatible);
+ out.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+ cipher.close();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/minecraft/server/LegacyPingHandler.java b/src/main/java/net/minecraft/server/LegacyPingHandler.java
index 4a49fe4cc600e2b70963302ddae0c4479849f3f5..3abc3869b8012f060e1997822ffdb321f4884929 100644
--- a/src/main/java/net/minecraft/server/LegacyPingHandler.java
+++ b/src/main/java/net/minecraft/server/LegacyPingHandler.java
@@ -23,6 +23,11 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter {
}
public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) throws Exception {
+ // Yatopia start - New network system
+ if (!channelhandlercontext.channel().isActive()) {
+ ((ByteBuf) object).clear();
+ return;
+ } // Yatopia end
ByteBuf bytebuf = (ByteBuf) object;
// Paper start - Make legacy ping handler more reliable
diff --git a/src/main/java/net/minecraft/server/LoginListener.java b/src/main/java/net/minecraft/server/LoginListener.java
index 847122f76f6d951b24b22c86276140e02aaf37d6..174197f41bedab6a45e96323adff4f3f6238cef2 100644
--- a/src/main/java/net/minecraft/server/LoginListener.java
+++ b/src/main/java/net/minecraft/server/LoginListener.java
@@ -8,6 +8,7 @@ import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
+import java.security.GeneralSecurityException; // Yatopia
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.Random;
@@ -256,8 +257,8 @@ public class LoginListener implements PacketLoginInListener {
s = (new BigInteger(MinecraftEncryption.a("", this.server.getKeyPair().getPublic(), this.loginKey))).toString(16);
this.g = LoginListener.EnumProtocolState.AUTHENTICATING;
- this.networkManager.a(this.loginKey); // Tuinity
- } catch (CryptographyException cryptographyexception) {
+ this.networkManager.setupEncryption(this.loginKey); // Tuinity // Yatopia - New network system
+ } catch (CryptographyException | GeneralSecurityException cryptographyexception) { // Yatopia
throw new IllegalStateException("Protocol error", cryptographyexception);
}
diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java
index 08227ab446d6332af76491a063653f7f13f43560..a6890caa3c78dea773a7f71e919d5f352db02e1b 100644
--- a/src/main/java/net/minecraft/server/NetworkManager.java
+++ b/src/main/java/net/minecraft/server/NetworkManager.java
@@ -21,6 +21,17 @@ import java.net.SocketAddress;
import java.util.Queue;
import javax.annotation.Nullable;
import javax.crypto.Cipher;
+// Yatopia start
+import javax.crypto.SecretKey;
+import me.steinborn.krypton.mod.shared.network.compression.MinecraftCompressDecoder;
+import me.steinborn.krypton.mod.shared.network.compression.MinecraftCompressEncoder;
+import me.steinborn.krypton.mod.shared.network.pipeline.MinecraftCipherDecoder;
+import me.steinborn.krypton.mod.shared.network.pipeline.MinecraftCipherEncoder;
+import com.velocitypowered.natives.compression.VelocityCompressor;
+import com.velocitypowered.natives.encryption.VelocityCipher;
+import com.velocitypowered.natives.util.Natives;
+import java.security.GeneralSecurityException;
+// Yatopia end
import org.apache.commons.lang3.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -107,6 +118,17 @@ public class NetworkManager extends SimpleChannelInboundHandler<Packet<?>> {
}
// Tuinity end - allow controlled flushing
+ // Yatopia start
+ public void setupEncryption(SecretKey key) throws GeneralSecurityException {
+ VelocityCipher decryption = Natives.cipher.get().forDecryption(key);
+ VelocityCipher encryption = Natives.cipher.get().forEncryption(key);
+
+ this.n = true;
+ this.channel.pipeline().addBefore("splitter", "decrypt", new MinecraftCipherDecoder(decryption));
+ this.channel.pipeline().addBefore("prepender", "encrypt", new MinecraftCipherEncoder(encryption));
+ }
+ // Yatopia end
+
public NetworkManager(EnumProtocolDirection enumprotocoldirection) {
this.h = enumprotocoldirection;
}
diff --git a/src/main/java/net/minecraft/server/PacketDataSerializer.java b/src/main/java/net/minecraft/server/PacketDataSerializer.java
index f43193c1090238f2241b878120247d1b3d0d4e57..7dc31ee3211a895993c522a7155a0d8641fd442c 100644
--- a/src/main/java/net/minecraft/server/PacketDataSerializer.java
+++ b/src/main/java/net/minecraft/server/PacketDataSerializer.java
@@ -7,6 +7,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
+import io.netty.buffer.ByteBufUtil; // Yatopia
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.EncoderException;
import io.netty.util.ByteProcessor;
@@ -30,7 +31,7 @@ import org.bukkit.craftbukkit.inventory.CraftItemStack; // CraftBukkit
public class PacketDataSerializer extends ByteBuf {
- private final ByteBuf a;
+ private final ByteBuf a; private final ByteBuf getParent() { return a; } // Yatopia - OBFHELPER
public PacketDataSerializer(ByteBuf bytebuf) {
this.a = bytebuf;
@@ -210,6 +211,7 @@ public class PacketDataSerializer extends ByteBuf {
return new UUID(this.readLong(), this.readLong());
}
+ public PacketDataSerializer writeVarInt(int i){ return d(i); } // Yatopia - OBFHELPER
public PacketDataSerializer d(int i) {
while ((i & -128) != 0) {
this.writeByte(i & 127 | 128);
@@ -358,6 +360,8 @@ public class PacketDataSerializer extends ByteBuf {
}
public PacketDataSerializer a(String s, int i) {
+ // Yatopia start - New network system
+ /*
byte[] abyte = s.getBytes(StandardCharsets.UTF_8);
if (abyte.length > i) {
@@ -367,6 +371,16 @@ public class PacketDataSerializer extends ByteBuf {
this.writeBytes(abyte);
return this;
}
+ */
+ int utf8Bytes = ByteBufUtil.utf8Bytes(s);
+ if (utf8Bytes > i) {
+ throw new EncoderException("String too big (was " + utf8Bytes + " bytes encoded, max " + i + ")");
+ } else {
+ this.writeVarInt(utf8Bytes);
+ this.writeCharSequence(s, StandardCharsets.UTF_8);
+ return new PacketDataSerializer(getParent());
+ }
+ // Yatopia end
}
public MinecraftKey p() {
diff --git a/src/main/java/net/minecraft/server/PacketSplitter.java b/src/main/java/net/minecraft/server/PacketSplitter.java
index 2aaa8770edfd8acc6861c23176e405863858b275..b1e1aa95b5d7a1555428327f2e7fd0eafc679dc1 100644
--- a/src/main/java/net/minecraft/server/PacketSplitter.java
+++ b/src/main/java/net/minecraft/server/PacketSplitter.java
@@ -5,14 +5,20 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
+// Yatopia start
+import io.netty.handler.codec.DecoderException;
+import me.steinborn.krypton.mod.shared.network.VarintByteDecoder;
+// Yatopia end
import java.util.List;
public class PacketSplitter extends ByteToMessageDecoder {
+ private final VarintByteDecoder reader = new VarintByteDecoder(); // Yatopia
private final byte[] lenBuf = new byte[3]; // Paper
public PacketSplitter() {}
protected void decode(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf, List<Object> list) throws Exception {
+ /* // Yatopia start - New network system
// Paper start - if channel is not active just discard the packet
if (!channelhandlercontext.channel().isActive()) {
bytebuf.skipBytes(bytebuf.readableBytes());
@@ -53,5 +59,38 @@ public class PacketSplitter extends ByteToMessageDecoder {
}
throw new CorruptedFrameException("length wider than 21-bit");
+ */
+ if (!channelhandlercontext.channel().isActive()) {
+ bytebuf.clear();
+ return;
+ }
+
+ reader.reset();
+
+ int varintEnd = bytebuf.forEachByte(reader);
+ if (varintEnd == -1) {
+ // We tried to go beyond the end of the buffer. This is probably a good sign that the
+ // buffer was too short to hold a proper varint.
+ return;
+ }
+
+ if (reader.getResult() == VarintByteDecoder.DecodeResult.SUCCESS) {
+ int readLen = reader.readVarint();
+ if (readLen < 0) {
+ throw new DecoderException("Bad packet length");
+ } else if (readLen == 0) {
+ // skip over the empty packet and ignore it
+ bytebuf.readerIndex(varintEnd + 1);
+ } else {
+ int minimumRead = reader.varintBytes() + readLen;
+ if (bytebuf.isReadable(minimumRead)) {
+ list.add(bytebuf.retainedSlice(varintEnd + 1, readLen));
+ bytebuf.skipBytes(minimumRead);
+ }
+ }
+ } else if (reader.getResult() == VarintByteDecoder.DecodeResult.TOO_BIG) {
+ throw new DecoderException("Varint too big");
+ }
+ // Yatopia end
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index 756be0886856aabc31e436f82948d3d069f66fef..c1dd04b57e8ffeb343b0efdec36d6fb36c16884f 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -10,6 +10,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
+import io.netty.util.ResourceLeakDetector; // Yatopia
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper
@@ -289,6 +290,24 @@ public class Main {
// Paper End
}
}
+ // Yatopia start - New network system
+ // By default, Netty allocates 16MiB arenas for the PooledByteBufAllocator. This is too much
+ // memory for Minecraft, which imposes a maximum packet size of 2MiB! We'll use 4MiB as a more
+ // sane default.
+ //
+ // Note: io.netty.allocator.pageSize << io.netty.allocator.maxOrder is the formula used to
+ // compute the chunk size. We lower maxOrder from its default of 11 to 9. (We also use a null
+ // check, so that the user is free to choose another setting if need be.)
+ if (System.getProperty("io.netty.allocator.maxOrder") == null) {
+ System.setProperty("io.netty.allocator.maxOrder", "9");
+ }
+
+ // Disable the resource leak detector by default as it reduces performance. Allow the user to
+ // override this if desired.
+ if (System.getProperty("io.netty.leakDetection.level") == null) {
+ ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED);
+ }
+ // Yatopia end
// Paper start - Log Java and OS versioning to help with debugging plugin issues
java.lang.management.RuntimeMXBean runtimeMX = java.lang.management.ManagementFactory.getRuntimeMXBean();