Files
paper-mc/paper-server/patches/features/0005-Use-Velocity-compression-and-cipher-natives.patch
Owen 7f60924390 Configuration API (#12301)
This implements a solution that preemptively exits the tick loop if we have already marked the connection as disconnecting. This avoids changing the result of Connection#isConnected in order to avoid other possibly unintentional changes. Fundamentally it should be investigated if closing the connection async is really still needed.

This also additionally removes the login disconnecting logic for server stopping, as this was also a possible issue in the config stage but also shouldn't be an issue as connections are closed on server stop very early.

Additionally, do not check for isConnecting() on VERIFYING, as that seemed to be an old diff originally trying to resolve this code, however isConnected is not updated at this point so it pretty much was useless.
2025-06-30 11:49:15 +02:00

355 lines
18 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Andrew Steinborn <git@steinborn.me>
Date: Mon, 26 Jul 2021 02:15:17 -0400
Subject: [PATCH] Use Velocity compression and cipher natives
diff --git a/net/minecraft/network/CipherDecoder.java b/net/minecraft/network/CipherDecoder.java
index 429325ffb7db2b85ed271ddf3da64c6fdc593673..d764552aea825af7bbf0f47c9d88af527d9dbe62 100644
--- a/net/minecraft/network/CipherDecoder.java
+++ b/net/minecraft/network/CipherDecoder.java
@@ -7,14 +7,30 @@ import java.util.List;
import javax.crypto.Cipher;
public class CipherDecoder extends MessageToMessageDecoder<ByteBuf> {
- private final CipherBase cipher;
+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - Use Velocity cipher
- public CipherDecoder(Cipher cipher) {
- this.cipher = new CipherBase(cipher);
+ public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher
+ this.cipher = cipher; // Paper - Use Velocity cipher
}
@Override
protected void decode(ChannelHandlerContext context, ByteBuf in, List<Object> out) throws Exception {
- out.add(this.cipher.decipher(context, in));
+ // Paper start - Use Velocity cipher
+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(context.alloc(), this.cipher, in);
+ try {
+ this.cipher.process(compatible);
+ out.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
+ // Paper end - Use Velocity cipher
}
+
+ // Paper start - Use Velocity cipher
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) {
+ this.cipher.close();
+ }
+ // Paper end - Use Velocity cipher
}
diff --git a/net/minecraft/network/CipherEncoder.java b/net/minecraft/network/CipherEncoder.java
index 992b9c7aed57ce29cdd2b4f66737d39db214f0cf..b927c85923147d2b346e892a3e4deee48b3d073e 100644
--- a/net/minecraft/network/CipherEncoder.java
+++ b/net/minecraft/network/CipherEncoder.java
@@ -5,15 +5,31 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import javax.crypto.Cipher;
-public class CipherEncoder extends MessageToByteEncoder<ByteBuf> {
- private final CipherBase cipher;
+public class CipherEncoder extends io.netty.handler.codec.MessageToMessageEncoder<ByteBuf> { // Paper - Use Velocity cipher; change superclass
+ private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - Use Velocity cipher
- public CipherEncoder(Cipher cipher) {
- this.cipher = new CipherBase(cipher);
+ public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher
+ this.cipher = cipher; // Paper - Use Velocity cipher
}
+ // Paper start - Use Velocity cipher
@Override
- protected void encode(ChannelHandlerContext context, ByteBuf message, ByteBuf out) throws Exception {
- this.cipher.encipher(message, out);
+ protected void encode(ChannelHandlerContext context, ByteBuf message, java.util.List<Object> list) throws Exception {
+ ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(context.alloc(), this.cipher, message);
+ try {
+ this.cipher.process(compatible);
+ list.add(compatible);
+ } catch (Exception e) {
+ compatible.release(); // compatible will never be used if we throw an exception
+ throw e;
+ }
+ // Paper end - Use Velocity cipher
}
+
+ // Paper start - Use Velocity cipher
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) {
+ this.cipher.close();
+ }
+ // Paper end - Use Velocity cipher
}
diff --git a/net/minecraft/network/CompressionDecoder.java b/net/minecraft/network/CompressionDecoder.java
index fcf0e557fbcbd5f306625096d859578fe8511734..2c7f935fcecb24a4394fdde523219a5b5984a673 100644
--- a/net/minecraft/network/CompressionDecoder.java
+++ b/net/minecraft/network/CompressionDecoder.java
@@ -12,14 +12,22 @@ import java.util.zip.Inflater;
public class CompressionDecoder extends ByteToMessageDecoder {
public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152;
public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608;
+ private com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher
private Inflater inflater;
private int threshold;
private boolean validateDecompressed;
+ // Paper start - Use Velocity cipher
+ @io.papermc.paper.annotation.DoNotUse
public CompressionDecoder(int threshold, boolean validateDecompressed) {
+ this(null, threshold, validateDecompressed);
+ }
+ public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int threshold, boolean validateDecompressed) {
this.threshold = threshold;
this.validateDecompressed = validateDecompressed;
- this.inflater = new Inflater();
+ this.inflater = compressor == null ? new Inflater() : null;
+ this.compressor = compressor;
+ // Paper end - Use Velocity cipher
}
@Override
@@ -39,14 +47,42 @@ public class CompressionDecoder extends ByteToMessageDecoder {
}
}
+ if (inflater != null) { // Paper - Use Velocity cipher; fallback to vanilla inflater
this.setupInflaterInput(in);
ByteBuf byteBuf = this.inflate(context, i);
this.inflater.reset();
out.add(byteBuf);
+ return; // Paper - Use Velocity cipher
+ } // Paper - use velocity compression
+
+ // Paper start - Use Velocity cipher
+ int claimedUncompressedSize = i; // OBFHELPER
+ ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(context.alloc(), this.compressor, in);
+ ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(context.alloc(), this.compressor, claimedUncompressedSize);
+ try {
+ this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
+ out.add(uncompressed);
+ in.clear();
+ } catch (Exception e) {
+ uncompressed.release();
+ throw e;
+ } finally {
+ compatibleIn.release();
+ }
+ // Paper end - Use Velocity cipher
}
}
}
+ // Paper start - Use Velocity cipher
+ @Override
+ public void handlerRemoved0(ChannelHandlerContext ctx) {
+ if (this.compressor != null) {
+ this.compressor.close();
+ }
+ }
+ // Paper end - Use Velocity cipher
+
private void setupInflaterInput(ByteBuf buffer) {
ByteBuffer byteBuffer;
if (buffer.nioBufferCount() > 0) {
@@ -81,7 +117,13 @@ public class CompressionDecoder extends ByteToMessageDecoder {
}
}
- public void setThreshold(int threshold, boolean validateDecompressed) {
+ // Paper start - Use Velocity cipher
+ public void setThreshold(com.velocitypowered.natives.compression.VelocityCompressor compressor, int threshold, boolean validateDecompressed) {
+ if (this.compressor == null && compressor != null) { // Only re-configure once. Re-reconfiguring would require closing the native compressor.
+ this.compressor = compressor;
+ this.inflater = null;
+ }
+ // Paper end - Use Velocity cipher
this.threshold = threshold;
this.validateDecompressed = validateDecompressed;
}
diff --git a/net/minecraft/network/CompressionEncoder.java b/net/minecraft/network/CompressionEncoder.java
index bc674b08a41d5529fe06c6d3f077051cf4138f73..ea8a894158c44c2e7943dea43ecd8e1f0075b18f 100644
--- a/net/minecraft/network/CompressionEncoder.java
+++ b/net/minecraft/network/CompressionEncoder.java
@@ -6,17 +6,31 @@ import io.netty.handler.codec.MessageToByteEncoder;
import java.util.zip.Deflater;
public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
- private final byte[] encodeBuf = new byte[8192];
+ @javax.annotation.Nullable private final byte[] encodeBuf; // Paper - Use Velocity cipher
+ @javax.annotation.Nullable // Paper - Use Velocity cipher
private final Deflater deflater;
+ @javax.annotation.Nullable private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher
private int threshold;
+ // Paper start - Use Velocity cipher
public CompressionEncoder(int threshold) {
+ this(null, threshold);
+ }
+ public CompressionEncoder(@javax.annotation.Nullable com.velocitypowered.natives.compression.VelocityCompressor compressor, int threshold) {
this.threshold = threshold;
- this.deflater = new Deflater();
+ if (compressor == null) {
+ this.encodeBuf = new byte[8192];
+ this.deflater = new Deflater();
+ } else {
+ this.encodeBuf = null;
+ this.deflater = null;
+ }
+ this.compressor = compressor;
+ // Paper end - Use Velocity cipher
}
@Override
- protected void encode(ChannelHandlerContext context, ByteBuf encodingByteBuf, ByteBuf byteBuf) {
+ protected void encode(ChannelHandlerContext context, ByteBuf encodingByteBuf, ByteBuf byteBuf) throws Exception { // Paper - Use Velocity cipher
int i = encodingByteBuf.readableBytes();
if (i > 8388608) {
throw new IllegalArgumentException("Packet too big (is " + i + ", should be less than 8388608)");
@@ -25,6 +39,7 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
VarInt.write(byteBuf, 0);
byteBuf.writeBytes(encodingByteBuf);
} else {
+ if (this.deflater != null) { // Paper - Use Velocity cipher
byte[] bytes = new byte[i];
encodingByteBuf.readBytes(bytes);
VarInt.write(byteBuf, bytes.length);
@@ -37,6 +52,17 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
}
this.deflater.reset();
+ // Paper start - Use Velocity cipher
+ return;
+ }
+
+ VarInt.write(byteBuf, i);
+ final ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(context.alloc(), this.compressor, encodingByteBuf);
+ try {
+ this.compressor.deflate(compatibleIn, byteBuf);
+ } finally {
+ compatibleIn.release();
+ }
}
}
}
@@ -48,4 +74,31 @@ public class CompressionEncoder extends MessageToByteEncoder<ByteBuf> {
public void setThreshold(int threshold) {
this.threshold = threshold;
}
+
+ // Paper start - Use Velocity cipher
+ @Override
+ protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception {
+ if (this.compressor != null) {
+ // 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.
+ final int initialBufferSize = msg.readableBytes() + 1;
+ return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, initialBufferSize);
+ }
+
+ return super.allocateBuffer(ctx, msg, preferDirect);
+ }
+
+ @Override
+ public void handlerRemoved(ChannelHandlerContext ctx) {
+ if (this.compressor != null) {
+ this.compressor.close();
+ }
+ }
+ // Paper end - Use Velocity cipher
}
diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 682fb4cbf9975b21171ae210defd6e338de3b718..8bab2c26e10e8495fd39be470bcb02917fe56f40 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -763,11 +763,22 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
return connection;
}
- public void setEncryptionKey(Cipher decryptingCipher, Cipher encryptingCipher) {
- this.encrypted = true;
- this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptingCipher));
- this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptingCipher));
+ // Paper start - Use Velocity cipher
+ public void setEncryptionKey(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException {
+ if (!this.encrypted) {
+ try {
+ com.velocitypowered.natives.encryption.VelocityCipher decryptionCipher = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key);
+ com.velocitypowered.natives.encryption.VelocityCipher encryptionCipher = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key);
+
+ this.encrypted = true;
+ this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher));
+ this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher));
+ } catch (java.security.GeneralSecurityException e) {
+ throw new net.minecraft.util.CryptException(e);
+ }
+ }
}
+ // Paper end - Use Velocity cipher
public boolean isEncrypted() {
return this.encrypted;
@@ -806,16 +817,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
// Paper end - add proper async disconnect
public void setupCompression(int threshold, boolean validateDecompressed) {
if (threshold >= 0) {
+ com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(io.papermc.paper.configuration.GlobalConfiguration.get().misc.compressionLevel.or(-1)); // Paper - Use Velocity cipher
if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder compressionDecoder) {
- compressionDecoder.setThreshold(threshold, validateDecompressed);
+ compressionDecoder.setThreshold(compressor, threshold, validateDecompressed); // Paper - Use Velocity cipher
} else {
- this.channel.pipeline().addAfter("splitter", "decompress", new CompressionDecoder(threshold, validateDecompressed));
+ this.channel.pipeline().addAfter("splitter", "decompress", new CompressionDecoder(compressor, threshold, validateDecompressed)); // Paper - Use Velocity cipher
}
if (this.channel.pipeline().get("compress") instanceof CompressionEncoder compressionEncoder) {
compressionEncoder.setThreshold(threshold);
} else {
- this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(threshold));
+ this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressor, threshold)); // Paper - Use Velocity cipher
}
this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
} else {
diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java
index 7de11ba404f0b60e7f7b7c16954811a343688219..bd07e6a5aa1883786d789ea71711a0c0c0a95c26 100644
--- a/net/minecraft/server/network/ServerConnectionListener.java
+++ b/net/minecraft/server/network/ServerConnectionListener.java
@@ -108,6 +108,10 @@ public class ServerConnectionListener {
LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled.");
}
// Paper end - Warn people with console access that HAProxy is in use.
+ // Paper start - Use Velocity cipher
+ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity.");
+ ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity.");
+ // Paper end - Use Velocity cipher
this.channels
.add(
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index 0b0e57cc534862e265108f0e9cbb324ffbc2bcd1..ebf4d4516233c002b33084f1679044b23869d848 100644
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -242,11 +242,9 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
}
SecretKey secretKey = packet.getSecretKey(_private);
- Cipher cipher = Crypt.getCipher(2, secretKey);
- Cipher cipher1 = Crypt.getCipher(1, secretKey);
string = new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretKey)).toString(16);
this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING;
- this.connection.setEncryptionKey(cipher, cipher1);
+ this.connection.setEncryptionKey(secretKey); // Paper - Use Velocity cipher
} catch (CryptException var7) {
throw new IllegalStateException("Protocol error", var7);
}