Skip to content

Commit

Permalink
Add Compatibility Mode (#43)
Browse files Browse the repository at this point in the history
Adds a compatibility mode option to prevent crashes and disconnections by bringing behavior closer in line with the vanilla client. Closes #41; Closes #34; Closes #33; and Closes #30.
  • Loading branch information
Alemiz112 authored Jul 8, 2024
2 parents f408c9a + 5f13c77 commit bcf0bbd
Show file tree
Hide file tree
Showing 14 changed files with 240 additions and 28 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ subprojects {
options.encoding = "UTF-8"
}
named<Test>("test") {
minHeapSize = "512m"
maxHeapSize = "1024m"
jvmArgs = listOf("-XX:MaxMetaspaceSize=512m")
useJUnitPlatform()
}
}
Expand Down
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand All @@ -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]
Expand Down
9 changes: 2 additions & 7 deletions transport-raknet/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +32,7 @@

public class RakClientChannel extends ProxyChannel<DatagramChannel> implements RakChannel {

private static final InternalLogger log = InternalLoggerFactory.getInstance(RakClientChannel.class);
private static final ChannelMetadata metadata = new ChannelMetadata(true);

/**
Expand All @@ -41,6 +44,7 @@ public class RakClientChannel extends ProxyChannel<DatagramChannel> 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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -35,14 +37,21 @@ 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);
}

@Override
public Map<ChannelOption<?>, 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")
Expand All @@ -56,6 +65,14 @@ public <T> T getOption(ChannelOption<T> 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);
}
Expand All @@ -76,6 +93,18 @@ public <T> boolean setOption(ChannelOption<T> 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);
}
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -58,7 +61,7 @@ public Map<ChannelOption<?>, 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")
Expand Down Expand Up @@ -103,6 +106,9 @@ public <T> T getOption(ChannelOption<T> 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);
}

Expand Down Expand Up @@ -133,10 +139,13 @@ public <T> boolean setOption(ChannelOption<T> 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;
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,36 @@ public class RakChannelOption<T> extends ChannelOption<T> {
public static final ChannelOption<Integer> 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<Boolean> 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<Boolean> 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<Integer[]> 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<Boolean> 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<Integer> RAK_CLIENT_INTERNAL_ADDRESSES =
valueOf(RakChannelOption.class, "RAK_CLIENT_INTERNAL_ADDRESSES");

@SuppressWarnings("deprecation")
protected RakChannelOption() {
super(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,8 @@ public interface RakServerChannelConfig extends ChannelConfig {
void setMetrics(RakServerMetrics metrics);

RakServerMetrics getMetrics();

void setIpDontFragment(boolean ipDontFragment);

boolean getIpDontFragment();
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class RakClientOfflineHandler extends SimpleChannelInboundHandler<ByteBuf
private ScheduledFuture<?> retryFuture;

private RakOfflineState state = RakOfflineState.HANDSHAKE_1;
private int connectionAttempts;
private int connectionAttempts = 0;
private int cookie;
private boolean security;

Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit bcf0bbd

Please sign in to comment.