3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +01:00

Merge branch 'decode-multiple' into dev/1.1.0

Dieser Commit ist enthalten in:
Andrew Steinborn 2020-06-21 13:39:24 -04:00
Commit ab9115178b
37 geänderte Dateien mit 462 neuen und 587 gelöschten Zeilen

2
.gitignore vendored
Datei anzeigen

@ -89,3 +89,5 @@ plugins/
### Natives stuff ### ### Natives stuff ###
native/mbedtls native/mbedtls
native/zlib-ng native/zlib-ng
native/zlib-cf
native/libdeflate

Datei anzeigen

@ -1,21 +1,22 @@
#!/bin/bash #!/bin/bash
if [ ! -d zlib-ng ]; then if [ ! -d libdeflate ]; then
echo "Cloning zlib-ng..." echo "Cloning libdeflate..."
git clone https://github.com/zlib-ng/zlib-ng.git git clone https://github.com/ebiggers/libdeflate.git
fi fi
echo "Compiling zlib-ng..." echo "Compiling libdeflate..."
cd zlib-ng cd libdeflate || exit
CFLAGS="-fPIC -O3" ./configure --zlib-compat --static make
make clean && make
cd .. cd ..
# Modify as you need. # Modify as you need.
MBEDTLS_ROOT=mbedtls MBEDTLS_ROOT=mbedtls
CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared" CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/linux/ -fPIC -shared -Wl,-z,noexecstack"
gcc $CFLAGS -Izlib-ng src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \ ARCH=$(uname -m)
src/main/c/jni_zlib_common.c zlib-ng/libz.a -o src/main/resources/linux_x64/velocity-compress.so mkdir -p src/main/resources/linux_$ARCH
gcc $CFLAGS -I $MBEDTLS_ROOT/include -shared $MBEDTLS_ROOT/library/aes.c $MBEDTLS_ROOT/library/aesni.c \ gcc $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
gcc $CFLAGS -I $MBEDTLS_ROOT/include -shared $MBEDTLS_ROOT/library/aes.c $MBEDTLS_ROOT/library/aesni.c \
$MBEDTLS_ROOT/library/platform.c $MBEDTLS_ROOT/library/platform_util.c src/main/c/jni_util.c src/main/c/jni_cipher.c \ $MBEDTLS_ROOT/library/platform.c $MBEDTLS_ROOT/library/platform_util.c src/main/c/jni_util.c src/main/c/jni_cipher.c \
-o src/main/resources/linux_x64/velocity-cipher.so -o src/main/resources/linux_$ARCH/velocity-cipher.so

Datei anzeigen

@ -1,23 +0,0 @@
#!/bin/bash
if [ ! -d zlib-ng ]; then
echo "Cloning zlib-ng..."
git clone https://github.com/zlib-ng/zlib-ng.git
fi
echo "Compiling zlib-ng..."
cd zlib-ng
CFLAGS="-fPIC -O3" ./configure --zlib-compat --static
make clean && make
cd ..
# Modify as you need.
MBEDTLS_ROOT=mbedtls
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
CFLAGS="-O3 -I$JAVA_HOME/include/ -I$JAVA_HOME/include/darwin/ -fPIC -shared"
clang $CFLAGS -Izlib-ng src/main/c/jni_util.c src/main/c/jni_zlib_deflate.c src/main/c/jni_zlib_inflate.c \
src/main/c/jni_zlib_common.c zlib-ng/libz.a -o src/main/resources/macosx/velocity-compress.dylib
clang $CFLAGS -I $MBEDTLS_ROOT/include -shared $MBEDTLS_ROOT/library/aes.c $MBEDTLS_ROOT/library/aesni.c \
$MBEDTLS_ROOT/library/platform.c $MBEDTLS_ROOT/library/platform_util.c src/main/c/jni_util.c src/main/c/jni_cipher.c \
-o src/main/resources/macosx/velocity-cipher.dylib

Datei anzeigen

@ -1,29 +0,0 @@
#include <jni.h>
#include <stdbool.h>
#include <stdlib.h>
#include <zlib.h>
#include "jni_util.h"
void JNICALL
check_zlib_free(JNIEnv *env, z_stream *stream, bool deflate)
{
int ret = deflate ? deflateEnd(stream) : inflateEnd(stream);
const char *msg = stream->msg;
free((void*) stream);
switch (ret) {
case Z_OK:
break;
case Z_STREAM_ERROR:
if (msg == NULL) {
msg = "stream state inconsistent";
}
// fall-through
case Z_DATA_ERROR:
if (msg == NULL) {
msg = "data was discarded";
}
throwException(env, "java/lang/IllegalArgumentException", msg);
break;
}
}

Datei anzeigen

@ -1,6 +0,0 @@
#include <jni.h>
#include <stdbool.h>
#include <zlib.h>
void JNICALL
check_zlib_free(JNIEnv *env, z_stream *stream, bool deflate);

Datei anzeigen

@ -2,56 +2,21 @@
#include <jni.h> #include <jni.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <zlib.h> #include <libdeflate.h>
#include "jni_util.h" #include "jni_util.h"
#include "jni_zlib_common.h"
static jfieldID finishedID;
static jfieldID consumedID;
JNIEXPORT void JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_initIDs(JNIEnv *env, jclass cls)
{
finishedID = (*env)->GetFieldID(env, cls, "finished", "Z");
consumedID = (*env)->GetFieldID(env, cls, "consumed", "I");
}
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env, Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env,
jobject obj, jobject obj,
jint level) jint level)
{ {
z_stream* stream = calloc(1, sizeof(z_stream)); struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level);
if (compressor == NULL) {
if (stream == 0) {
// Out of memory! // Out of memory!
throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream"); throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate compressor");
return 0;
}
int ret = deflateInit(stream, level);
if (ret == Z_OK) {
return (jlong) stream;
} else {
const char *zlib_msg = stream->msg;
free(stream);
switch (ret) {
case Z_MEM_ERROR:
throwException(env, "java/lang/OutOfMemoryError", "zlib init");
break;
case Z_STREAM_ERROR: {
// Thanks Ken and Ritchie!
char message[32];
snprintf(message, 32, "invalid level %d", level);
throwException(env, "java/lang/IllegalArgumentException", message);
break;
}
default:
throwException(env, "java/util/zip/DataFormatException", zlib_msg);
break;
}
return 0; return 0;
} }
return (jlong) compressor;
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
@ -59,11 +24,10 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_free(JNIEnv *env,
jobject obj, jobject obj,
jlong ctx) jlong ctx)
{ {
z_stream* stream = (z_stream*) ctx; libdeflate_free_compressor((struct libdeflate_compressor *) ctx);
check_zlib_free(env, stream, true);
} }
JNIEXPORT int JNICALL JNIEXPORT jboolean JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env, Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env,
jobject obj, jobject obj,
jlong ctx, jlong ctx,
@ -73,38 +37,8 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *e
jint destinationLength, jint destinationLength,
jboolean finish) jboolean finish)
{ {
z_stream* stream = (z_stream*) ctx; struct libdeflate_compressor *compressor = (struct libdeflate_compressor *) ctx;
stream->next_in = (Bytef *) sourceAddress; size_t produced = libdeflate_zlib_compress(compressor, (void *) sourceAddress, sourceLength,
stream->next_out = (Bytef *) destinationAddress; (void *) destinationAddress, destinationLength);
stream->avail_in = sourceLength; return (jlong) produced;
stream->avail_out = destinationLength;
int res = deflate(stream, finish ? Z_FINISH : Z_NO_FLUSH);
switch (res) {
case Z_STREAM_END:
// The stream has ended.
(*env)->SetBooleanField(env, obj, finishedID, JNI_TRUE);
// fall-through
case Z_OK:
// Not yet completed, but progress has been made. Tell Java how many bytes we've processed.
(*env)->SetIntField(env, obj, consumedID, sourceLength - stream->avail_in);
return destinationLength - stream->avail_out;
case Z_BUF_ERROR:
// This is not fatal. Just say we need more data. Usually this applies to the next_out buffer,
// which NativeVelocityCompressor will notice and will expand the buffer.
return 0;
default:
throwException(env, "java/util/zip/DataFormatException", stream->msg);
return 0;
}
}
JNIEXPORT void JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_reset(JNIEnv *env,
jobject obj,
jlong ctx)
{
z_stream* stream = (z_stream*) ctx;
int ret = deflateReset(stream);
assert(ret == Z_OK);
} }

Datei anzeigen

@ -2,50 +2,21 @@
#include <jni.h> #include <jni.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <zlib.h> #include <libdeflate.h>
#include "jni_util.h" #include "jni_util.h"
#include "jni_zlib_common.h"
static jfieldID finishedID;
static jfieldID consumedID;
JNIEXPORT void JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibInflate_initIDs(JNIEnv *env, jclass cls)
{
finishedID = (*env)->GetFieldID(env, cls, "finished", "Z");
consumedID = (*env)->GetFieldID(env, cls, "consumed", "I");
}
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env, Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env,
jobject obj) jobject obj)
{ {
z_stream* stream = calloc(1, sizeof(z_stream)); struct libdeflate_decompressor *decompress = libdeflate_alloc_decompressor();
if (decompress == NULL) {
if (stream == 0) {
// Out of memory! // Out of memory!
throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream"); throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate decompressor");
return 0; return 0;
} }
int ret = inflateInit(stream); return (jlong) decompress;
if (ret == Z_OK) {
return (jlong) stream;
} else {
const char *zlib_msg = stream->msg;
free(stream);
switch (ret) {
case Z_MEM_ERROR:
throwException(env, "java/lang/OutOfMemoryError", "zlib init");
return 0;
case Z_STREAM_ERROR:
throwException(env, "java/lang/IllegalArgumentException", "stream clobbered?");
return 0;
default:
throwException(env, "java/util/zip/DataFormatException", zlib_msg);
return 0;
}
}
} }
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
@ -53,51 +24,34 @@ Java_com_velocitypowered_natives_compression_NativeZlibInflate_free(JNIEnv *env,
jobject obj, jobject obj,
jlong ctx) jlong ctx)
{ {
z_stream* stream = (z_stream*) ctx; libdeflate_free_decompressor((struct libdeflate_decompressor *) ctx);
check_zlib_free(env, stream, false);
} }
JNIEXPORT int JNICALL JNIEXPORT jboolean JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env, Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env,
jobject obj, jobject obj,
jlong ctx, jlong ctx,
jlong sourceAddress, jlong sourceAddress,
jint sourceLength, jint sourceLength,
jlong destinationAddress, jlong destinationAddress,
jint destinationLength) jint destinationLength,
jlong maximumSize)
{ {
z_stream* stream = (z_stream*) ctx; struct libdeflate_decompressor *decompress = (struct libdeflate_decompressor *) ctx;
stream->next_in = (Bytef *) sourceAddress; enum libdeflate_result result = libdeflate_zlib_decompress(decompress, (void *) sourceAddress,
stream->next_out = (Bytef *) destinationAddress; sourceLength, (void *) destinationAddress, destinationLength, NULL);
stream->avail_in = sourceLength;
stream->avail_out = destinationLength;
int res = inflate(stream, Z_PARTIAL_FLUSH); switch (result) {
switch (res) { case LIBDEFLATE_SUCCESS:
case Z_STREAM_END: // We are happy
// The stream has ended return JNI_TRUE;
(*env)->SetBooleanField(env, obj, finishedID, JNI_TRUE); case LIBDEFLATE_BAD_DATA:
// fall-through throwException(env, "java/util/zip/DataFormatException", "inflate data is bad");
case Z_OK: return JNI_FALSE;
// Not yet completed, but progress has been made. Tell Java how many bytes we've processed. case LIBDEFLATE_SHORT_OUTPUT:
(*env)->SetIntField(env, obj, consumedID, sourceLength - stream->avail_in); case LIBDEFLATE_INSUFFICIENT_SPACE:
return destinationLength - stream->avail_out; // These cases are the same for us. We expect the full uncompressed size to be known.
case Z_BUF_ERROR: throwException(env, "java/util/zip/DataFormatException", "uncompressed size is inaccurate");
// This is not fatal. Just say we need more data. Usually this applies to the next_out buffer, return JNI_FALSE;
// which NativeVelocityCompressor will notice and will expand the buffer.
return 0;
default:
throwException(env, "java/util/zip/DataFormatException", stream->msg);
return 0;
} }
} }
JNIEXPORT void JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibInflate_reset(JNIEnv *env,
jobject obj,
jlong ctx)
{
z_stream* stream = (z_stream*) ctx;
int ret = inflateReset(stream);
assert(ret == Z_OK);
}

Datei anzeigen

@ -54,7 +54,8 @@ public class Java11VelocityCompressor implements VelocityCompressor {
} }
@Override @Override
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException { public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
throws DataFormatException {
ensureNotDisposed(); ensureNotDisposed();
// We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly. // We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly.
@ -67,7 +68,7 @@ public class Java11VelocityCompressor implements VelocityCompressor {
while (!inflater.finished() && inflater.getBytesRead() < source.readableBytes()) { while (!inflater.finished() && inflater.getBytesRead() < source.readableBytes()) {
if (!destination.isWritable()) { if (!destination.isWritable()) {
ensureMaxSize(destination, max); ensureMaxSize(destination, uncompressedSize);
destination.ensureWritable(ZLIB_BUFFER_SIZE); destination.ensureWritable(ZLIB_BUFFER_SIZE);
} }

Datei anzeigen

@ -25,20 +25,21 @@ public class JavaVelocityCompressor implements VelocityCompressor {
} }
@Override @Override
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException { public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
throws DataFormatException {
ensureNotDisposed(); ensureNotDisposed();
final int available = source.readableBytes(); final int available = source.readableBytes();
this.setInflaterInput(source); this.setInflaterInput(source);
if (destination.hasArray()) { if (destination.hasArray()) {
this.inflateDestinationIsHeap(destination, available, max); this.inflateDestinationIsHeap(destination, available, uncompressedSize);
} else { } else {
if (buf.length == 0) { if (buf.length == 0) {
buf = new byte[ZLIB_BUFFER_SIZE]; buf = new byte[ZLIB_BUFFER_SIZE];
} }
while (!inflater.finished() && inflater.getBytesRead() < available) { while (!inflater.finished() && inflater.getBytesRead() < available) {
ensureMaxSize(destination, max); ensureMaxSize(destination, uncompressedSize);
int read = inflater.inflate(buf); int read = inflater.inflate(buf);
destination.writeBytes(buf, 0, read); destination.writeBytes(buf, 0, read);
} }

Datei anzeigen

@ -0,0 +1,87 @@
package com.velocitypowered.natives.compression;
import com.google.common.base.Preconditions;
import com.velocitypowered.natives.util.BufferPreference;
import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException;
public class LibdeflateVelocityCompressor implements VelocityCompressor {
public static final VelocityCompressorFactory FACTORY = LibdeflateVelocityCompressor::new;
private final NativeZlibInflate inflate = new NativeZlibInflate();
private final long inflateCtx;
private final NativeZlibDeflate deflate = new NativeZlibDeflate();
private final long deflateCtx;
private boolean disposed = false;
private LibdeflateVelocityCompressor(int level) {
int correctedLevel = level == -1 ? 6 : level;
if (correctedLevel > 12 || correctedLevel < 1) {
throw new IllegalArgumentException("Invalid compression level " + level);
}
this.inflateCtx = inflate.init();
this.deflateCtx = deflate.init(level == -1 ? 6 : level);
}
@Override
public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
throws DataFormatException {
ensureNotDisposed();
source.memoryAddress();
destination.memoryAddress();
// libdeflate recommends we work with a known uncompressed size - so we work strictly within
// those parameters. If the uncompressed size doesn't match the compressed size, then we will
// throw an exception from native code.
destination.ensureWritable(uncompressedSize);
long sourceAddress = source.memoryAddress() + source.readerIndex();
long destinationAddress = destination.memoryAddress() + destination.writerIndex();
inflate.process(inflateCtx, sourceAddress, source.readableBytes(), destinationAddress,
uncompressedSize);
destination.writerIndex(destination.writerIndex() + uncompressedSize);
}
@Override
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed();
source.memoryAddress();
destination.memoryAddress();
while (true) {
long sourceAddress = source.memoryAddress() + source.readerIndex();
long destinationAddress = destination.memoryAddress() + destination.writerIndex();
int produced = deflate.process(deflateCtx, sourceAddress, source.readableBytes(),
destinationAddress, destination.writableBytes());
if (produced > 0) {
destination.writerIndex(destination.writerIndex() + produced);
return;
}
// Insufficient room - enlarge the buffer.
destination.capacity(destination.capacity() * 2);
}
}
private void ensureNotDisposed() {
Preconditions.checkState(!disposed, "Object already disposed");
}
@Override
public void dispose() {
if (!disposed) {
inflate.free(inflateCtx);
deflate.free(deflateCtx);
}
disposed = true;
}
@Override
public BufferPreference preferredBufferType() {
return BufferPreference.DIRECT_REQUIRED;
}
}

Datei anzeigen

@ -1,89 +0,0 @@
package com.velocitypowered.natives.compression;
import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFER_SIZE;
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
import com.google.common.base.Preconditions;
import com.velocitypowered.natives.util.BufferPreference;
import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException;
public class NativeVelocityCompressor implements VelocityCompressor {
public static final VelocityCompressorFactory FACTORY = NativeVelocityCompressor::new;
private final NativeZlibInflate inflate = new NativeZlibInflate();
private final long inflateCtx;
private final NativeZlibDeflate deflate = new NativeZlibDeflate();
private final long deflateCtx;
private boolean disposed = false;
private NativeVelocityCompressor(int level) {
this.inflateCtx = inflate.init();
this.deflateCtx = deflate.init(level);
}
@Override
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
ensureNotDisposed();
source.memoryAddress();
destination.memoryAddress();
while (!inflate.finished && source.isReadable()) {
if (!destination.isWritable()) {
ensureMaxSize(destination, max);
destination.ensureWritable(ZLIB_BUFFER_SIZE);
}
int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(),
source.readableBytes(), destination.memoryAddress() + destination.writerIndex(),
destination.writableBytes());
source.readerIndex(source.readerIndex() + inflate.consumed);
destination.writerIndex(destination.writerIndex() + produced);
}
inflate.reset(inflateCtx);
inflate.consumed = 0;
inflate.finished = false;
}
@Override
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed();
source.memoryAddress();
destination.memoryAddress();
while (!deflate.finished) {
if (!destination.isWritable()) {
destination.ensureWritable(ZLIB_BUFFER_SIZE);
}
int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(),
source.readableBytes(),
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(),
true);
source.readerIndex(source.readerIndex() + deflate.consumed);
destination.writerIndex(destination.writerIndex() + produced);
}
deflate.reset(deflateCtx);
deflate.consumed = 0;
deflate.finished = false;
}
private void ensureNotDisposed() {
Preconditions.checkState(!disposed, "Object already disposed");
}
@Override
public void dispose() {
if (!disposed) {
inflate.free(inflateCtx);
deflate.free(deflateCtx);
}
disposed = true;
}
@Override
public BufferPreference preferredBufferType() {
return BufferPreference.DIRECT_REQUIRED;
}
}

Datei anzeigen

@ -5,21 +5,10 @@ package com.velocitypowered.natives.compression;
*/ */
class NativeZlibDeflate { class NativeZlibDeflate {
boolean finished;
int consumed;
native long init(int level); native long init(int level);
native long free(long ctx); native long free(long ctx);
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
int destinationLength, boolean finish); int destinationLength);
native void reset(long ctx);
static {
initIDs();
}
private static native void initIDs();
} }

Datei anzeigen

@ -1,25 +1,16 @@
package com.velocitypowered.natives.compression; package com.velocitypowered.natives.compression;
import java.util.zip.DataFormatException;
/** /**
* Represents a native interface for zlib's inflate functions. * Represents a native interface for zlib's inflate functions.
*/ */
class NativeZlibInflate { class NativeZlibInflate {
boolean finished;
int consumed;
native long init(); native long init();
native long free(long ctx); native long free(long ctx);
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, native boolean process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
int destinationLength); int destinationLength) throws DataFormatException;
native void reset(long ctx);
static {
initIDs();
}
private static native void initIDs();
} }

Datei anzeigen

@ -6,10 +6,12 @@ import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
/** /**
* Provides an interface to inflate and deflate {@link ByteBuf}s using zlib. * Provides an interface to inflate and deflate {@link ByteBuf}s using zlib or a compatible
* implementation.
*/ */
public interface VelocityCompressor extends Disposable, Native { public interface VelocityCompressor extends Disposable, Native {
void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException; void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
throws DataFormatException;
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
} }

Datei anzeigen

@ -7,6 +7,7 @@ import java.util.function.BooleanSupplier;
public class NativeConstraints { public class NativeConstraints {
private static final boolean NATIVES_ENABLED = !Boolean.getBoolean("velocity.natives-disabled"); private static final boolean NATIVES_ENABLED = !Boolean.getBoolean("velocity.natives-disabled");
private static final boolean IS_AMD64; private static final boolean IS_AMD64;
private static final boolean IS_AARCH64;
private static final boolean CAN_GET_MEMORYADDRESS; private static final boolean CAN_GET_MEMORYADDRESS;
static { static {
@ -21,20 +22,21 @@ public class NativeConstraints {
// HotSpot on Intel macOS prefers x86_64, but OpenJ9 on macOS and HotSpot/OpenJ9 elsewhere // HotSpot on Intel macOS prefers x86_64, but OpenJ9 on macOS and HotSpot/OpenJ9 elsewhere
// give amd64. // give amd64.
IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64"); IS_AMD64 = osArch.equals("amd64") || osArch.equals("x86_64");
IS_AARCH64 = osArch.equals("aarch64");
} }
static final BooleanSupplier MACOS = () -> { static final BooleanSupplier NATIVE_BASE = () -> NATIVES_ENABLED && CAN_GET_MEMORYADDRESS;
return NATIVES_ENABLED
&& CAN_GET_MEMORYADDRESS static final BooleanSupplier LINUX_X86_64 = () -> {
&& System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X") return NATIVE_BASE.getAsBoolean()
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux")
&& IS_AMD64; && IS_AMD64;
}; };
static final BooleanSupplier LINUX = () -> { static final BooleanSupplier LINUX_AARCH64 = () -> {
return NATIVES_ENABLED return NATIVE_BASE.getAsBoolean()
&& CAN_GET_MEMORYADDRESS
&& System.getProperty("os.name", "").equalsIgnoreCase("Linux") && System.getProperty("os.name", "").equalsIgnoreCase("Linux")
&& IS_AMD64; && IS_AARCH64;
}; };
static final BooleanSupplier JAVA_11 = () -> { static final BooleanSupplier JAVA_11 = () -> {

Datei anzeigen

@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableList;
import com.velocitypowered.natives.NativeSetupException; import com.velocitypowered.natives.NativeSetupException;
import com.velocitypowered.natives.compression.Java11VelocityCompressor; import com.velocitypowered.natives.compression.Java11VelocityCompressor;
import com.velocitypowered.natives.compression.JavaVelocityCompressor; import com.velocitypowered.natives.compression.JavaVelocityCompressor;
import com.velocitypowered.natives.compression.NativeVelocityCompressor; import com.velocitypowered.natives.compression.LibdeflateVelocityCompressor;
import com.velocitypowered.natives.compression.VelocityCompressorFactory; import com.velocitypowered.natives.compression.VelocityCompressorFactory;
import com.velocitypowered.natives.encryption.JavaVelocityCipher; import com.velocitypowered.natives.encryption.JavaVelocityCipher;
import com.velocitypowered.natives.encryption.NativeVelocityCipher; import com.velocitypowered.natives.encryption.NativeVelocityCipher;
@ -62,12 +62,14 @@ public class Natives {
public static final NativeCodeLoader<VelocityCompressorFactory> compress = new NativeCodeLoader<>( public static final NativeCodeLoader<VelocityCompressorFactory> compress = new NativeCodeLoader<>(
ImmutableList.of( ImmutableList.of(
new NativeCodeLoader.Variant<>(NativeConstraints.MACOS, new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)", copyAndLoadNative("/linux_x86_64/velocity-compress.so"),
NativeVelocityCompressor.FACTORY), "libdeflate (Linux x86_64)",
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX, LibdeflateVelocityCompressor.FACTORY),
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)", new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
NativeVelocityCompressor.FACTORY), copyAndLoadNative("/linux_aarch64/velocity-compress.so"),
"libdeflate (Linux aarch64)",
LibdeflateVelocityCompressor.FACTORY),
new NativeCodeLoader.Variant<>(NativeConstraints.JAVA_11, () -> { new NativeCodeLoader.Variant<>(NativeConstraints.JAVA_11, () -> {
}, "Java 11", () -> Java11VelocityCompressor.FACTORY), }, "Java 11", () -> Java11VelocityCompressor.FACTORY),
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> { new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
@ -77,12 +79,12 @@ public class Natives {
public static final NativeCodeLoader<VelocityCipherFactory> cipher = new NativeCodeLoader<>( public static final NativeCodeLoader<VelocityCipherFactory> cipher = new NativeCodeLoader<>(
ImmutableList.of( ImmutableList.of(
new NativeCodeLoader.Variant<>(NativeConstraints.MACOS, new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_X86_64,
copyAndLoadNative("/macosx/velocity-cipher.dylib"), "mbed TLS (macOS)", copyAndLoadNative("/linux_x86_64/velocity-cipher.so"),
NativeVelocityCipher.FACTORY), "mbed TLS (Linux x86_64)", NativeVelocityCipher.FACTORY),
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX, new NativeCodeLoader.Variant<>(NativeConstraints.LINUX_AARCH64,
copyAndLoadNative("/linux_x64/velocity-cipher.so"), "mbed TLS (Linux amd64)", copyAndLoadNative("/linux_aarch64/velocity-cipher.so"),
NativeVelocityCipher.FACTORY), "mbed TLS (Linux aarch64)", NativeVelocityCipher.FACTORY),
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> { new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
}, "Java", JavaVelocityCipher.FACTORY) }, "Java", JavaVelocityCipher.FACTORY)
) )

Binäre Datei nicht angezeigt.

Binäre Datei nicht angezeigt.

Datei anzeigen

@ -33,13 +33,13 @@ class VelocityCompressorTest {
} }
@Test @Test
@EnabledOnOs({MAC, LINUX}) @EnabledOnOs({LINUX})
void sanityCheckNative() { void sanityCheckNative() {
assertThrows(IllegalArgumentException.class, () -> Natives.compress.get().create(-42)); assertThrows(IllegalArgumentException.class, () -> Natives.compress.get().create(-42));
} }
@Test @Test
@EnabledOnOs({MAC, LINUX}) @EnabledOnOs({LINUX})
void nativeIntegrityCheck() throws DataFormatException { void nativeIntegrityCheck() throws DataFormatException {
VelocityCompressor compressor = Natives.compress.get().create(Deflater.DEFAULT_COMPRESSION); VelocityCompressor compressor = Natives.compress.get().create(Deflater.DEFAULT_COMPRESSION);
if (compressor.preferredBufferType() != BufferPreference.DIRECT_REQUIRED) { if (compressor.preferredBufferType() != BufferPreference.DIRECT_REQUIRED) {
@ -86,10 +86,11 @@ class VelocityCompressorTest {
ByteBuf decompressed = bufSupplier.get(); ByteBuf decompressed = bufSupplier.get();
source.writeBytes(TEST_DATA); source.writeBytes(TEST_DATA);
int uncompressedData = source.readableBytes();
try { try {
compressor.deflate(source, dest); compressor.deflate(source, dest);
compressor.inflate(dest, decompressed, Integer.MAX_VALUE); compressor.inflate(dest, decompressed, uncompressedData);
source.readerIndex(0); source.readerIndex(0);
assertTrue(ByteBufUtil.equals(source, decompressed)); assertTrue(ByteBufUtil.equals(source, decompressed));
} finally { } finally {

Datei anzeigen

@ -48,8 +48,7 @@ dependencies {
compile "io.netty:netty-handler:${nettyVersion}" compile "io.netty:netty-handler:${nettyVersion}"
compile "io.netty:netty-transport-native-epoll:${nettyVersion}" compile "io.netty:netty-transport-native-epoll:${nettyVersion}"
compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64" compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}" compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-aarch64"
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}:osx-x86_64"
compile "io.netty:netty-resolver-dns:${nettyVersion}" compile "io.netty:netty-resolver-dns:${nettyVersion}"
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}" compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"

Datei anzeigen

@ -120,12 +120,12 @@ public class VelocityServer implements ProxyServer {
} }
public KeyPair getServerKeyPair() { public KeyPair getServerKeyPair() {
return ensureInitialized(serverKeyPair); return serverKeyPair;
} }
@Override @Override
public VelocityConfiguration getConfiguration() { public VelocityConfiguration getConfiguration() {
return ensureInitialized(this.configuration); return this.configuration;
} }
@Override @Override
@ -229,7 +229,6 @@ public class VelocityServer implements ProxyServer {
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics()); Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
} }
@RequiresNonNull({"pluginManager", "eventManager"})
private void loadPlugins() { private void loadPlugins() {
logger.info("Loading plugins..."); logger.info("Loading plugins...");
@ -443,18 +442,11 @@ public class VelocityServer implements ProxyServer {
} }
public AsyncHttpClient getAsyncHttpClient() { public AsyncHttpClient getAsyncHttpClient() {
return ensureInitialized(cm).getHttpClient(); return cm.getHttpClient();
} }
public Ratelimiter getIpAttemptLimiter() { public Ratelimiter getIpAttemptLimiter() {
return ensureInitialized(ipAttemptLimiter); return ipAttemptLimiter;
}
private static <T> T ensureInitialized(T o) {
if (o == null) {
throw new IllegalStateException("The proxy isn't fully initialized.");
}
return o;
} }
/** /**

Datei anzeigen

@ -465,7 +465,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
Advanced advanced = new Advanced(toml.getTable("advanced")); Advanced advanced = new Advanced(toml.getTable("advanced"));
Query query = new Query(toml.getTable("query")); Query query = new Query(toml.getTable("query"));
Metrics metrics = new Metrics(toml.getTable("metrics")); Metrics metrics = new Metrics(toml.getTable("metrics"));
byte[] forwardingSecret = toml.getString("forwarding-secret", "5up3r53cr3t") byte[] forwardingSecret = toml.getString("forwarding-secret", generateRandomString(12))
.getBytes(StandardCharsets.UTF_8); .getBytes(StandardCharsets.UTF_8);
String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN") String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN")

Datei anzeigen

@ -16,7 +16,6 @@ import com.velocitypowered.natives.encryption.VelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory; import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.network.netty.DiscardHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftCipherDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftCipherDecoder;
@ -125,6 +124,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} }
} }
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) {
sessionHandler.readCompleted();
}
}
@Override @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx.channel().isActive()) { if (ctx.channel().isActive()) {
@ -145,7 +151,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
} }
} }
installDiscardHandler(ctx);
ctx.close(); ctx.close();
} }
} }
@ -161,18 +166,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
Preconditions.checkState(this.channel.eventLoop().inEventLoop(), "Not in event loop"); Preconditions.checkState(this.channel.eventLoop().inEventLoop(), "Not in event loop");
} }
private void installDiscardHandler(ChannelHandlerContext ctx) {
if (ctx.pipeline().get("discard") == null) {
ctx.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER);
}
}
private void installDiscardHandler() {
if (channel.pipeline().get("discard") == null) {
channel.pipeline().addBefore(MINECRAFT_DECODER, "discard", DiscardHandler.HANDLER);
}
}
public EventLoop eventLoop() { public EventLoop eventLoop() {
return channel.eventLoop(); return channel.eventLoop();
} }
@ -212,9 +205,10 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
*/ */
public void closeWith(Object msg) { public void closeWith(Object msg) {
if (channel.isActive()) { if (channel.isActive()) {
knownDisconnect = true; channel.eventLoop().execute(() -> {
installDiscardHandler(); knownDisconnect = true;
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE); channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
});
} }
} }
@ -223,7 +217,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
*/ */
public void close() { public void close() {
if (channel.isActive()) { if (channel.isActive()) {
installDiscardHandler();
channel.close(); channel.close();
} }
} }

Datei anzeigen

@ -70,6 +70,10 @@ public interface MinecraftSessionHandler {
} }
default void readCompleted() {
}
default boolean handle(AvailableCommands commands) { default boolean handle(AvailableCommands commands) {
return false; return false;
} }

Datei anzeigen

@ -188,12 +188,17 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
if (packet instanceof PluginMessage) { if (packet instanceof PluginMessage) {
((PluginMessage) packet).retain(); ((PluginMessage) packet).retain();
} }
playerConnection.write(packet); playerConnection.delayedWrite(packet);
} }
@Override @Override
public void handleUnknown(ByteBuf buf) { public void handleUnknown(ByteBuf buf) {
playerConnection.write(buf.retain()); playerConnection.delayedWrite(buf.retain());
}
@Override
public void readCompleted() {
playerConnection.flush();
} }
@Override @Override

Datei anzeigen

@ -170,59 +170,45 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
public boolean handle(PluginMessage packet) { public boolean handle(PluginMessage packet) {
VelocityServerConnection serverConn = player.getConnectedServer(); VelocityServerConnection serverConn = player.getConnectedServer();
MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null; MinecraftConnection backendConn = serverConn != null ? serverConn.getConnection() : null;
if (serverConn != null && backendConn != null) { if (serverConn == null || backendConn == null) {
if (backendConn.getState() != StateRegistry.PLAY) { return true;
logger.warn("A plugin message was received while the backend server was not "
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
} else if (PluginMessageUtil.isRegister(packet)) {
player.getKnownChannels().addAll(PluginMessageUtil.getChannels(packet));
backendConn.write(packet.retain());
} else if (PluginMessageUtil.isUnregister(packet)) {
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
backendConn.write(packet.retain());
} else if (PluginMessageUtil.isMcBrand(packet)) {
backendConn.write(PluginMessageUtil
.rewriteMinecraftBrand(packet, server.getVersion(), player.getProtocolVersion()));
} else {
if (serverConn.getPhase() == BackendConnectionPhases.IN_TRANSITION) {
// We must bypass the currently-connected server when forwarding Forge packets.
VelocityServerConnection inFlight = player.getConnectionInFlight();
if (inFlight != null) {
player.getPhase().handle(player, packet, inFlight);
}
return true;
}
if (!player.getPhase().handle(player, packet, serverConn)) {
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
.consideredComplete()) {
// The client is trying to send messages too early. This is primarily caused by mods,
// but further aggravated by Velocity. To work around these issues, we will queue any
// non-FML handshake messages to be sent once the FML handshake has completed or the
// JoinGame packet has been received by the proxy, whichever comes first.
//
// We also need to make sure to retain these packets so they can be flushed
// appropriately.
loginPluginMessages.add(packet.retain());
} else {
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
backendConn.write(packet.retain());
} else {
byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id,
ByteBufUtil.getBytes(packet.content()));
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
PluginMessage message = new PluginMessage(packet.getChannel(),
Unpooled.wrappedBuffer(copy));
backendConn.write(message);
}, backendConn.eventLoop());
}
}
}
}
} }
if (backendConn.getState() != StateRegistry.PLAY) {
logger.warn("A plugin message was received while the backend server was not "
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
return true;
}
if (this.tryHandleVanillaPluginMessageChannel(packet, backendConn)) {
return true;
}
if (serverConn.getPhase() == BackendConnectionPhases.IN_TRANSITION) {
// We must bypass the currently-connected server when forwarding Forge packets.
VelocityServerConnection inFlight = player.getConnectionInFlight();
if (inFlight != null) {
if (player.getPhase().handle(player, packet, inFlight)) {
return true;
}
}
} else if (!this.tryHandleForgeMessage(packet, serverConn)) {
return true;
}
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
if (id == null) {
backendConn.write(packet.retain());
} else {
byte[] copy = ByteBufUtil.getBytes(packet.content());
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id,
ByteBufUtil.getBytes(packet.content()));
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
PluginMessage message = new PluginMessage(packet.getChannel(),
Unpooled.wrappedBuffer(copy));
backendConn.write(message);
}, backendConn.eventLoop());
}
return true; return true;
} }
@ -277,9 +263,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public void writabilityChanged() { public void writabilityChanged() {
boolean writable = player.getConnection().getChannel().isWritable();
if (!writable) {
// We might have packets queued for the server, so flush them now to free up memory.
player.getConnection().flush();
}
VelocityServerConnection serverConn = player.getConnectedServer(); VelocityServerConnection serverConn = player.getConnectedServer();
if (serverConn != null) { if (serverConn != null) {
boolean writable = player.getConnection().getChannel().isWritable();
MinecraftConnection smc = serverConn.getConnection(); MinecraftConnection smc = serverConn.getConnection();
if (smc != null) { if (smc != null) {
smc.setAutoReading(writable); smc.setAutoReading(writable);
@ -287,6 +279,44 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} }
} }
private boolean tryHandleVanillaPluginMessageChannel(PluginMessage packet,
MinecraftConnection backendConn) {
if (PluginMessageUtil.isRegister(packet)) {
player.getKnownChannels().addAll(PluginMessageUtil.getChannels(packet));
backendConn.write(packet.retain());
return true;
} else if (PluginMessageUtil.isUnregister(packet)) {
player.getKnownChannels().removeAll(PluginMessageUtil.getChannels(packet));
backendConn.write(packet.retain());
return true;
} else if (PluginMessageUtil.isMcBrand(packet)) {
backendConn.write(PluginMessageUtil.rewriteMinecraftBrand(packet, server.getVersion(),
player.getProtocolVersion()));
return true;
}
return false;
}
private boolean tryHandleForgeMessage(PluginMessage packet, VelocityServerConnection serverConn) {
if (player.getPhase().handle(player, packet, serverConn)) {
return true;
}
if (!player.getPhase().consideredComplete() || !serverConn.getPhase().consideredComplete()) {
// The client is trying to send messages too early. This is primarily caused by mods,
// but further aggravated by Velocity. To work around these issues, we will queue any
// non-FML handshake messages to be sent once the FML handshake has completed or the
// JoinGame packet has been received by the proxy, whichever comes first.
//
// We also need to make sure to retain these packets so they can be flushed
// appropriately.
loginPluginMessages.add(packet.retain());
return true;
} else {
return false;
}
}
/** /**
* Handles the {@code JoinGame} packet. This function is responsible for handling the client-side * Handles the {@code JoinGame} packet. This function is responsible for handling the client-side
* switching servers in Velocity. * switching servers in Velocity.

Datei anzeigen

@ -6,7 +6,7 @@ import static org.asynchttpclient.Dsl.config;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.network.netty.DnsAddressResolverGroupNameResolverAdapter; import com.velocitypowered.proxy.network.netty.SeparatePoolInetNameResolver;
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler; import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
@ -18,6 +18,7 @@ import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.epoll.EpollChannelOption; import io.netty.channel.epoll.EpollChannelOption;
import io.netty.resolver.dns.DnsAddressResolverGroup; import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.resolver.dns.DnsNameResolverBuilder; import io.netty.resolver.dns.DnsNameResolverBuilder;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -48,7 +49,7 @@ public final class ConnectionManager {
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public final BackendChannelInitializerHolder backendChannelInitializer; public final BackendChannelInitializerHolder backendChannelInitializer;
private final DnsAddressResolverGroup resolverGroup; private final SeparatePoolInetNameResolver resolver;
private final AsyncHttpClient httpClient; private final AsyncHttpClient httpClient;
/** /**
@ -65,21 +66,16 @@ public final class ConnectionManager {
new ServerChannelInitializer(this.server)); new ServerChannelInitializer(this.server));
this.backendChannelInitializer = new BackendChannelInitializerHolder( this.backendChannelInitializer = new BackendChannelInitializerHolder(
new BackendChannelInitializer(this.server)); new BackendChannelInitializer(this.server));
this.resolverGroup = new DnsAddressResolverGroup(new DnsNameResolverBuilder() this.resolver = new SeparatePoolInetNameResolver(GlobalEventExecutor.INSTANCE);
.channelType(this.transportType.datagramChannelClass)
.negativeTtl(15)
.ndots(1));
this.httpClient = asyncHttpClient(config() this.httpClient = asyncHttpClient(config()
.setEventLoopGroup(this.workerGroup) .setEventLoopGroup(this.workerGroup)
.setUserAgent(server.getVersion().getName() + "/" + server.getVersion().getVersion()) .setUserAgent(server.getVersion().getName() + "/" + server.getVersion().getVersion())
.addRequestFilter(new RequestFilter() { .addRequestFilter(new RequestFilter() {
@Override @Override
public <T> FilterContext<T> filter(FilterContext<T> ctx) throws FilterException { public <T> FilterContext<T> filter(FilterContext<T> ctx) {
return new FilterContextBuilder<>(ctx) return new FilterContextBuilder<>(ctx)
.request(new RequestBuilder(ctx.getRequest()) .request(new RequestBuilder(ctx.getRequest())
.setNameResolver( .setNameResolver(resolver)
new DnsAddressResolverGroupNameResolverAdapter(resolverGroup, workerGroup)
)
.build()) .build())
.build(); .build();
} }
@ -162,7 +158,7 @@ public final class ConnectionManager {
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
this.server.getConfiguration().getConnectTimeout()) this.server.getConfiguration().getConnectTimeout())
.group(group == null ? this.workerGroup : group) .group(group == null ? this.workerGroup : group)
.resolver(this.resolverGroup); .resolver(this.resolver.asGroup());
if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) { if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) {
bootstrap.option(EpollChannelOption.TCP_FASTOPEN_CONNECT, true); bootstrap.option(EpollChannelOption.TCP_FASTOPEN_CONNECT, true);
} }
@ -194,6 +190,8 @@ public final class ConnectionManager {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
this.resolver.shutdown();
} }
public EventLoopGroup getBossGroup() { public EventLoopGroup getBossGroup() {

Datei anzeigen

@ -7,11 +7,6 @@ import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel; import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.kqueue.KQueue;
import io.netty.channel.kqueue.KQueueDatagramChannel;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.kqueue.KQueueServerSocketChannel;
import io.netty.channel.kqueue.KQueueSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.ServerSocketChannel;
@ -27,11 +22,7 @@ enum TransportType {
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))), (name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class, EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
EpollDatagramChannel.class, EpollDatagramChannel.class,
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))), (name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type)));
KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class,
KQueueDatagramChannel.class,
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)));
final String name; final String name;
final Class<? extends ServerSocketChannel> serverSocketChannelClass; final Class<? extends ServerSocketChannel> serverSocketChannelClass;
@ -71,8 +62,6 @@ enum TransportType {
if (Epoll.isAvailable()) { if (Epoll.isAvailable()) {
return EPOLL; return EPOLL;
} else if (KQueue.isAvailable()) {
return KQUEUE;
} else { } else {
return NIO; return NIO;
} }

Datei anzeigen

@ -1,17 +0,0 @@
package com.velocitypowered.proxy.network.netty;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
@Sharable
public class DiscardHandler extends ChannelInboundHandlerAdapter {
public static final DiscardHandler HANDLER = new DiscardHandler();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ReferenceCountUtil.release(msg);
}
}

Datei anzeigen

@ -1,68 +0,0 @@
package com.velocitypowered.proxy.network.netty;
import io.netty.channel.EventLoopGroup;
import io.netty.resolver.InetNameResolver;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.ThreadExecutorMap;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
public class DnsAddressResolverGroupNameResolverAdapter extends InetNameResolver {
private final DnsAddressResolverGroup resolverGroup;
private final EventLoopGroup group;
/**
* Creates a DnsAddressResolverGroupNameResolverAdapter.
* @param resolverGroup the resolver group to use
* @param group the event loop group
*/
public DnsAddressResolverGroupNameResolverAdapter(
DnsAddressResolverGroup resolverGroup, EventLoopGroup group) {
super(ImmediateEventExecutor.INSTANCE);
this.resolverGroup = resolverGroup;
this.group = group;
}
@Override
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
EventExecutor executor = this.findExecutor();
resolverGroup.getResolver(executor).resolve(InetSocketAddress.createUnresolved(inetHost, 17))
.addListener((FutureListener<InetSocketAddress>) future -> {
if (future.isSuccess()) {
promise.trySuccess(future.getNow().getAddress());
} else {
promise.tryFailure(future.cause());
}
});
}
@Override
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise)
throws Exception {
EventExecutor executor = this.findExecutor();
resolverGroup.getResolver(executor).resolveAll(InetSocketAddress.createUnresolved(inetHost, 17))
.addListener((FutureListener<List<InetSocketAddress>>) future -> {
if (future.isSuccess()) {
List<InetAddress> addresses = new ArrayList<>(future.getNow().size());
for (InetSocketAddress address : future.getNow()) {
addresses.add(address.getAddress());
}
promise.trySuccess(addresses);
} else {
promise.tryFailure(future.cause());
}
});
}
private EventExecutor findExecutor() {
EventExecutor current = ThreadExecutorMap.currentExecutor();
return current == null ? group.next() : current;
}
}

Datei anzeigen

@ -0,0 +1,79 @@
package com.velocitypowered.proxy.network.netty;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.resolver.AddressResolver;
import io.netty.resolver.AddressResolverGroup;
import io.netty.resolver.DefaultNameResolver;
import io.netty.resolver.InetNameResolver;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
public final class SeparatePoolInetNameResolver extends InetNameResolver {
private final ExecutorService resolveExecutor;
private final InetNameResolver delegate;
private AddressResolverGroup<InetSocketAddress> resolverGroup;
/**
* Creates a new instance of {@code SeparatePoolInetNameResolver}.
*
* @param executor the {@link EventExecutor} which is used to notify the listeners of the {@link
* Future} returned by {@link #resolve(String)}
*/
public SeparatePoolInetNameResolver(EventExecutor executor) {
super(executor);
this.resolveExecutor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder()
.setNameFormat("Velocity DNS Resolver")
.setDaemon(true)
.build());
this.delegate = new DefaultNameResolver(executor);
}
@Override
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
try {
resolveExecutor.execute(() -> this.delegate.resolve(inetHost, promise));
} catch (RejectedExecutionException e) {
promise.setFailure(e);
}
}
@Override
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise)
throws Exception {
try {
resolveExecutor.execute(() -> this.delegate.resolveAll(inetHost, promise));
} catch (RejectedExecutionException e) {
promise.setFailure(e);
}
}
public void shutdown() {
this.resolveExecutor.shutdown();
}
/**
* Returns a view of this resolver as a AddressResolverGroup.
*
* @return a view of this resolver as a AddressResolverGroup
*/
public AddressResolverGroup<InetSocketAddress> asGroup() {
if (this.resolverGroup == null) {
this.resolverGroup = new AddressResolverGroup<InetSocketAddress>() {
@Override
protected AddressResolver<InetSocketAddress> newResolver(EventExecutor executor) {
return asAddressResolver();
}
};
}
return this.resolverGroup;
}
}

Datei anzeigen

@ -22,7 +22,6 @@ public class MinecraftCipherDecoder extends MessageToMessageDecoder<ByteBuf> {
try { try {
cipher.process(compatible); cipher.process(compatible);
out.add(compatible); out.add(compatible);
in.skipBytes(in.readableBytes());
} catch (Exception e) { } catch (Exception e) {
compatible.release(); // compatible will never be used if we throw an exception compatible.release(); // compatible will never be used if we throw an exception
throw e; throw e;

Datei anzeigen

@ -13,9 +13,13 @@ import java.util.List;
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> { public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
private static final int SOFT_MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB 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 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 int threshold;
private final VelocityCompressor compressor; private final VelocityCompressor compressor;
@ -28,21 +32,21 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int claimedUncompressedSize = ProtocolUtils.readVarInt(in); int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
if (claimedUncompressedSize == 0) { if (claimedUncompressedSize == 0) {
// Strip the now-useless uncompressed size, this message is already uncompressed. // This message is not compressed.
out.add(in.retainedSlice()); out.add(in.retainedSlice());
in.skipBytes(in.readableBytes());
return; return;
} }
checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than" checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than"
+ " threshold %s", claimedUncompressedSize, threshold); + " threshold %s", claimedUncompressedSize, threshold);
int allowedMax = Math.min(claimedUncompressedSize, HARD_MAXIMUM_UNCOMPRESSED_SIZE); checkFrame(claimedUncompressedSize <= UNCOMPRESSED_CAP,
int initialCapacity = Math.min(claimedUncompressedSize, SOFT_MAXIMUM_UNCOMPRESSED_SIZE); "Uncompressed size %s exceeds hard threshold of %s", claimedUncompressedSize,
UNCOMPRESSED_CAP);
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in); ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity); ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, claimedUncompressedSize);
try { try {
compressor.inflate(compatibleIn, uncompressed, allowedMax); compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize);
out.add(uncompressed); out.add(uncompressed);
} catch (Exception e) { } catch (Exception e) {
uncompressed.release(); uncompressed.release();

Datei anzeigen

@ -38,8 +38,11 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
@Override @Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect)
throws Exception { throws Exception {
int initialBufferSize = msg.readableBytes() <= threshold ? msg.readableBytes() + 1 : // Follow the advice of https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103
msg.readableBytes() / 3; // here for compression. The maximum buffer size if the data compresses well (which is almost
// always the case) is one less the input buffer.
int offset = msg.readableBytes() < threshold ? 1 : -1;
int initialBufferSize = msg.readableBytes() + offset;
return MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, initialBufferSize); return MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, initialBufferSize);
} }

Datei anzeigen

@ -1,43 +1,88 @@
package com.velocitypowered.proxy.protocol.netty; package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.util.except.QuietException;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException; import io.netty.util.ByteProcessor;
import java.util.List; import java.util.List;
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder { public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
private static final QuietException BAD_LENGTH_CACHED = new QuietException("Bad packet length");
private static final QuietException VARINT_BIG_CACHED = new QuietException("VarInt too big");
private final VarintByteDecoder reader = new VarintByteDecoder();
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (!in.isReadable()) { if (!ctx.channel().isActive()) {
in.skipBytes(in.readableBytes());
return; return;
} }
int origReaderIndex = in.readerIndex(); while (in.isReadable()) {
for (int i = 0; i < 3; i++) { reader.reset();
if (!in.isReadable()) {
in.readerIndex(origReaderIndex); int varintEnd = in.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; return;
} }
byte read = in.readByte(); if (reader.result == DecodeResult.SUCCESS) {
if (read >= 0) { if (reader.readVarint < 0) {
// Make sure reader index of length buffer is returned to the beginning throw BAD_LENGTH_CACHED;
in.readerIndex(origReaderIndex); } else if (reader.readVarint == 0) {
int packetLength = ProtocolUtils.readVarInt(in); // skip over the empty packet and ignore it
in.readerIndex(varintEnd + 1);
if (in.readableBytes() >= packetLength) {
out.add(in.readRetainedSlice(packetLength));
} else { } else {
in.readerIndex(origReaderIndex); int minimumRead = reader.bytesRead + reader.readVarint;
if (in.isReadable(minimumRead)) {
out.add(in.retainedSlice(varintEnd + 1, reader.readVarint));
in.skipBytes(minimumRead);
} else {
return;
}
} }
} else if (reader.result == DecodeResult.TOO_BIG) {
return; throw VARINT_BIG_CACHED;
} else if (reader.result == DecodeResult.TOO_SHORT) {
// No-op: we couldn't get a useful result.
break;
} }
} }
}
throw new CorruptedFrameException("VarInt too big"); private static 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;
}
void reset() {
readVarint = 0;
bytesRead = 0;
result = DecodeResult.TOO_SHORT;
}
}
private enum DecodeResult {
SUCCESS,
TOO_SHORT,
TOO_BIG
} }
} }