Skip to content

Commit

Permalink
Support cookies (#38)
Browse files Browse the repository at this point in the history
* Support cookies

Signed-off-by: Joshua Castle <[email protected]>

* Address existing GH reviews

- Add RAK_SEND_COOKIE to DefaultRakServerConfig#getOptions()
- Consolidate RakClientOfflineHandler#sendOpenConnectionRequest2 to single method
- Combine pendingConnections ExpiringMap

Signed-off-by: GitHub <[email protected]>

* Remove unused packages

Signed-off-by: Joshua Castle <[email protected]>

* Create provider for preferred SecureRandom algorithm

Signed-off-by: Joshua Castle <[email protected]>

* preferredAlgorithms in block

* Why we should not do web edits

Signed-off-by: Joshua Castle <[email protected]>

---------

Signed-off-by: Joshua Castle <[email protected]>
Signed-off-by: GitHub <[email protected]>
  • Loading branch information
Kas-tle authored Apr 16, 2024
1 parent 04d4c83 commit 730acaa
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package org.cloudburstmc.netty.channel.raknet;

import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelPipeline;
import org.cloudburstmc.netty.channel.raknet.config.RakChannelConfig;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class DefaultRakServerConfig extends DefaultChannelConfig implements RakS
private volatile int packetLimit = RakConstants.DEFAULT_PACKET_LIMIT;
private volatile int globalPacketLimit = RakConstants.DEFAULT_GLOBAL_PACKET_LIMIT;
private volatile RakServerMetrics metrics;
private volatile boolean sendCookie;

public DefaultRakServerConfig(RakServerChannel channel) {
super(channel);
Expand All @@ -56,7 +57,8 @@ public Map<ChannelOption<?>, Object> getOptions() {
return 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_SERVER_METRICS);
RakChannelOption.RAK_ADVERTISEMENT, RakChannelOption.RAK_HANDLE_PING, RakChannelOption.RAK_PACKET_LIMIT, RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, RakChannelOption.RAK_SEND_COOKIE,
RakChannelOption.RAK_SERVER_METRICS);
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -98,6 +100,9 @@ public <T> T getOption(ChannelOption<T> option) {
if (option == RakChannelOption.RAK_SERVER_METRICS) {
return (T) this.getMetrics();
}
if (option == RakChannelOption.RAK_SEND_COOKIE) {
return (T) Boolean.valueOf(this.sendCookie);
}
return this.channel.parent().config().getOption(option);
}

Expand Down Expand Up @@ -127,6 +132,8 @@ public <T> boolean setOption(ChannelOption<T> option, T value) {
this.setPacketLimit((Integer) value);
} else if (option == RakChannelOption.RAK_GLOBAL_PACKET_LIMIT) {
this.setGlobalPacketLimit((Integer) value);
} else if (option == RakChannelOption.RAK_SEND_COOKIE) {
this.sendCookie = (Boolean) value;
} else if (option == RakChannelOption.RAK_SERVER_METRICS) {
this.setMetrics((RakServerMetrics) value);
} else{
Expand Down Expand Up @@ -265,6 +272,16 @@ public void setGlobalPacketLimit(int globalPacketLimit) {
this.globalPacketLimit = globalPacketLimit;
}

@Override
public void setSendCookie(boolean sendCookie) {
this.sendCookie = sendCookie;
}

@Override
public boolean getSendCookie() {
return this.sendCookie;
}

@Override
public void setMetrics(RakServerMetrics metrics) {
this.metrics = metrics;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ public class RakChannelOption<T> extends ChannelOption<T> {
public static final ChannelOption<Integer> RAK_GLOBAL_PACKET_LIMIT =
valueOf(RakChannelOption.class, "RAK_GLOBAL_PACKET_LIMIT");

/**
* 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");

@SuppressWarnings("deprecation")
protected RakChannelOption() {
super(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public interface RakServerChannelConfig extends ChannelConfig {

void setGlobalPacketLimit(int limit);

void setSendCookie(boolean sendCookie);

boolean getSendCookie();

void setMetrics(RakServerMetrics metrics);

RakServerMetrics getMetrics();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.*;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.util.concurrent.ScheduledFuture;
import org.cloudburstmc.netty.channel.raknet.RakChannel;
Expand All @@ -45,6 +44,8 @@ public class RakClientOfflineHandler extends SimpleChannelInboundHandler<ByteBuf

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

public RakClientOfflineHandler(RakChannel rakChannel, ChannelPromise promise) {
this.rakChannel = rakChannel;
Expand Down Expand Up @@ -153,11 +154,11 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Excep
private void onOpenConnectionReply1(ChannelHandlerContext ctx, ByteBuf buffer) {
long serverGuid = buffer.readLong();
boolean security = buffer.readBoolean();
int mtu = buffer.readShort();
if (security) {
this.successPromise.tryFailure(new SecurityException());
return;
this.cookie = buffer.readInt();
this.security = true;
}
int mtu = buffer.readShort();

this.rakChannel.config().setOption(RakChannelOption.RAK_MTU, mtu);
this.rakChannel.config().setOption(RakChannelOption.RAK_REMOTE_GUID, serverGuid);
Expand All @@ -170,7 +171,11 @@ private void onOpenConnectionReply2(ChannelHandlerContext ctx, ByteBuf buffer) {
buffer.readLong(); // serverGuid
RakUtils.readAddress(buffer); // serverAddress
int mtu = buffer.readShort();
buffer.readBoolean(); // security
boolean security = buffer.readBoolean(); // security
if (security) {
this.successPromise.tryFailure(new SecurityException());
return;
}

this.rakChannel.config().setOption(RakChannelOption.RAK_MTU, mtu);
this.state = RakOfflineState.HANDSHAKE_COMPLETED;
Expand Down Expand Up @@ -200,9 +205,13 @@ private void sendOpenConnectionRequest2(Channel channel) {
int mtuSize = this.rakChannel.config().getOption(RakChannelOption.RAK_MTU);
ByteBuf magicBuf = this.rakChannel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC);

ByteBuf request = channel.alloc().ioBuffer(34);
ByteBuf request = channel.alloc().ioBuffer(this.security ? 39 : 34);
request.writeByte(ID_OPEN_CONNECTION_REQUEST_2);
request.writeBytes(magicBuf, magicBuf.readerIndex(), magicBuf.readableBytes());
if (this.security) {
request.writeInt(this.cookie);
request.writeBoolean(false); // Client wrote challenge
}
RakUtils.writeAddress(request, (InetSocketAddress) channel.remoteAddress());
request.writeShort(mtuSize);
request.writeLong(this.rakChannel.config().getOption(RakChannelOption.RAK_GUID));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@


import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
Expand All @@ -32,7 +31,6 @@
import org.cloudburstmc.netty.util.RakUtils;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;

import static org.cloudburstmc.netty.channel.raknet.RakConstants.*;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.cloudburstmc.netty.channel.raknet.RakChildChannel;
import org.cloudburstmc.netty.channel.raknet.RakConstants;
import org.cloudburstmc.netty.channel.raknet.RakPing;
import org.cloudburstmc.netty.channel.raknet.RakServerChannel;
import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption;
import org.cloudburstmc.netty.channel.raknet.config.RakServerMetrics;
import org.cloudburstmc.netty.handler.codec.raknet.AdvancedChannelInboundHandler;
import org.cloudburstmc.netty.util.RakUtils;
import org.cloudburstmc.netty.util.SecureAlgorithmProvider;

import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

Expand All @@ -46,7 +48,15 @@ public class RakServerOfflineHandler extends AdvancedChannelInboundHandler<Datag

private static final InternalLogger log = InternalLoggerFactory.getInstance(RakServerOfflineHandler.class);

private final ExpiringMap<InetSocketAddress, Integer> pendingConnections = ExpiringMap.builder()
private final ThreadLocal<SecureRandom> random = ThreadLocal.withInitial(() -> {
try {
return SecureRandom.getInstance(SecureAlgorithmProvider.getSecurityAlgorithm());
} catch (NoSuchAlgorithmException e) {
return new SecureRandom();
}
});

private final ExpiringMap<InetSocketAddress, PendingConnection> pendingConnections = ExpiringMap.builder()
.expiration(10, TimeUnit.SECONDS)
.expirationPolicy(ExpirationPolicy.CREATED)
.expirationListener((key, value) -> ReferenceCountUtil.release(value))
Expand Down Expand Up @@ -163,16 +173,31 @@ private void onOpenConnectionRequest1(ChannelHandlerContext ctx, DatagramPacket
// TODO: banned address check?
// TODO: max connections check?

Integer version = this.pendingConnections.put(sender, protocolVersion);
if (version != null && log.isTraceEnabled()) {

boolean sendCookie = ctx.channel().config().getOption(RakChannelOption.RAK_SEND_COOKIE);
int cookie;

if (sendCookie) {
cookie = this.random.get().nextInt();
} else {
cookie = 0;
}

PendingConnection connection = this.pendingConnections.put(sender, new PendingConnection(protocolVersion, cookie));
if (connection != null && log.isTraceEnabled()) {
log.trace("Received duplicate open connection request 1 from {}", sender);
}

ByteBuf replyBuffer = ctx.alloc().ioBuffer(28, 28);
int bufferCapacity = sendCookie ? 32 : 28; // 4 byte cookie

ByteBuf replyBuffer = ctx.alloc().ioBuffer(bufferCapacity, bufferCapacity);
replyBuffer.writeByte(ID_OPEN_CONNECTION_REPLY_1);
replyBuffer.writeBytes(magicBuf, magicBuf.readerIndex(), magicBuf.readableBytes());
replyBuffer.writeLong(guid);
replyBuffer.writeBoolean(false); // Security
replyBuffer.writeBoolean(sendCookie); // Security
if (sendCookie) {
replyBuffer.writeInt(cookie);
}
replyBuffer.writeShort(RakUtils.clamp(mtu, ctx.channel().config().getOption(RakChannelOption.RAK_MIN_MTU), ctx.channel().config().getOption(RakChannelOption.RAK_MAX_MTU)));
ctx.writeAndFlush(new DatagramPacket(replyBuffer, sender));
}
Expand All @@ -183,18 +208,31 @@ private void onOpenConnectionRequest2(ChannelHandlerContext ctx, DatagramPacket
// Skip already verified magic
buffer.skipBytes(magicBuf.readableBytes());

Integer version = this.pendingConnections.remove(sender);
if (version == null) {
// We can't determine the version without the previous request, so assume it's the wrong version.

PendingConnection connection = this.pendingConnections.remove(sender);
if (connection == null) {
if (log.isTraceEnabled()) {
log.trace("Received open connection request 2 from {} without open connection request 1", sender);
}
int[] supportedProtocols = ctx.channel().config().getOption(RakChannelOption.RAK_SUPPORTED_PROTOCOLS);
int latestVersion = supportedProtocols == null ? RakConstants.RAKNET_PROTOCOL_VERSION : supportedProtocols[supportedProtocols.length - 1];
this.sendIncompatibleVersion(ctx, sender, latestVersion, magicBuf, guid);
// Don't respond yet as we cannot verify the connection source IP
return;
}

boolean sendCookie = ctx.channel().config().getOption(RakChannelOption.RAK_SEND_COOKIE);
if (sendCookie) {
int cookie = buffer.readInt();
int expectedCookie = connection.getCookie();
if (expectedCookie != cookie) {
if (log.isTraceEnabled()) {
log.trace("Received open connection request 2 from {} with invalid cookie (expected {}, but received {})", sender, expectedCookie, cookie);
}
// Incorrect cookie provided
// This is likely source IP spoofing so we will not reply
return;
}
buffer.readBoolean(); // Client wrote challenge
}

// TODO: Verify serverAddress matches?
InetSocketAddress serverAddress = RakUtils.readAddress(buffer);
int mtu = buffer.readUnsignedShort();
Expand All @@ -207,7 +245,7 @@ private void onOpenConnectionRequest2(ChannelHandlerContext ctx, DatagramPacket
}

RakServerChannel serverChannel = (RakServerChannel) ctx.channel();
RakChildChannel channel = serverChannel.createChildChannel(sender, clientGuid, version, mtu);
RakChildChannel channel = serverChannel.createChildChannel(sender, clientGuid, connection.getProtocolVersion(), mtu);
if (channel == null) {
// Already connected
this.sendAlreadyConnected(ctx, sender, magicBuf, guid);
Expand Down Expand Up @@ -240,4 +278,22 @@ private void sendAlreadyConnected(ChannelHandlerContext ctx, InetSocketAddress s
buffer.writeLong(guid);
ctx.writeAndFlush(new DatagramPacket(buffer, sender));
}

private class PendingConnection {
private final int protocolVersion;
private final int cookie;

public PendingConnection(int protocolVersion, int cookie) {
this.protocolVersion = protocolVersion;
this.cookie = cookie;
}

public int getProtocolVersion() {
return this.protocolVersion;
}

public int getCookie() {
return this.cookie;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public static InetSocketAddress readAddress(ByteBuf buffer) {
int scopeId = buffer.readInt();
address = Inet6Address.getByAddress(null, addressBytes, scopeId);
} else {
throw new UnsupportedOperationException("Unknown Internet Protocol version.");
throw new UnsupportedOperationException("Unknown Internet Protocol version. Expected 4 or 6, got " + type);
}
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.cloudburstmc.netty.util;

import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class SecureAlgorithmProvider {
private static final String SECURITY_ALGORITHM;

static {
// SecureRandom algorithms in order of most preferred to least preferred.
final List<String> preferredAlgorithms = Arrays.asList(
"SHA1PRNG",
"NativePRNGNonBlocking",
"Windows-PRNG",
"NativePRNG",
"PKCS11",
"DRBG",
"NativePRNGBlocking"
);

SECURITY_ALGORITHM = Stream.of(Security.getProviders())
.flatMap(provider -> provider.getServices().stream())
.filter(service -> "SecureRandom".equals(service.getType()))
.map(Provider.Service::getAlgorithm)
.filter(preferredAlgorithms::contains)
.min((s1, s2) -> Integer.compare(preferredAlgorithms.indexOf(s1), preferredAlgorithms.indexOf(s2)))
.orElse(new SecureRandom().getAlgorithm());
}

public static String getSecurityAlgorithm() {
return SECURITY_ALGORITHM;
}
}
Loading

0 comments on commit 730acaa

Please sign in to comment.