Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
From 3168598e19fb93e789d66b9434815a8121b59b14 Mon Sep 17 00:00:00 2001
From 89de3db7eeb6f66fdb8f59edff3da206b878368b Mon Sep 17 00:00:00 2001
From: creeper123123321 <creeper123123321@gmail.com>
Date: Thu, 17 Jan 2019 03:25:59 +0000
Subject: [PATCH] Don't use a bytebuf for packet decoding


diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java b/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java
index cf7dea17..3a802ca4 100644
index cf7dea17..3d7bdd02 100644
--- a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java
+++ b/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java
@@ -27,8 +27,7 @@ public class Varint21FrameDecoder extends ByteToMessageDecoder
Expand Down Expand Up @@ -35,36 +35,6 @@ index cf7dea17..3a802ca4 100644
if ( false && length == 0) // Waterfall - ignore
{
throw new CorruptedFrameException( "Empty Packet!" );
@@ -49,25 +51,11 @@ public class Varint21FrameDecoder extends ByteToMessageDecoder
{
in.resetReaderIndex();
return;
- } else
- {
- if ( in.hasMemoryAddress() )
- {
- out.add( in.readRetainedSlice( length ) );
- } else
- {
- if ( !DIRECT_WARNING )
- {
- DIRECT_WARNING = true;
- System.out.println( "Netty is not using direct IO buffers." );
- }
-
- // See https://github.com/SpigotMC/BungeeCord/issues/1717
- ByteBuf dst = ctx.alloc().directBuffer( length );
- in.readBytes( dst );
- out.add( dst );
- }
+ // Waterfall start
+ } else {
+ out.add(in.readRetainedSlice(length));
return;
+ // Waterfall end
}
}
}
--
2.44.0
2.43.0

Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
From fd479b4784c896f3fb2077e4dd09dfd9afdfe631 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 5 Sep 2026 19:02:58 +0200
Subject: [PATCH] Test that framed packets are always direct

The native zlib behind PacketDecompressor calls ByteBuf#memoryAddress, so every
frame leaving Varint21FrameDecoder has to be direct. Cover both the decoder's own
non-direct fallback and the full cipher chain in front of it.

diff --git a/proxy/src/test/java/net/md_5/bungee/netty/DirectFrameTest.java b/proxy/src/test/java/net/md_5/bungee/netty/DirectFrameTest.java
new file mode 100644
index 00000000..b718bd3a
--- /dev/null
+++ b/proxy/src/test/java/net/md_5/bungee/netty/DirectFrameTest.java
@@ -0,0 +1,96 @@
+package net.md_5.bungee.netty;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
+import io.netty.channel.embedded.EmbeddedChannel;
+import java.util.Arrays;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import net.md_5.bungee.jni.cipher.BungeeCipher;
+import net.md_5.bungee.jni.cipher.JavaCipher;
+import net.md_5.bungee.netty.cipher.CipherDecoder;
+import net.md_5.bungee.protocol.DefinedPacket;
+import net.md_5.bungee.protocol.Varint21FrameDecoder;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The native zlib behind PacketDecompressor calls ByteBuf#memoryAddress, so every frame
+ * leaving Varint21FrameDecoder has to be direct.
+ */
+public class DirectFrameTest
+{
+
+ private static final byte[] PAYLOAD = new byte[ 64 ];
+
+ static
+ {
+ Arrays.fill( PAYLOAD, (byte) 0x42 );
+ }
+
+ private static ByteBuf framed(ByteBuf into)
+ {
+ DefinedPacket.writeVarInt( PAYLOAD.length, into );
+ into.writeBytes( PAYLOAD );
+ return into;
+ }
+
+ private static void assertDirectFrame(EmbeddedChannel channel)
+ {
+ ByteBuf frame = channel.readInbound();
+ try
+ {
+ assertNotNull( frame, "no frame decoded" );
+ byte[] got = new byte[ frame.readableBytes() ];
+ frame.getBytes( frame.readerIndex(), got );
+ assertArrayEquals( PAYLOAD, got, "frame contents" );
+ assertTrue( frame.hasMemoryAddress(), "frame handed to PacketDecompressor must be direct" );
+ frame.memoryAddress();
+ } finally
+ {
+ if ( frame != null )
+ {
+ frame.release();
+ }
+ channel.finishAndReleaseAll();
+ }
+ }
+
+ @Test
+ public void heapInputIsCopiedToDirect()
+ {
+ EmbeddedChannel channel = new EmbeddedChannel( new Varint21FrameDecoder() );
+ channel.config().setAllocator( PooledByteBufAllocator.DEFAULT );
+
+ assertTrue( channel.writeInbound( framed( PooledByteBufAllocator.DEFAULT.heapBuffer() ) ) );
+ assertDirectFrame( channel );
+ }
+
+ @Test
+ public void framesFromTheJavaCipherAreDirect() throws Exception
+ {
+ SecretKey key = newKey();
+ BungeeCipher encrypt = new JavaCipher();
+ encrypt.init( true, key );
+ BungeeCipher decrypt = new JavaCipher();
+ decrypt.init( false, key );
+
+ EmbeddedChannel channel = new EmbeddedChannel( new CipherDecoder( decrypt ), new Varint21FrameDecoder() );
+ channel.config().setAllocator( PooledByteBufAllocator.DEFAULT );
+
+ ByteBuf plain = framed( PooledByteBufAllocator.DEFAULT.directBuffer() );
+ ByteBuf encrypted = encrypt.cipher( channel.pipeline().firstContext(), plain );
+ plain.release();
+
+ assertTrue( channel.writeInbound( encrypted ) );
+ assertDirectFrame( channel );
+ }
+
+ private static SecretKey newKey() throws Exception
+ {
+ KeyGenerator gen = KeyGenerator.getInstance( "AES" );
+ gen.init( 128 );
+ return gen.generateKey();
+ }
+}
--
2.43.0

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
From 5e1fd61c298d334c96e20de0b96f67ded62bfdf5 Mon Sep 17 00:00:00 2001
From: Shane Freeder <theboyetronic@gmail.com>
Date: Sat, 5 Sep 2026 19:03:06 +0200
Subject: [PATCH] Cipher into a direct buffer in JavaCipher

JavaCipher became the default when the native cipher was turned off, and it emitted
a heap buffer. The native zlib behind PacketDecompressor calls ByteBuf#memoryAddress,
so Varint21FrameDecoder's non-direct fallback went from a corner case to the hot path
for every online-mode connection: an allocation and a copy per frame, plus a spurious
"Netty is not using direct IO buffers." on startup.

Ciphering into a direct buffer instead moves that to one allocation per read and
leaves frames as zero-copy slices. Measured over the full decrypt + frame decode path
(AES-128-CFB8, 16KiB reads, JDK 21, Linux x86_64, median of 3):

frame size heap out direct out
64B 19.9 MiB/s 21.0 MiB/s +5.5%
256B 21.2 MiB/s 21.6 MiB/s +1.9%
2048B 21.6 MiB/s 21.7 MiB/s +0.5%

CFB8 dominates so the end-to-end win is small; with the crypto stubbed out the buffer
handling alone is 30-40% faster. Cipher.update(ByteBuffer, ByteBuffer) was measured as
an alternative and rejected: with direct buffers the JDK falls back to temporary heap
arrays, allocating roughly the payload size per call and running no faster.

diff --git a/native/src/main/java/net/md_5/bungee/jni/cipher/JavaCipher.java b/native/src/main/java/net/md_5/bungee/jni/cipher/JavaCipher.java
index 94d02691..d3a6a35c 100644
--- a/native/src/main/java/net/md_5/bungee/jni/cipher/JavaCipher.java
+++ b/native/src/main/java/net/md_5/bungee/jni/cipher/JavaCipher.java
@@ -66,10 +66,28 @@ public class JavaCipher implements BungeeCipher
int readableBytes = in.readableBytes();
byte[] heapIn = bufToByte( in );

- ByteBuf heapOut = ctx.alloc().heapBuffer( cipher.getOutputSize( readableBytes ) );
- heapOut.writerIndex( cipher.update( heapIn, 0, readableBytes, heapOut.array(), heapOut.arrayOffset() ) );
+ // Waterfall start - cipher into a direct buffer; the native zlib downstream needs a
+ // memory address, and heap output makes Varint21FrameDecoder copy every frame
+ int outputSize = cipher.getOutputSize( readableBytes );
+ byte[] heapOut = heapOutLocal.get();
+ if ( heapOut.length < outputSize )
+ {
+ heapOut = new byte[ outputSize ];
+ heapOutLocal.set( heapOut );
+ }
+
+ ByteBuf out = ctx.alloc().directBuffer( outputSize );
+ try
+ {
+ out.writeBytes( heapOut, 0, cipher.update( heapIn, 0, readableBytes, heapOut ) );
+ } catch ( Throwable t )
+ {
+ out.release();
+ throw t;
+ }

- return heapOut;
+ return out;
+ // Waterfall end
}

@Override
--
2.43.0

Loading