diff --git a/build.gradle.kts b/build.gradle.kts index 67095af..9d06aab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -102,6 +102,9 @@ subprojects { options.encoding = "UTF-8" } named("test") { + minHeapSize = "512m" + maxHeapSize = "1024m" + jvmArgs = listOf("-XX:MaxMetaspaceSize=512m") useJUnitPlatform() } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c6ecdf0..0a451dc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ netty-common = { group = "io.netty", name = "netty-common", version.ref = "netty netty-buffer = { group = "io.netty", name = "netty-buffer", version.ref = "netty" } netty-codec = { group = "io.netty", name = "netty-codec", version.ref = "netty" } netty-transport = { group = "io.netty", name = "netty-transport", version.ref = "netty" } +netty-transport-native-unix-common = { group = "io.netty", name = "netty-transport-native-unix-common", version.ref = "netty" } expiringmap = { group = "net.jodah", name = "expiringmap", version = "0.5.10" } @@ -18,6 +19,8 @@ junit-jupiter-params = { group = "org.junit.jupiter", name = "junit-jupiter-para [bundles] +netty = [ "netty-common", "netty-buffer", "netty-codec", "netty-transport", "netty-transport-native-unix-common" ] +junit = [ "junit-jupiter-engine", "junit-jupiter-api", "junit-jupiter-params" ] [plugins] diff --git a/transport-raknet/build.gradle.kts b/transport-raknet/build.gradle.kts index 717607c..8548304 100644 --- a/transport-raknet/build.gradle.kts +++ b/transport-raknet/build.gradle.kts @@ -17,15 +17,10 @@ description = "RakNet transport for Netty" dependencies { - api(libs.netty.common) - api(libs.netty.buffer) - api(libs.netty.codec) - api(libs.netty.transport) + api(libs.bundles.netty) api(libs.expiringmap) - testImplementation(libs.junit.jupiter.engine) - testImplementation(libs.junit.jupiter.api) - testImplementation(libs.junit.jupiter.params) + testImplementation(libs.bundles.junit) } tasks.jar { diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakClientChannel.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakClientChannel.java index 6ca4105..fd74d0f 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakClientChannel.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakClientChannel.java @@ -20,6 +20,8 @@ import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.socket.DatagramChannel; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; import org.cloudburstmc.netty.channel.proxy.ProxyChannel; import org.cloudburstmc.netty.channel.raknet.config.DefaultRakClientConfig; import org.cloudburstmc.netty.channel.raknet.config.RakChannelConfig; @@ -30,6 +32,7 @@ public class RakClientChannel extends ProxyChannel implements RakChannel { + private static final InternalLogger log = InternalLoggerFactory.getInstance(RakClientChannel.class); private static final ChannelMetadata metadata = new ChannelMetadata(true); /** @@ -41,6 +44,7 @@ public class RakClientChannel extends ProxyChannel implements R public RakClientChannel(DatagramChannel channel) { super(channel); this.config = new DefaultRakClientConfig(this); + this.pipeline().addLast(RakClientRouteHandler.NAME, new RakClientRouteHandler(this)); // Transforms DatagramPacket to ByteBuf if channel has been already connected this.rakPipeline().addFirst(RakClientProxyRouteHandler.NAME, new RakClientProxyRouteHandler(this)); diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakConstants.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakConstants.java index 6bd9493..ba768d9 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakConstants.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/RakConstants.java @@ -28,6 +28,7 @@ public class RakConstants { public static final byte RAKNET_PROTOCOL_VERSION = 11; // Mojang's version. public static final int MINIMUM_MTU_SIZE = 576; public static final int MAXIMUM_MTU_SIZE = 1400; + public static final Integer[] MTU_SIZES = new Integer[]{MAXIMUM_MTU_SIZE, 1200, MINIMUM_MTU_SIZE}; /** * Maximum amount of ordering channels as defined in vanilla RakNet. */ diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/DefaultRakClientConfig.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/DefaultRakClientConfig.java index 303f04d..454444c 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/DefaultRakClientConfig.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/DefaultRakClientConfig.java @@ -20,10 +20,12 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; +import org.cloudburstmc.netty.util.IpDontFragmentProvider; import java.util.Map; import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_UNCONNECTED_MAGIC; +import static org.cloudburstmc.netty.channel.raknet.RakConstants.MTU_SIZES; import static org.cloudburstmc.netty.channel.raknet.RakConstants.SESSION_TIMEOUT_MS; /** @@ -35,6 +37,10 @@ public class DefaultRakClientConfig extends DefaultRakSessionConfig { private volatile long connectTimeout = SESSION_TIMEOUT_MS; private volatile long sessionTimeout = SESSION_TIMEOUT_MS; private volatile long serverGuid; + private volatile boolean compatibilityMode = false; + private volatile Integer[] mtuSizes = MTU_SIZES; + private volatile boolean ipDontFragment = false; + private volatile int clientInternalAddresses = 10; public DefaultRakClientConfig(Channel channel) { super(channel); @@ -42,7 +48,10 @@ public DefaultRakClientConfig(Channel channel) { @Override public Map, Object> getOptions() { - return this.getOptions(super.getOptions(), RakChannelOption.RAK_UNCONNECTED_MAGIC, RakChannelOption.RAK_CONNECT_TIMEOUT, RakChannelOption.RAK_REMOTE_GUID, RakChannelOption.RAK_SESSION_TIMEOUT); + return this.getOptions( + super.getOptions(), + RakChannelOption.RAK_UNCONNECTED_MAGIC, RakChannelOption.RAK_CONNECT_TIMEOUT, RakChannelOption.RAK_REMOTE_GUID, RakChannelOption.RAK_SESSION_TIMEOUT, RakChannelOption.RAK_COMPATIBILITY_MODE, + RakChannelOption.RAK_MTU_SIZES, RakChannelOption.RAK_IP_DONT_FRAGMENT, RakChannelOption.RAK_CLIENT_INTERNAL_ADDRESSES); } @SuppressWarnings("unchecked") @@ -56,6 +65,14 @@ public T getOption(ChannelOption option) { return (T) Long.valueOf(this.getServerGuid()); } else if (option == RakChannelOption.RAK_SESSION_TIMEOUT) { return (T) Long.valueOf(this.getSessionTimeout()); + } else if (option == RakChannelOption.RAK_COMPATIBILITY_MODE) { + return (T) Boolean.valueOf(this.isCompatibilityMode()); + } else if (option == RakChannelOption.RAK_MTU_SIZES) { + return (T) this.getMtuSizes(); + } else if (option == RakChannelOption.RAK_IP_DONT_FRAGMENT) { + return (T) Boolean.valueOf(this.ipDontFragment); + } else if (option == RakChannelOption.RAK_CLIENT_INTERNAL_ADDRESSES) { + return (T) Integer.valueOf(this.clientInternalAddresses); } return super.getOption(option); } @@ -76,6 +93,18 @@ public boolean setOption(ChannelOption option, T value) { } else if (option == RakChannelOption.RAK_SESSION_TIMEOUT) { this.setSessionTimeout((Long) value); return true; + } else if (option == RakChannelOption.RAK_COMPATIBILITY_MODE) { + this.setCompatibilityMode((Boolean) value); + return true; + } else if (option == RakChannelOption.RAK_MTU_SIZES) { + this.setMtuSizes((Integer[]) value); + return true; + } else if (option == RakChannelOption.RAK_IP_DONT_FRAGMENT) { + this.setIpDontFragment((Boolean) value); + return (Boolean) value == this.isIpDontFragment(); + } else if (option == RakChannelOption.RAK_CLIENT_INTERNAL_ADDRESSES) { + this.setClientInternalAddresses((Integer) value); + return true; } return super.setOption(option, value); } @@ -120,4 +149,36 @@ public RakChannelConfig setSessionTimeout(long timeout) { public long getSessionTimeout() { return this.sessionTimeout; } + + public boolean isCompatibilityMode() { + return this.compatibilityMode; + } + + public void setCompatibilityMode(boolean enable) { + this.compatibilityMode = enable; + } + + public Integer[] getMtuSizes() { + return this.mtuSizes.clone(); + } + + public void setMtuSizes(Integer[] mtuSizes) { + this.mtuSizes = mtuSizes.clone(); + } + + public boolean isIpDontFragment() { + return this.ipDontFragment; + } + + public void setIpDontFragment(boolean enable) { + this.ipDontFragment = IpDontFragmentProvider.trySet(this.channel, enable); + } + + public int getClientInternalAddresses() { + return this.clientInternalAddresses; + } + + public void setClientInternalAddresses(int clientInternalAddresses) { + this.clientInternalAddresses = clientInternalAddresses; + } } diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/DefaultRakServerConfig.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/DefaultRakServerConfig.java index 2bad184..6d57e39 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/DefaultRakServerConfig.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/DefaultRakServerConfig.java @@ -22,6 +22,7 @@ import io.netty.channel.DefaultChannelConfig; import org.cloudburstmc.netty.channel.raknet.RakConstants; import org.cloudburstmc.netty.channel.raknet.RakServerChannel; +import org.cloudburstmc.netty.util.IpDontFragmentProvider; import java.util.Arrays; import java.util.Map; @@ -47,6 +48,8 @@ public class DefaultRakServerConfig extends DefaultChannelConfig implements RakS private volatile int globalPacketLimit = RakConstants.DEFAULT_GLOBAL_PACKET_LIMIT; private volatile RakServerMetrics metrics; private volatile boolean sendCookie; + private volatile boolean ipDontFragment = false; + public DefaultRakServerConfig(RakServerChannel channel) { super(channel); @@ -58,7 +61,7 @@ public Map, Object> getOptions() { super.getOptions(), RakChannelOption.RAK_GUID, RakChannelOption.RAK_MAX_CHANNELS, RakChannelOption.RAK_MAX_CONNECTIONS, RakChannelOption.RAK_SUPPORTED_PROTOCOLS, RakChannelOption.RAK_UNCONNECTED_MAGIC, RakChannelOption.RAK_ADVERTISEMENT, RakChannelOption.RAK_HANDLE_PING, RakChannelOption.RAK_PACKET_LIMIT, RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, RakChannelOption.RAK_SEND_COOKIE, - RakChannelOption.RAK_SERVER_METRICS); + RakChannelOption.RAK_SERVER_METRICS, RakChannelOption.RAK_IP_DONT_FRAGMENT); } @SuppressWarnings("unchecked") @@ -103,6 +106,9 @@ public T getOption(ChannelOption option) { if (option == RakChannelOption.RAK_SEND_COOKIE) { return (T) Boolean.valueOf(this.sendCookie); } + if (option == RakChannelOption.RAK_IP_DONT_FRAGMENT) { + return (T) Boolean.valueOf(this.ipDontFragment); + } return this.channel.parent().config().getOption(option); } @@ -133,10 +139,13 @@ public boolean setOption(ChannelOption option, T value) { } else if (option == RakChannelOption.RAK_GLOBAL_PACKET_LIMIT) { this.setGlobalPacketLimit((Integer) value); } else if (option == RakChannelOption.RAK_SEND_COOKIE) { - this.sendCookie = (Boolean) value; + this.setSendCookie((Boolean) value); } else if (option == RakChannelOption.RAK_SERVER_METRICS) { this.setMetrics((RakServerMetrics) value); - } else{ + } else if (option == RakChannelOption.RAK_IP_DONT_FRAGMENT) { + this.setIpDontFragment((Boolean) value); + return (Boolean) value == this.getIpDontFragment(); + } else { return this.channel.parent().config().setOption(option, value); } return true; @@ -291,4 +300,14 @@ public void setMetrics(RakServerMetrics metrics) { public RakServerMetrics getMetrics() { return this.metrics; } + + @Override + public void setIpDontFragment(boolean ipDontFragment) { + this.ipDontFragment = IpDontFragmentProvider.trySet(this.channel.parent(), ipDontFragment); + } + + @Override + public boolean getIpDontFragment() { + return this.ipDontFragment; + } } diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelOption.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelOption.java index 571b7a6..d3dc14f 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelOption.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelOption.java @@ -152,12 +152,36 @@ public class RakChannelOption extends ChannelOption { public static final ChannelOption RAK_GLOBAL_PACKET_LIMIT = valueOf(RakChannelOption.class, "RAK_GLOBAL_PACKET_LIMIT"); + /** + * Whether the client should be run in compatibility mode for closer behavior to the vanilla client RakNet implementation. + */ + public static final ChannelOption RAK_COMPATIBILITY_MODE = + valueOf(RakChannelOption.class, "RAK_COMPATIBILITY_MODE"); + /** * Whether to send a cookie to the client during the connection process. */ public static final ChannelOption RAK_SEND_COOKIE = valueOf(RakChannelOption.class, "RAK_SEND_COOKIE"); + /** + * An array of MTU sizes that the RakNet client will use when initially connecting. + */ + public static final ChannelOption RAK_MTU_SIZES = + valueOf(RakChannelOption.class, "RAK_MTU_SIZES"); + + /** + * Whether to use the IP_DONT_FRAGMENT option for the client channel. + */ + public static final ChannelOption RAK_IP_DONT_FRAGMENT = + valueOf(RakChannelOption.class, "RAK_IP_DONT_FRAGMENT"); + + /** + * The amount of internal addresses to send from the client in New Incoming Connection packets. + */ + public static final ChannelOption RAK_CLIENT_INTERNAL_ADDRESSES = + valueOf(RakChannelOption.class, "RAK_CLIENT_INTERNAL_ADDRESSES"); + @SuppressWarnings("deprecation") protected RakChannelOption() { super(null); diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakServerChannelConfig.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakServerChannelConfig.java index b9028aa..0b1897b 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakServerChannelConfig.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakServerChannelConfig.java @@ -72,4 +72,8 @@ public interface RakServerChannelConfig extends ChannelConfig { void setMetrics(RakServerMetrics metrics); RakServerMetrics getMetrics(); + + void setIpDontFragment(boolean ipDontFragment); + + boolean getIpDontFragment(); } diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandler.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandler.java index 7f0a16e..a1a0d21 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandler.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandler.java @@ -43,7 +43,7 @@ public class RakClientOfflineHandler extends SimpleChannelInboundHandler retryFuture; private RakOfflineState state = RakOfflineState.HANDSHAKE_1; - private int connectionAttempts; + private int connectionAttempts = 0; private int cookie; private boolean security; @@ -169,7 +169,11 @@ private void onOpenConnectionReply1(ChannelHandlerContext ctx, ByteBuf buffer) { private void onOpenConnectionReply2(ChannelHandlerContext ctx, ByteBuf buffer) { buffer.readLong(); // serverGuid - RakUtils.readAddress(buffer); // serverAddress + if (this.rakChannel.config().getOption(RakChannelOption.RAK_COMPATIBILITY_MODE)) { + RakUtils.skipAddress(buffer); // serverAddress + } else { + RakUtils.readAddress(buffer); // serverAddress + } int mtu = buffer.readShort(); boolean security = buffer.readBoolean(); // security if (security) { @@ -182,11 +186,8 @@ private void onOpenConnectionReply2(ChannelHandlerContext ctx, ByteBuf buffer) { } private void sendOpenConnectionRequest1(Channel channel) { - int mtuDiff = (MAXIMUM_MTU_SIZE - MINIMUM_MTU_SIZE) / 9; - int mtuSize = this.rakChannel.config().getOption(RakChannelOption.RAK_MTU) - (this.connectionAttempts * mtuDiff); - if (mtuSize < MINIMUM_MTU_SIZE) { - mtuSize = MINIMUM_MTU_SIZE; - } + int mtuSizeIndex = Math.min(this.connectionAttempts / 4, this.rakChannel.config().getOption(RakChannelOption.RAK_MTU_SIZES).length - 1); + int mtuSize = this.rakChannel.config().getOption(RakChannelOption.RAK_MTU_SIZES)[mtuSizeIndex]; ByteBuf magicBuf = this.rakChannel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); int rakVersion = this.rakChannel.config().getOption(RakChannelOption.RAK_PROTOCOL_VERSION); diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandler.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandler.java index a78ad0c..7ebd9a4 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandler.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandler.java @@ -90,28 +90,34 @@ protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket messag private void onConnectionRequestAccepted(ChannelHandlerContext ctx, ByteBuf buf) { buf.skipBytes(1); - RakUtils.readAddress(buf); // Client address + + boolean compatibilityMode = this.rakChannel.config().getOption(RakChannelOption.RAK_COMPATIBILITY_MODE); + if (compatibilityMode) { + RakUtils.skipAddress(buf); // Client address + } else { + RakUtils.readAddress(buf); // Client address + } + buf.readUnsignedShort(); // System index // Address + 2 * Long - Minimum amount of data int required = IPV4_MESSAGE_SIZE + 16; - int count = 0; + long pingTime = 0; - try { - while (buf.isReadable(required)) { + while (buf.isReadable(required)) { + if (compatibilityMode) { + RakUtils.skipAddress(buf); + } else { RakUtils.readAddress(buf); - count++; } - pingTime = buf.readLong(); - buf.readLong(); - } catch (IndexOutOfBoundsException ignored) { - // Hive sends malformed IPv6 address } + pingTime = buf.readLong(); + buf.readLong(); ByteBuf buffer = ctx.alloc().ioBuffer(); buffer.writeByte(ID_NEW_INCOMING_CONNECTION); RakUtils.writeAddress(buffer, (InetSocketAddress) ctx.channel().remoteAddress()); - for (int i = 0; i < count; i++) { + for (int i = 0; i < this.rakChannel.config().getOption(RakChannelOption.RAK_CLIENT_INTERNAL_ADDRESSES); i++) { RakUtils.writeAddress(buffer, LOCAL_ADDRESS); } buffer.writeLong(pingTime); diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/util/IpDontFragmentProvider.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/util/IpDontFragmentProvider.java new file mode 100644 index 0000000..52b4d95 --- /dev/null +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/util/IpDontFragmentProvider.java @@ -0,0 +1,53 @@ +package org.cloudburstmc.netty.util; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelOption; +import io.netty.channel.socket.nio.NioChannelOption; +import io.netty.channel.unix.IntegerUnixChannelOption; + +import java.lang.reflect.Field; +import java.net.SocketOption; + +public class IpDontFragmentProvider { + private static final ChannelOption IP_DONT_FRAGMENT_OPTION; + private static final Object IP_DONT_FRAGMENT_TRUE_VALUE; + private static final Object IP_DONT_FRAGMENT_FALSE_VALUE; + + static { + ChannelOption ipDontFragmentOption = null; + Object ipDontFragmentTrueValue = null; + Object ipDontFragmentFalseValue = null; + + setterBlock: { + // Windows and Linux Compatible (Java 19+) + try { + Class c = Class.forName("jdk.net.ExtendedSocketOptions"); + Field f = c.getField("IP_DONTFRAGMENT"); + + ipDontFragmentOption = NioChannelOption.of((SocketOption) f.get(null)); + ipDontFragmentTrueValue = true; + ipDontFragmentFalseValue = false; + break setterBlock; + } catch (ClassNotFoundException | NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + + } + + // Unix Compatible (Java 8+) + ipDontFragmentOption = new IntegerUnixChannelOption("IP_DONTFRAG", 0 /* IPPROTO_IP */, 10 /* IP_MTU_DISCOVER */); + ipDontFragmentTrueValue = 2 /* IP_PMTUDISC_DO */; + ipDontFragmentFalseValue = 0 /* IP_PMTUDISC_DONT */; + break setterBlock; + } + + IP_DONT_FRAGMENT_OPTION = ipDontFragmentOption; + IP_DONT_FRAGMENT_TRUE_VALUE = ipDontFragmentTrueValue; + IP_DONT_FRAGMENT_FALSE_VALUE = ipDontFragmentFalseValue; + } + + @SuppressWarnings("unchecked") + public static boolean trySet(Channel channel, boolean value) { + if (IP_DONT_FRAGMENT_OPTION == null) return false; + boolean success = channel.config().setOption((ChannelOption) IP_DONT_FRAGMENT_OPTION, (T) (value ? IP_DONT_FRAGMENT_TRUE_VALUE : IP_DONT_FRAGMENT_FALSE_VALUE)); + return success ? value : !value; + } +} \ No newline at end of file diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/util/RakUtils.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/util/RakUtils.java index 5fee28d..cf548e4 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/util/RakUtils.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/util/RakUtils.java @@ -96,6 +96,25 @@ public static InetSocketAddress readAddress(ByteBuf buffer) { return new InetSocketAddress(address, port); } + public static boolean skipAddress(ByteBuf buffer) { + short type = buffer.readByte(); + try { + if (type == 4) { + // Skip 4 + 2 bytes + buffer.skipBytes(6); + } else if (type == 6) { + // Skip 2 + 2 + 4 + 16 + 4 bytes + buffer.skipBytes(28); + } else { + // Vanilla client skips over if the type is not 4 or 6 + return false; + } + } catch (IndexOutOfBoundsException e) { + return false; + } + return true; + } + public static void writeAddress(ByteBuf buffer, InetSocketAddress address) { byte[] addressBytes = address.getAddress().getAddress(); if (address.getAddress() instanceof Inet4Address) { diff --git a/transport-raknet/src/test/java/org/cloudburstmc/netty/RakTests.java b/transport-raknet/src/test/java/org/cloudburstmc/netty/RakTests.java index 2b54acb..9dac63b 100644 --- a/transport-raknet/src/test/java/org/cloudburstmc/netty/RakTests.java +++ b/transport-raknet/src/test/java/org/cloudburstmc/netty/RakTests.java @@ -178,6 +178,25 @@ protected void initChannel(RakClientChannel ch) throws Exception { .channel(); } + @Test + public void testCompatibleClientConnect() { + int mtu = RakConstants.MAXIMUM_MTU_SIZE; + System.out.println("Testing client with MTU " + mtu); + + Channel channel = clientBootstrap(mtu) + .option(RakChannelOption.RAK_COMPATIBILITY_MODE, true) + .option(RakChannelOption.RAK_GUID, ThreadLocalRandom.current().nextLong()) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(RakClientChannel ch) throws Exception { + System.out.println("Client channel initialized"); + } + }) + .connect(new InetSocketAddress("127.0.0.1", 19132)) + .awaitUninterruptibly() + .channel(); + } + @ParameterizedTest @MethodSource("validMtu")