From 80d41a1e52de99ccfac9d98bd64c0176caac13e1 Mon Sep 17 00:00:00 2001 From: xIsm4 Date: Fri, 27 Jan 2023 14:12:44 +0100 Subject: [PATCH] Implement libdeflate diff --git a/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java b/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java index 05f7da1d4..1ef7f6a72 100644 --- a/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java +++ b/flamecord/src/main/java/dev/_2lstudios/flamecord/configuration/FlameCordConfiguration.java @@ -60,6 +60,8 @@ public class FlameCordConfiguration extends FlameConfig { @Getter private int antibotFirewallExpire = 60; @Getter + private int compressionLevel = 6; + @Getter private boolean antibotFirewallLog = true; @Getter private boolean antibotFirewallIpset = true; @@ -318,6 +320,7 @@ public class FlameCordConfiguration extends FlameConfig { this.fakePlayersMode = setIfUnexistant("custom-motd.fakeplayers.mode", this.fakePlayersMode, configuration); this.tcpFastOpen = setIfUnexistant("tcp-fast-open", this.tcpFastOpen, configuration); + this.compressionLevel = setIfUnexistant("compression-level", this.compressionLevel, configuration); this.loggerInitialhandler = setIfUnexistant("logger.initialhandler", this.loggerInitialhandler, configuration); this.loggerExceptions = setIfUnexistant("logger.exceptions", this.loggerExceptions, configuration); diff --git a/native/compile-linux.sh b/native/compile-linux.sh new file mode 100644 index 000000000..5442dce9d --- /dev/null +++ b/native/compile-linux.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +if [ ! "$CC" ]; then + # The libdeflate authors recommend that we build using GCC as it produces "slightly faster binaries": + # https://github.com/ebiggers/libdeflate#for-unix + export CC=gcc +fi + +if [ ! -d libdeflate ]; then + echo "Cloning libdeflate..." + git clone https://github.com/ebiggers/libdeflate.git +fi + +echo "Compiling libdeflate..." +cd libdeflate || exit +CFLAGS="-fPIC -O2 -fomit-frame-pointer" make +cd .. + +CFLAGS="-O2 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack -Wall -Werror -fomit-frame-pointer" +ARCH=$(uname -m) +mkdir -p src/main/resources/linux_$ARCH +$CC $CFLAGS -Ilibdeflate src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \ + libdeflate/libdeflate.a -o src/main/resources/linux_$ARCH/velocity-compress.so +$CC $CFLAGS -shared src/main/c/jni_util.c src/main/c/jni_cipher_openssl.c \ + -o src/main/resources/linux_$ARCH/velocity-cipher.so -lcrypto \ No newline at end of file diff --git a/native/src/main/c/jni_cipher_macos.c b/native/src/main/c/jni_cipher_macos.c new file mode 100644 index 000000000..aa7d1aba3 --- /dev/null +++ b/native/src/main/c/jni_cipher_macos.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include "jni_util.h" + +typedef unsigned char byte; + +JNIEXPORT jlong JNICALL +Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env, + jclass clazz, + jbyteArray key, + jboolean encrypt) +{ + jsize keyLen = (*env)->GetArrayLength(env, key); + if (keyLen != 16) { + throwException(env, "java/lang/IllegalArgumentException", "cipher not 16 bytes"); + return 0; + } + + // Since we know the array size is always bounded, we can just use GetArrayRegion + // and save ourselves some error-checking headaches. + jbyte keyBytes[16]; + (*env)->GetByteArrayRegion(env, key, 0, keyLen, (jbyte*) keyBytes); + if ((*env)->ExceptionCheck(env)) { + return 0; + } + + CCCryptorRef cryptor = NULL; + CCCryptorStatus result = CCCryptorCreateWithMode(encrypt ? kCCEncrypt : kCCDecrypt, + kCCModeCFB8, + kCCAlgorithmAES128, + ccNoPadding, + keyBytes, + keyBytes, + 16, + NULL, + 0, + 0, + 0, + &cryptor); + if (result != kCCSuccess) { + throwException(env, "java/security/GeneralSecurityException", "openssl initialize cipher"); + return 0; + } + return (jlong) cryptor; +} + +JNIEXPORT void JNICALL +Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_free(JNIEnv *env, + jclass clazz, + jlong ptr) +{ + CCCryptorRelease((CCCryptorRef) ptr); +} + +JNIEXPORT void JNICALL +Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_process(JNIEnv *env, + jclass clazz, + jlong ptr, + jlong source, + jint len, + jlong dest) +{ + CCCryptorUpdate((CCCryptorRef) ptr, (byte*) source, len, (byte*) dest, len, NULL); +} \ No newline at end of file diff --git a/native/src/main/c/jni_cipher_openssl.c b/native/src/main/c/jni_cipher_openssl.c new file mode 100644 index 000000000..83515be52 --- /dev/null +++ b/native/src/main/c/jni_cipher_openssl.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include "jni_util.h" + +typedef unsigned char byte; + +JNIEXPORT jlong JNICALL +Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_init(JNIEnv *env, + jclass clazz, + jbyteArray key, + jboolean encrypt) +{ + jsize keyLen = (*env)->GetArrayLength(env, key); + if (keyLen != 16) { + throwException(env, "java/lang/IllegalArgumentException", "cipher not 16 bytes"); + return 0; + } + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + throwException(env, "java/lang/OutOfMemoryError", "allocate cipher"); + return 0; + } + + // Since we know the array size is always bounded, we can just use GetArrayRegion + // and save ourselves some error-checking headaches. + jbyte keyBytes[16]; + (*env)->GetByteArrayRegion(env, key, 0, keyLen, (jbyte*) keyBytes); + if ((*env)->ExceptionCheck(env)) { + return 0; + } + + int result = EVP_CipherInit(ctx, EVP_aes_128_cfb8(), (byte*) keyBytes, (byte*) keyBytes, + encrypt); + if (result != 1) { + EVP_CIPHER_CTX_free(ctx); + throwException(env, "java/security/GeneralSecurityException", "openssl initialize cipher"); + return 0; + } + return (jlong) ctx; +} + +JNIEXPORT void JNICALL +Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_free(JNIEnv *env, + jclass clazz, + jlong ptr) +{ + EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *) ptr); +} + +JNIEXPORT void JNICALL +Java_com_velocitypowered_natives_encryption_OpenSslCipherImpl_process(JNIEnv *env, + jclass clazz, + jlong ptr, + jlong source, + jint len, + jlong dest) +{ + EVP_CipherUpdate((EVP_CIPHER_CTX*) ptr, (byte*) dest, &len, (byte*) source, len); +} \ No newline at end of file diff --git a/native/src/main/c/jni_util.c b/native/src/main/c/jni_util.c new file mode 100644 index 000000000..1e2b6bd8c --- /dev/null +++ b/native/src/main/c/jni_util.c @@ -0,0 +1,12 @@ +#include +#include "jni_util.h" + +void JNICALL +throwException(JNIEnv *env, const char *type, const char *msg) +{ + jclass klazz = (*env)->FindClass(env, type); + + if (klazz != 0) { + (*env)->ThrowNew(env, klazz, msg); + } +} \ No newline at end of file diff --git a/native/src/main/c/jni_util.h b/native/src/main/c/jni_util.h new file mode 100644 index 000000000..8938b26c8 --- /dev/null +++ b/native/src/main/c/jni_util.h @@ -0,0 +1,4 @@ +#include + +JNIEXPORT void JNICALL +throwException(JNIEnv *env, const char *type, const char *msg); \ No newline at end of file diff --git a/native/src/main/c/jni_zlib_deflate.c b/native/src/main/c/jni_zlib_deflate.c new file mode 100644 index 000000000..809a7f857 --- /dev/null +++ b/native/src/main/c/jni_zlib_deflate.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include +#include "jni_util.h" + +JNIEXPORT jlong JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env, + jclass clazz, + jint level) +{ + struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level); + if (compressor == NULL) { + // Out of memory! + throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate compressor"); + return 0; + } + return (jlong) compressor; +} + +JNIEXPORT void JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibDeflate_free(JNIEnv *env, + jclass clazz, + jlong ctx) +{ + libdeflate_free_compressor((struct libdeflate_compressor *) ctx); +} + +JNIEXPORT jlong JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env, + jclass clazz, + jlong ctx, + jlong sourceAddress, + jint sourceLength, + jlong destinationAddress, + jint destinationLength) +{ + struct libdeflate_compressor *compressor = (struct libdeflate_compressor *) ctx; + size_t produced = libdeflate_zlib_compress(compressor, (void *) sourceAddress, sourceLength, + (void *) destinationAddress, destinationLength); + return (jlong) produced; +} \ No newline at end of file diff --git a/native/src/main/c/jni_zlib_inflate.c b/native/src/main/c/jni_zlib_inflate.c new file mode 100644 index 000000000..d91319089 --- /dev/null +++ b/native/src/main/c/jni_zlib_inflate.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include "jni_util.h" + +JNIEXPORT jlong JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env, + jclass clazz) +{ + struct libdeflate_decompressor *decompress = libdeflate_alloc_decompressor(); + if (decompress == NULL) { + // Out of memory! + throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate decompressor"); + return 0; + } + + return (jlong) decompress; +} + +JNIEXPORT void JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibInflate_free(JNIEnv *env, + jclass clazz, + jlong ctx) +{ + libdeflate_free_decompressor((struct libdeflate_decompressor *) ctx); +} + +JNIEXPORT jboolean JNICALL +Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env, + jclass clazz, + jlong ctx, + jlong sourceAddress, + jint sourceLength, + jlong destinationAddress, + jint destinationLength, + jlong maximumSize) +{ + struct libdeflate_decompressor *decompress = (struct libdeflate_decompressor *) ctx; + enum libdeflate_result result = libdeflate_zlib_decompress(decompress, (void *) sourceAddress, + sourceLength, (void *) destinationAddress, destinationLength, NULL); + + switch (result) { + case LIBDEFLATE_SUCCESS: + // We are happy + return JNI_TRUE; + case LIBDEFLATE_BAD_DATA: + throwException(env, "java/util/zip/DataFormatException", "inflate data is bad"); + return JNI_FALSE; + case LIBDEFLATE_SHORT_OUTPUT: + case LIBDEFLATE_INSUFFICIENT_SPACE: + // These cases are the same for us. We expect the full uncompressed size to be known. + throwException(env, "java/util/zip/DataFormatException", "uncompressed size is inaccurate"); + return JNI_FALSE; + default: + // Unhandled case + throwException(env, "java/util/zip/DataFormatException", "unknown libdeflate return code"); + return JNI_FALSE; + } +} \ No newline at end of file diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java new file mode 100644 index 000000000..4ba41fc11 --- /dev/null +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibDeflate.java @@ -0,0 +1,15 @@ +package com.velocitypowered.natives.compression; + +/** + * Represents a native interface for zlib's deflate functions. + */ + +//TODO Need refactor and recompile native! +public class NativeZlibDeflate { + + public static native long init(int level); + + public static native long free(long ctx); + + public static native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength); +} diff --git a/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java new file mode 100644 index 000000000..81d92e75b --- /dev/null +++ b/native/src/main/java/com/velocitypowered/natives/compression/NativeZlibInflate.java @@ -0,0 +1,17 @@ +package com.velocitypowered.natives.compression; + +import java.util.zip.DataFormatException; + +/** + * Represents a native interface for zlib's inflate functions. + */ + +//TODO Need refactor and recompile native! +public class NativeZlibInflate { + + public static native long init(); + + public static native long free(long ctx); + + public static native boolean process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength) throws DataFormatException; +} diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/MoreByteBufUtils.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/MoreByteBufUtils.java new file mode 100644 index 000000000..a16a25844 --- /dev/null +++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/MoreByteBufUtils.java @@ -0,0 +1,28 @@ +package dev._2lstudios.flamecord.natives; + +import dev._2lstudios.flamecord.natives.compress.Compressor; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; + +public class MoreByteBufUtils { + + public static ByteBuf ensureCompatible(ByteBufAllocator alloc, Compressor nativeStuff, ByteBuf buf) { + if (isCompatible(nativeStuff, buf)) { + return buf.retain(); + } + + // It's not, so we must make a direct copy. + ByteBuf newBuf = alloc.directBuffer(buf.readableBytes()); + newBuf.writeBytes(buf); + return newBuf; + } + + private static boolean isCompatible(Compressor nativeStuff, ByteBuf buf) { + if (nativeStuff.isNeedDirectBuffer()) { + return buf.hasMemoryAddress(); + } + + return true; + } + +} \ No newline at end of file diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/NativeEnvironmentDetector.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/NativeEnvironmentDetector.java new file mode 100644 index 000000000..50f042bdf --- /dev/null +++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/NativeEnvironmentDetector.java @@ -0,0 +1,36 @@ +package dev._2lstudios.flamecord.natives; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +public final class NativeEnvironmentDetector { + + private static final boolean IS_AMD64; + private static final boolean IS_AARCH64; + private static final boolean CAN_GET_MEMORYADDRESS; + + static { + ByteBuf test = Unpooled.directBuffer(); + try { + CAN_GET_MEMORYADDRESS = test.hasMemoryAddress(); + } + finally { + test.release(); + } + + String osArch = System.getProperty("os.arch", ""); + // HotSpot on Intel macOS prefers x86_64, but OpenJ9 on macOS and HotSpot/OpenJ9 elsewhere + // give amd64. + IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64"); + IS_AARCH64 = osArch.equals("aarch64") || osArch.equals("arm64"); + } + + public static boolean isLinux_X86_64() { + return CAN_GET_MEMORYADDRESS && System.getProperty("os.name", "").equalsIgnoreCase("Linux") && IS_AMD64; + } + + public static boolean isLinux_AARCH64() { + return CAN_GET_MEMORYADDRESS && System.getProperty("os.name", "").equalsIgnoreCase("Linux") && IS_AARCH64; + } + +} \ No newline at end of file diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/Natives.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/Natives.java new file mode 100644 index 000000000..a6709a869 --- /dev/null +++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/Natives.java @@ -0,0 +1,138 @@ +package dev._2lstudios.flamecord.natives; + +import dev._2lstudios.flamecord.natives.compress.Compressor; +import dev._2lstudios.flamecord.natives.compress.CompressorFactory; +import dev._2lstudios.flamecord.natives.compress.JavaCompressor; +import dev._2lstudios.flamecord.natives.compress.LibdeflateCompressor; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.List; + +public class Natives { + + private static final CompressorFactory COMPRESSOR_FACTORY = loadAvailableCompressFactory(); + + public static CompressorFactory getCompressorFactory() { + return COMPRESSOR_FACTORY; + } + + public static List getAvailableCompressorFactories() { + return Arrays.asList(new CompressorFactory() { + @Override + public String getName() { + return "Libdeflate (linux_x86_64)"; + } + + @Override + public boolean isCorrectEnvironment() { + return NativeEnvironmentDetector.isLinux_X86_64(); + } + + @Override + public String getNativePath() { + return "/libdeflate_x86_64.so"; + } + + @Override + public Compressor create(int level) { + return new LibdeflateCompressor(level); + } + }, new CompressorFactory() { + @Override + public String getName() { + return "Libdeflate (linux_aarch64)"; + } + + @Override + public boolean isCorrectEnvironment() { + return NativeEnvironmentDetector.isLinux_AARCH64(); + } + + @Override + public String getNativePath() { + return "/libdeflate_aarch64.so"; + } + + @Override + public Compressor create(int level) { + return new LibdeflateCompressor(level); + } + }, new CompressorFactory() { + @Override + public String getName() { + return "Java"; + } + + @Override + public boolean isCorrectEnvironment() { + return true; + } + + @Override + public String getNativePath() { + return null; + } + + @Override + public Compressor create(int level) { + return new JavaCompressor(level); + } + }); + } + + public static CompressorFactory loadAvailableCompressFactory() { + for (CompressorFactory factory : getAvailableCompressorFactories()) { + if (factory.isCorrectEnvironment()) { + String nativePath = factory.getNativePath(); + if (nativePath != null) { + try { + copyAndLoadNative(nativePath); + } + catch (Exception ignored) { + continue; + } + } + + return factory; + } + } + + throw new IllegalStateException("None of the compress factories recognized the environment!"); + } + + //Too good method to rewrite + private static void copyAndLoadNative(String path) { + try { + InputStream nativeLib = Natives.class.getResourceAsStream(path); + if (nativeLib == null) { + throw new IllegalStateException("Native library " + path + " not found."); + } + + Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.'))); + Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + Files.deleteIfExists(tempFile); + } + catch (IOException ignored) { + // Well, it doesn't matter... + } + })); + + try { + System.load(tempFile.toAbsolutePath().toString()); + } + catch (UnsatisfiedLinkError e) { + throw new RuntimeException("Unable to load native " + tempFile.toAbsolutePath(), e); + } + } + catch (IOException e) { + throw new RuntimeException("Unable to copy natives", e); + } + } +} \ No newline at end of file diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/Compressor.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/Compressor.java new file mode 100644 index 000000000..f28ae3145 --- /dev/null +++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/Compressor.java @@ -0,0 +1,16 @@ +package dev._2lstudios.flamecord.natives.compress; + +import io.netty.buffer.ByteBuf; + +import java.io.Closeable; +import java.util.zip.DataFormatException; + +public interface Compressor extends Closeable { + + void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) throws DataFormatException; + + void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; + + boolean isNeedDirectBuffer(); + +} \ No newline at end of file diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/CompressorFactory.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/CompressorFactory.java new file mode 100644 index 000000000..3aed690ad --- /dev/null +++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/CompressorFactory.java @@ -0,0 +1,13 @@ +package dev._2lstudios.flamecord.natives.compress; + +public interface CompressorFactory { + + String getName(); + + boolean isCorrectEnvironment(); + + String getNativePath(); + + Compressor create(int level); + +} \ No newline at end of file diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/JavaCompressor.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/JavaCompressor.java new file mode 100644 index 000000000..7d99eb02b --- /dev/null +++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/JavaCompressor.java @@ -0,0 +1,90 @@ +package dev._2lstudios.flamecord.natives.compress; + +import io.netty.buffer.ByteBuf; + +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +public class JavaCompressor implements Compressor { + + private final byte[] buffer = new byte[8192]; //NullCordX + + private final Deflater deflater; + private final Inflater inflater; + private boolean disposed = false; + + public JavaCompressor(int level) { + this.deflater = new Deflater(level); + this.inflater = new Inflater(); + } + + @Override + public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) throws DataFormatException { + ensureNotDisposed(); + + // We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly. + checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers"); + checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers"); + + try { + byte[] inData = new byte[source.readableBytes()]; + source.readBytes(inData); + + this.inflater.setInput(inData); + + while (!this.inflater.finished() && this.inflater.getTotalIn() < inData.length) { + int count = this.inflater.inflate(this.buffer); + destination.writeBytes(this.buffer, 0, count); + } + + } + finally { + this.inflater.reset(); + } + } + + @Override + public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + ensureNotDisposed(); + // We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly. + checkArgument(source.nioBufferCount() == 1, "source has multiple backing buffers"); + checkArgument(destination.nioBufferCount() == 1, "destination has multiple backing buffers"); + + try { + byte[] inData = new byte[source.readableBytes()]; + source.readBytes(inData); + + this.deflater.setInput(inData); + this.deflater.finish(); + + while (!this.deflater.finished()) { + int count = this.deflater.deflate(this.buffer); + destination.writeBytes(this.buffer, 0, count); + } + } + finally { + this.deflater.reset(); + } + } + + @Override + public void close() { + this.disposed = true; + this.deflater.end(); + this.inflater.end(); + } + + private void ensureNotDisposed() { + checkState(!this.disposed, "Object already disposed"); + } + + @Override + public boolean isNeedDirectBuffer() { + return false; + } + +} diff --git a/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/LibdeflateCompressor.java b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/LibdeflateCompressor.java new file mode 100644 index 000000000..fd9f9007d --- /dev/null +++ b/native/src/main/java/dev/_2lstudios/flamecord/natives/compress/LibdeflateCompressor.java @@ -0,0 +1,83 @@ +package dev._2lstudios.flamecord.natives.compress; + +import com.google.common.base.Preconditions; +import com.velocitypowered.natives.compression.NativeZlibDeflate; +import com.velocitypowered.natives.compression.NativeZlibInflate; +import io.netty.buffer.ByteBuf; + +import java.util.zip.DataFormatException; + +public class LibdeflateCompressor implements Compressor { + + private final long inflateCtx; + private final long deflateCtx; + private boolean disposed = false; + + public LibdeflateCompressor(int level) { + int correctedLevel = level == -1 ? 6 : level; + if (correctedLevel > 12 || correctedLevel < 1) { + throw new IllegalArgumentException("Invalid compression level " + level); + } + + this.inflateCtx = NativeZlibInflate.init(); + this.deflateCtx = NativeZlibDeflate.init(correctedLevel); + } + + @Override + public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize) + throws DataFormatException { + ensureNotDisposed(); + + destination.ensureWritable(uncompressedSize); + + long sourceAddress = source.memoryAddress() + source.readerIndex(); + long destinationAddress = destination.memoryAddress() + destination.writerIndex(); + + NativeZlibInflate.process(this.inflateCtx, sourceAddress, source.readableBytes(), destinationAddress, + uncompressedSize); + destination.writerIndex(destination.writerIndex() + uncompressedSize); + } + + @Override + public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { + ensureNotDisposed(); + + while (true) { + long sourceAddress = source.memoryAddress() + source.readerIndex(); + long destinationAddress = destination.memoryAddress() + destination.writerIndex(); + + int produced = NativeZlibDeflate.process(this.deflateCtx, sourceAddress, source.readableBytes(), + destinationAddress, destination.writableBytes()); + if (produced > 0) { + destination.writerIndex(destination.writerIndex() + produced); + break; + } + else if (produced == 0) { + // Insufficient room - enlarge the buffer. + destination.capacity(destination.capacity() * 2); + } + else { + throw new DataFormatException("libdeflate returned unknown code " + produced); + } + } + } + + private void ensureNotDisposed() { + Preconditions.checkState(!this.disposed, "Object already disposed"); + } + + @Override + public void close() { + if (!this.disposed) { + NativeZlibInflate.free(this.inflateCtx); + NativeZlibDeflate.free(this.deflateCtx); + } + this.disposed = true; + } + + @Override + public boolean isNeedDirectBuffer() { + return true; + } + +} diff --git a/native/src/main/resources/libdeflate_aarch64.so b/native/src/main/resources/libdeflate_aarch64.so new file mode 100644 index 0000000000000000000000000000000000000000..284450dd1c3632601b58628619dabee4ea57fa91 GIT binary patch literal 74880 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 ZQ9^b2<4NheI5jq>K-J7jD3EZ-{uco)TXz5e literal 0 HcmV?d00001 diff --git a/native/src/main/resources/libdeflate_x86_64.so b/native/src/main/resources/libdeflate_x86_64.so new file mode 100644 index 0000000000000000000000000000000000000000..cda376fa2c627f3457d3fb5ef79b2791f47118d7 GIT binary patch literal 61176 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 V!n_vFZMVlN^j;mUKt|X2{|D$~Sdst$ delta 7376 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 diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java b/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java index f33ce913a..01b208ed2 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java @@ -30,6 +30,22 @@ public abstract class DefinedPacket writeString( s, buf, Short.MAX_VALUE ); } + //Velocity start + private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33]; + + static { + for (int i = 0; i <= 32; ++i) { + VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d); + } + VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for the number 0. + } + + public static int varIntBytes(int value) { + return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)]; + } + //Velocity end + + public static void writeString(String s, ByteBuf buf, int maxLength) { if ( s.length() > maxLength ) @@ -198,6 +214,12 @@ public abstract class DefinedPacket } } + public static void write21BitVarInt(ByteBuf buf, int value) { + // See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/ + int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14); + buf.writeMedium(w); + } + public static int readVarShort(ByteBuf buf) { int low = buf.readUnsignedShort(); diff --git a/proxy/pom.xml b/proxy/pom.xml index 10fe411d8..f6daaea4d 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -165,6 +165,12 @@ ${project.version} compile + + io.github.waterfallmc + waterfall-native + ${project.version} + compile + io.github.waterfallmc waterfall-module-cmd-alert diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index d67519484..f7a3952a2 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -17,6 +17,7 @@ import dev._2lstudios.flamecord.commands.FlameCordCommand; import dev._2lstudios.flamecord.configuration.ModulesConfiguration; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.github.waterfallmc.waterfall.conf.WaterfallConfiguration; +import dev._2lstudios.flamecord.natives.Natives; import io.github.waterfallmc.waterfall.event.ProxyExceptionEvent; import io.github.waterfallmc.waterfall.exception.ProxyPluginEnableDisableException; import io.netty.bootstrap.ServerBootstrap; @@ -256,23 +257,7 @@ public class BungeeCord extends ProxyServer pluginManager = new PluginManager( this ); - if ( !Boolean.getBoolean( "net.md_5.bungee.native.disable" ) ) - { - if ( EncryptionUtil.nativeFactory.load() ) - { - logger.info( "Using mbed TLS based native cipher." ); - } else - { - logger.info( "Using standard Java JCE cipher." ); - } - if ( CompressFactory.zlib.load() ) - { - logger.info( "Using zlib based native compressor." ); - } else - { - logger.info( "Using standard Java compressor." ); - } - } + logger.log(Level.INFO, "FlameCord is using " + Natives.getCompressorFactory().getName() + " compression"); } /** diff --git a/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java b/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java index d07cf4627..10cb0430c 100644 --- a/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java +++ b/proxy/src/main/java/net/md_5/bungee/compress/PacketCompressor.java @@ -1,45 +1,74 @@ package net.md_5.bungee.compress; +import java.util.zip.DataFormatException; + +import dev._2lstudios.flamecord.natives.MoreByteBufUtils; +import dev._2lstudios.flamecord.natives.compress.Compressor; import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; -import java.util.zip.Deflater; import lombok.Setter; -import net.md_5.bungee.jni.zlib.BungeeZlib; import net.md_5.bungee.protocol.DefinedPacket; -public class PacketCompressor extends MessageToByteEncoder -{ +public class PacketCompressor extends MessageToByteEncoder { + + private final Compressor compressor; - private final BungeeZlib zlib = CompressFactory.zlib.newInstance(); @Setter private int threshold = 256; - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception - { - zlib.init( true, Deflater.DEFAULT_COMPRESSION ); + public PacketCompressor(Compressor compressor) { + this.compressor = compressor; } @Override - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception - { - zlib.free(); + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + compressor.close(); } @Override - protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception - { + protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { int origSize = msg.readableBytes(); - if ( origSize < threshold ) - { - DefinedPacket.writeVarInt( 0, out ); - out.writeBytes( msg ); - } else - { - DefinedPacket.writeVarInt( origSize, out ); - - zlib.process( msg, out ); + if (origSize < threshold) { + // Under the threshold, there is nothing to do. + DefinedPacket.writeVarInt(0, out); + out.writeBytes(msg); + return; + } + + int uncompressed = msg.readableBytes(); + + DefinedPacket.writeVarInt(uncompressed, out); + ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, msg); + + int startCompressed = out.writerIndex(); + try { + compressor.deflate(compatibleIn, out); + } finally { + compatibleIn.release(); + } + int compressedLength = out.writerIndex() - startCompressed; + if (compressedLength >= 1 << 21) { + throw new DataFormatException("The server sent a very large (over 2MiB compressed) packet."); } } + + @Override + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception { + return allocateByteBufForCompression(ctx.alloc(), msg, threshold); + } + + public static ByteBuf allocateByteBufForCompression(ByteBufAllocator allocator, ByteBuf msg, int threshold) { + int uncompressed = msg.readableBytes(); + if (uncompressed < threshold) { + int finalBufferSize = uncompressed + 1; + finalBufferSize += DefinedPacket.varIntBytes(finalBufferSize); + return allocator.directBuffer(finalBufferSize); + } + + // (maximum data length after compression) + packet length varint + uncompressed data varint + int initialBufferSize = (uncompressed - 1) + 3 + DefinedPacket.varIntBytes(uncompressed); + return allocator.directBuffer(initialBufferSize); + } } diff --git a/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java b/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java index eaedf4bc4..066bdafc8 100644 --- a/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java +++ b/proxy/src/main/java/net/md_5/bungee/compress/PacketDecompressor.java @@ -1,5 +1,6 @@ package net.md_5.bungee.compress; +import dev._2lstudios.flamecord.natives.compress.Compressor; import lombok.*; import com.google.common.base.Preconditions; @@ -8,53 +9,75 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; import java.util.List; import net.md_5.bungee.jni.zlib.BungeeZlib; +import net.md_5.bungee.protocol.BadPacketException; import net.md_5.bungee.protocol.DefinedPacket; +import static dev._2lstudios.flamecord.natives.MoreByteBufUtils.ensureCompatible; + + @RequiredArgsConstructor -public class PacketDecompressor extends MessageToMessageDecoder -{ +public class PacketDecompressor extends MessageToMessageDecoder { + private static final int VANILLA_MAXIMUM_UNCOMPRESSED_SIZE = 8 * 1024 * 1024; // 8MiB + private static final int MAXIMUM_UNCOMPRESSED_SIZE_WHILE_CHECKING = ((100 * 4) + Short.MAX_VALUE) + 5 + 5; //((100 chars channel tag) + max data size) + string varint + packet id varint - private final int compressionThreshold; - private final BungeeZlib zlib = CompressFactory.zlib.newInstance(); + @Setter + private boolean checking = false; - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception - { - zlib.init( false, 0 ); - } + private final Compressor compressor; + private final int threshold; @Override - public void handlerRemoved(ChannelHandlerContext ctx) throws Exception - { - zlib.free(); + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + compressor.close(); } @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception - { - int size = DefinedPacket.readVarInt( in ); - if ( size == 0 ) - { - out.add( in.retain() ); - } else - { - Preconditions.checkArgument( size >= compressionThreshold, "Decompressed size %s less than compression threshold %s", size, compressionThreshold); - ByteBuf decompressed = ctx.alloc().directBuffer(); - - try - { - zlib.process( in, decompressed ); - Preconditions.checkArgument( decompressed.readableBytes() == size, "Decompressed size %s is not equal to actual decompressed bytes", size, decompressed.readableBytes()); - - out.add( decompressed ); - decompressed = null; - } finally - { - if ( decompressed != null ) - { - decompressed.release(); - } - } + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + int size = DefinedPacket.readVarInt(in); + if (size == 0) { + out.add(in.retain()); + return; + } + + //Velocity start + if (threshold != -1 && size < threshold) { + throw new BadPacketException( + "Uncompressed size " + size + " is less than threshold " + threshold + ); + } + + if (size > VANILLA_MAXIMUM_UNCOMPRESSED_SIZE) { + throw new BadPacketException("" + + "Uncompressed size " + size + " exceeds threshold of " + VANILLA_MAXIMUM_UNCOMPRESSED_SIZE + ); + } + + if (checking && size > MAXIMUM_UNCOMPRESSED_SIZE_WHILE_CHECKING) { + throw new BadPacketException( + "Uncompressed size " + size + " exceeds threshold of " + MAXIMUM_UNCOMPRESSED_SIZE_WHILE_CHECKING + " (While checking)" + ); + } + //Velocity end + + ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in); + ByteBuf uncompressed; + if (checking) { + uncompressed = ctx.alloc().directBuffer(size, size); + } + else { + uncompressed = ctx.alloc().directBuffer(size); + } + + try { + compressor.inflate(compatibleIn, uncompressed, size); + out.add(uncompressed); + } + catch (Exception e) { + uncompressed.release(); + throw e; + } + finally { + compatibleIn.release(); } } -} +} \ No newline at end of file diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java index 8d4439ab8..95ed34416 100644 --- a/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java +++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java @@ -44,8 +44,8 @@ public class ForgeUtils if ( discriminator == 2 ) // ModList { ByteBuf buffer = payload.slice(); - int modCount = DefinedPacket.readVarInt( buffer, 2 ); - for ( int i = 0; i < modCount; i++ ) + int modCount = DefinedPacket.readVarInt( buffer ); // FlameCord - Remove length check for compression + for ( int i = 0; i < modCount; i++ ) { modTags.put( DefinedPacket.readString( buffer ), DefinedPacket.readString( buffer ) ); } diff --git a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java index b98ee6a75..ffe038168 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java @@ -1,6 +1,10 @@ package net.md_5.bungee.netty; import com.google.common.base.Preconditions; +import dev._2lstudios.flamecord.FlameCord; +import dev._2lstudios.flamecord.configuration.FlameCordConfiguration; +import dev._2lstudios.flamecord.natives.Natives; +import dev._2lstudios.flamecord.natives.compress.Compressor; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; @@ -145,15 +149,16 @@ public class ChannelWrapper return ch; } - public void setCompressionThreshold(int compressionThreshold) - { + public void setCompressionThreshold(int compressionThreshold) { + Compressor compressor = Natives.getCompressorFactory().create( FlameCord.getInstance().getFlameCordConfiguration().getCompressionLevel() ); + // FlameCord - Use pipeline to reduce redundancy final ChannelPipeline pipeline = ch.pipeline(); if ( pipeline.get( PacketCompressor.class ) == null && compressionThreshold != -1 ) { - addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor() ); + addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor( compressor ) ); } - if ( compressionThreshold != -1 ) + if ( compressionThreshold != -1 ) { pipeline.get( PacketCompressor.class ).setThreshold( compressionThreshold ); } else @@ -163,7 +168,7 @@ public class ChannelWrapper if ( pipeline.get( PacketDecompressor.class ) == null && compressionThreshold != -1 ) { - addBefore( PipelineUtils.PACKET_DECODER, "decompress", new PacketDecompressor(compressionThreshold) ); + addBefore( PipelineUtils.PACKET_DECODER, "decompress", new PacketDecompressor( compressor, compressionThreshold ) ); // FlameCord - Implement Libdeflate } if ( compressionThreshold == -1 ) { @@ -171,6 +176,7 @@ public class ChannelWrapper } } + // FlameCord start - Antibot System // Make the channel accessible public Channel getChannel() { diff --git a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java index f04fdd9ce..9aebc9e1a 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java @@ -126,7 +126,8 @@ public class PipelineUtils }; public static final Base BASE = new Base(); private static final KickStringWriter legacyKicker = new KickStringWriter(); - private static final Varint21LengthFieldPrepender framePrepender = new Varint21LengthFieldPrepender(); + // FlameCord - Implement Libdeflate + public static final Varint21LengthFieldPrepender framePrepender = new Varint21LengthFieldPrepender(); public static final String TIMEOUT_HANDLER = "timeout"; public static final String PACKET_DECODER = "packet-decoder"; public static final String PACKET_ENCODER = "packet-encoder"; -- 2.37.3.windows.1