├── src └── main │ ├── java │ └── org │ │ └── nukkit │ │ └── raknetty │ │ ├── package-info.java │ │ ├── handler │ │ ├── codec │ │ │ ├── package-info.java │ │ │ ├── ReliabilityMessage.java │ │ │ ├── offline │ │ │ │ ├── UnconnectedPingOpenConnections.java │ │ │ │ ├── OfflinePingResponse.java │ │ │ │ ├── ConnectionAttemptFailed.java │ │ │ │ ├── DefaultOfflinePingResponse.java │ │ │ │ ├── NoFreeIncomingConnection.java │ │ │ │ ├── AlreadyConnected.java │ │ │ │ ├── ConnectionBanned.java │ │ │ │ ├── IpRecentlyConnected.java │ │ │ │ ├── UnconnectedPing.java │ │ │ │ ├── IncompatibleProtocolVersion.java │ │ │ │ ├── OpenConnectionRequest1.java │ │ │ │ ├── OpenConnectionReply1.java │ │ │ │ ├── UnconnectedPong.java │ │ │ │ ├── OpenConnectionRequest2.java │ │ │ │ ├── OpenConnectionReply2.java │ │ │ │ ├── DefaultClientOfflineHandler.java │ │ │ │ ├── AbstractOfflineHandler.java │ │ │ │ └── DefaultServerOfflineHandler.java │ │ │ ├── ByteBufSerializable.java │ │ │ ├── OfflineMessageAdapter.java │ │ │ ├── Message.java │ │ │ ├── OfflineMessage.java │ │ │ ├── reliability │ │ │ │ ├── HeapedPacket.java │ │ │ │ ├── DisconnectionNotification.java │ │ │ │ ├── ConnectedPing.java │ │ │ │ ├── ConnectedPong.java │ │ │ │ ├── ConnectionRequest.java │ │ │ │ ├── DatagramHistory.java │ │ │ │ ├── NewIncomingConnection.java │ │ │ │ ├── ConnectionRequestAccepted.java │ │ │ │ ├── SplitPacketList.java │ │ │ │ ├── AcknowledgePacket.java │ │ │ │ ├── SlidingWindow.java │ │ │ │ ├── ReliabilityMessageHandler.java │ │ │ │ └── ReliabilityInboundHandler.java │ │ │ ├── AbstractInternalPacket.java │ │ │ ├── PacketPriority.java │ │ │ ├── DatagramHeader.java │ │ │ ├── PacketReliability.java │ │ │ └── InternalPacket.java │ │ └── ipfilter │ │ │ └── BannedIpFilter.java │ │ ├── channel │ │ ├── event │ │ │ ├── RakChannelEvent.java │ │ │ └── ReceiptAcknowledgeEvent.java │ │ ├── SharedChannelConfig.java │ │ ├── RakServerChannelConfig.java │ │ ├── AddressedOfflineMessage.java │ │ ├── RakChannelConfig.java │ │ ├── RakServerChannel.java │ │ ├── RakServerChannelOption.java │ │ ├── RakChannelOption.java │ │ ├── RakChannel.java │ │ ├── AbstractRakDatagramChannel.java │ │ ├── DefaultRakServerChannelConfig.java │ │ ├── nio │ │ │ ├── NioRakServerChannel.java │ │ │ └── NioRakChannel.java │ │ └── DefaultRakChannelConfig.java │ │ ├── example │ │ ├── ExampleBedrockPingResponse.java │ │ ├── RakNettyClient.java │ │ ├── RakNettyServer.java │ │ ├── TCPTest.java │ │ └── BedrockForwarder.java │ │ └── util │ │ └── RakNetUtil.java │ └── resources │ └── log4j2.xml ├── .gitignore ├── pom.xml └── README.md /src/main/java/org/nukkit/raknetty/package-info.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty; -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/package-info.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/event/RakChannelEvent.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel.event; 2 | 3 | public class RakChannelEvent { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/ReliabilityMessage.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | public interface ReliabilityMessage extends Message { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/UnconnectedPingOpenConnections.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | public class UnconnectedPingOpenConnections extends UnconnectedPing { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/ByteBufSerializable.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public interface ByteBufSerializable { 6 | 7 | void encode(ByteBuf buf) throws Exception; 8 | 9 | void decode(ByteBuf buf) throws Exception; 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 11 | .mvn/wrapper/maven-wrapper.jar 12 | .idea 13 | *.iml -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/OfflinePingResponse.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.nukkit.raknetty.channel.RakServerChannel; 5 | 6 | public interface OfflinePingResponse { 7 | 8 | ByteBuf get(RakServerChannel channel); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/OfflineMessageAdapter.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public abstract class OfflineMessageAdapter implements OfflineMessage { 6 | 7 | @Override 8 | public void encode(ByteBuf buf) throws Exception { 9 | 10 | } 11 | 12 | @Override 13 | public void decode(ByteBuf buf) throws Exception { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/Message.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public interface Message { 6 | 7 | int UDP_HEADER_SIZE = 28; 8 | 9 | int MESSAGE_HEADER_MAX_SIZE = 1 + 2 + 3 + 3 + 3 + 1 + 4 + 2 + 4; 10 | 11 | MessageIdentifier getId(); 12 | 13 | void encode(ByteBuf buf) throws Exception; 14 | 15 | void decode(ByteBuf buf) throws Exception; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/ConnectionAttemptFailed.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 4 | import org.nukkit.raknetty.handler.codec.OfflineMessageAdapter; 5 | 6 | public class ConnectionAttemptFailed extends OfflineMessageAdapter { 7 | 8 | @Override 9 | public MessageIdentifier getId() { 10 | return MessageIdentifier.ID_CONNECTION_ATTEMPT_FAILED; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/OfflineMessage.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | public interface OfflineMessage extends Message { 4 | 5 | byte[] OFFLINE_MESSAGE_DATA_ID = new byte[]{ 6 | (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, 7 | (byte) 0xFE, (byte) 0xFE, (byte) 0xFE, (byte) 0xFE, 8 | (byte) 0xFD, (byte) 0xFD, (byte) 0xFD, (byte) 0xFD, 9 | (byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0x78 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/SharedChannelConfig.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | import io.netty.channel.ChannelConfig; 4 | 5 | public interface SharedChannelConfig extends ChannelConfig { 6 | 7 | long getLocalGuid(); 8 | 9 | SharedChannelConfig setLocalGuid(long guid); 10 | 11 | int getMaximumNumberOfInternalIds(); 12 | 13 | SharedChannelConfig setMaximumNumberOfInternalIds(int numberOfInternalIds); 14 | 15 | int getRakNetProtocolVersion(); 16 | 17 | SharedChannelConfig setRakNetProtocolVersion(int protocolVersion); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/HeapedPacket.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import org.nukkit.raknetty.handler.codec.InternalPacket; 4 | 5 | public class HeapedPacket implements Comparable { 6 | 7 | int weight; 8 | InternalPacket packet; 9 | 10 | public HeapedPacket(int weight, InternalPacket packet) { 11 | this.weight = weight; 12 | this.packet = packet; 13 | } 14 | 15 | @Override 16 | public int compareTo(HeapedPacket o) { 17 | return Integer.compare(weight, o.weight); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/RakServerChannelConfig.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | import org.nukkit.raknetty.handler.codec.offline.OfflinePingResponse; 4 | 5 | public interface RakServerChannelConfig extends SharedChannelConfig { 6 | 7 | int getMaximumConnections(); 8 | 9 | RakServerChannelConfig setMaximumConnections(int maxConnections); 10 | 11 | int getMaximumMtuSize(); 12 | 13 | RakServerChannelConfig setMaximumMtuSize(int maxMtuSize); 14 | 15 | OfflinePingResponse getOfflinePingResponse(); 16 | 17 | RakServerChannelConfig setOfflinePingResponseBuilder(OfflinePingResponse response); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/AddressedOfflineMessage.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | import io.netty.channel.DefaultAddressedEnvelope; 4 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 5 | 6 | import java.net.InetSocketAddress; 7 | 8 | public class AddressedOfflineMessage extends DefaultAddressedEnvelope { 9 | public AddressedOfflineMessage(OfflineMessage message, InetSocketAddress recipient, InetSocketAddress sender) { 10 | super(message, recipient, sender); 11 | } 12 | 13 | public AddressedOfflineMessage(OfflineMessage message, InetSocketAddress recipient) { 14 | super(message, recipient); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/event/ReceiptAcknowledgeEvent.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel.event; 2 | 3 | public final class ReceiptAcknowledgeEvent extends RakChannelEvent { 4 | 5 | private final AcknowledgeState state; 6 | private final int receiptSerial; 7 | 8 | public ReceiptAcknowledgeEvent(AcknowledgeState state, int receiptSerial) { 9 | this.state = state; 10 | this.receiptSerial = receiptSerial; 11 | } 12 | 13 | public AcknowledgeState state() { 14 | return state; 15 | } 16 | 17 | public int receiptSerial() { 18 | return receiptSerial; 19 | } 20 | 21 | public enum AcknowledgeState { 22 | ACKED, 23 | LOSS 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/DisconnectionNotification.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 5 | import org.nukkit.raknetty.handler.codec.ReliabilityMessage; 6 | 7 | public class DisconnectionNotification implements ReliabilityMessage { 8 | 9 | @Override 10 | public MessageIdentifier getId() { 11 | return MessageIdentifier.ID_DISCONNECTION_NOTIFICATION; 12 | } 13 | 14 | @Override 15 | public void decode(ByteBuf buf) throws Exception { 16 | 17 | } 18 | 19 | @Override 20 | public void encode(ByteBuf buf) throws Exception { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/DefaultOfflinePingResponse.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import org.nukkit.raknetty.channel.RakServerChannel; 6 | 7 | import java.nio.charset.StandardCharsets; 8 | 9 | public class DefaultOfflinePingResponse implements OfflinePingResponse { 10 | 11 | protected final ByteBuf offlineData; 12 | 13 | public DefaultOfflinePingResponse(String message) { 14 | this.offlineData = Unpooled.buffer(); 15 | this.offlineData.writeCharSequence(message, StandardCharsets.UTF_8); 16 | } 17 | 18 | @Override 19 | public ByteBuf get(RakServerChannel channel) { 20 | return offlineData; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/RakChannelConfig.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | public interface RakChannelConfig extends SharedChannelConfig { 4 | 5 | int[] getConnectMtuSizes(); 6 | 7 | RakChannelConfig setConnectMtuSizes(int[] mtuSizes); 8 | 9 | int getConnectAttempts(); 10 | 11 | RakChannelConfig setConnectAttempts(int connectAttempts); 12 | 13 | int getConnectIntervalMillis(); 14 | 15 | RakChannelConfig setConnectIntervalMillis(int connectIntervalMillis); 16 | 17 | @Override 18 | int getConnectTimeoutMillis(); 19 | 20 | @Override 21 | RakChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis); 22 | 23 | int getTimeoutMillis(); 24 | 25 | RakChannelConfig setTimeoutMillis(int timeoutMillis); 26 | 27 | int getUnreliableTimeoutMillis(); 28 | 29 | RakChannelConfig setUnreliableTimeoutMillis(int unreliableTimeoutMillis); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/NoFreeIncomingConnection.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 5 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 6 | 7 | public class NoFreeIncomingConnection implements OfflineMessage { 8 | 9 | public long senderGuid; 10 | 11 | @Override 12 | public MessageIdentifier getId() { 13 | return MessageIdentifier.ID_NO_FREE_INCOMING_CONNECTIONS; 14 | } 15 | 16 | @Override 17 | public void encode(ByteBuf buf) { 18 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 19 | buf.writeLong(senderGuid); 20 | } 21 | 22 | @Override 23 | public void decode(ByteBuf buf) { 24 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 25 | senderGuid = buf.readLong(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/ConnectedPing.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.ReliabilityMessage; 7 | 8 | public class ConnectedPing implements ReliabilityMessage { 9 | 10 | public long pingTime; 11 | 12 | @Override 13 | public MessageIdentifier getId() { 14 | return MessageIdentifier.ID_CONNECTED_PING; 15 | } 16 | 17 | @Override 18 | public void encode(ByteBuf buf) { 19 | buf.writeLong(pingTime); 20 | } 21 | 22 | @Override 23 | public void decode(ByteBuf buf) { 24 | pingTime = buf.readLong(); 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return new ToStringBuilder(this) 30 | .append("pingTime", pingTime) 31 | .toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/AbstractInternalPacket.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public abstract class AbstractInternalPacket implements Cloneable { 6 | 7 | public PacketReliability reliability; 8 | public PacketPriority priority; 9 | public DatagramHeader header; 10 | 11 | public int headerLength; 12 | 13 | public int receiptSerial; 14 | public int timeSent; 15 | public long actionTime; 16 | public long retransmissionTime; 17 | public long creationTime = System.nanoTime(); 18 | 19 | public abstract int bodyLength(); 20 | 21 | public abstract void encode(ByteBuf buf) throws Exception; 22 | 23 | public abstract void decode(ByteBuf buf) throws Exception; 24 | 25 | @Override 26 | protected AbstractInternalPacket clone() throws CloneNotSupportedException { 27 | AbstractInternalPacket msg = (AbstractInternalPacket) super.clone(); 28 | msg.creationTime = System.nanoTime(); 29 | msg.header = header.clone(); 30 | return msg; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/AlreadyConnected.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 7 | 8 | public class AlreadyConnected implements OfflineMessage { 9 | 10 | public long senderGuid; 11 | 12 | @Override 13 | public MessageIdentifier getId() { 14 | return MessageIdentifier.ID_ALREADY_CONNECTED; 15 | } 16 | 17 | @Override 18 | public void encode(ByteBuf buf) { 19 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 20 | buf.writeLong(senderGuid); 21 | } 22 | 23 | @Override 24 | public void decode(ByteBuf buf) { 25 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 26 | senderGuid = buf.readLong(); 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return new ToStringBuilder(this) 32 | .append("senderGuid", senderGuid) 33 | .toString(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/ConnectionBanned.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 7 | 8 | public class ConnectionBanned implements OfflineMessage { 9 | 10 | public long senderGuid; 11 | 12 | @Override 13 | public MessageIdentifier getId() { 14 | return MessageIdentifier.ID_CONNECTION_BANNED; 15 | } 16 | 17 | @Override 18 | public void encode(ByteBuf buf) { 19 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 20 | buf.writeLong(senderGuid); 21 | } 22 | 23 | @Override 24 | public void decode(ByteBuf buf) { 25 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 26 | senderGuid = buf.readLong(); 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return new ToStringBuilder(this) 32 | .append("senderGuid", senderGuid) 33 | .toString(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/ConnectedPong.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.ReliabilityMessage; 7 | 8 | public class ConnectedPong implements ReliabilityMessage { 9 | 10 | public long pingTime; 11 | public long pongTime; 12 | 13 | @Override 14 | public MessageIdentifier getId() { 15 | return MessageIdentifier.ID_CONNECTED_PONG; 16 | } 17 | 18 | @Override 19 | public void encode(ByteBuf buf) { 20 | buf.writeLong(pingTime); 21 | buf.writeLong(pongTime); 22 | } 23 | 24 | @Override 25 | public void decode(ByteBuf buf) { 26 | pingTime = buf.readLong(); 27 | pongTime = buf.readLong(); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return new ToStringBuilder(this) 33 | .append("pingTime", pingTime) 34 | .append("pongTime", pongTime) 35 | .toString(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/IpRecentlyConnected.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 7 | 8 | public class IpRecentlyConnected implements OfflineMessage { 9 | 10 | public long senderGuid; 11 | 12 | @Override 13 | public MessageIdentifier getId() { 14 | return MessageIdentifier.ID_IP_RECENTLY_CONNECTED; 15 | } 16 | 17 | @Override 18 | public void encode(ByteBuf buf) { 19 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 20 | buf.writeLong(senderGuid); 21 | } 22 | 23 | @Override 24 | public void decode(ByteBuf buf) { 25 | buf.skipBytes(1); 26 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 27 | senderGuid = buf.readLong(); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return new ToStringBuilder(this) 33 | .append("senderGuid", senderGuid) 34 | .toString(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/RakServerChannel.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ServerChannel; 5 | import io.netty.channel.socket.DatagramChannel; 6 | import org.nukkit.raknetty.handler.codec.offline.OpenConnectionRequest2; 7 | import org.nukkit.raknetty.handler.ipfilter.BannedIpFilter; 8 | 9 | import java.net.InetSocketAddress; 10 | 11 | 12 | public interface RakServerChannel extends ServerChannel { 13 | 14 | void accept(ChannelHandlerContext ctx, OpenConnectionRequest2 request, InetSocketAddress remoteAddress); 15 | 16 | boolean allowNewConnections(); 17 | 18 | int numberOfConnections(); 19 | 20 | RakChannel getChildChannel(InetSocketAddress address); 21 | 22 | BannedIpFilter banList(); 23 | 24 | void removeChildChannel(InetSocketAddress address); 25 | 26 | //RakChannel getChannel(long guid); 27 | 28 | long localGuid(); 29 | 30 | long remoteGuid(); 31 | 32 | @Override 33 | RakServerChannelConfig config(); 34 | 35 | @Override 36 | InetSocketAddress localAddress(); 37 | 38 | @Override 39 | InetSocketAddress remoteAddress(); 40 | 41 | DatagramChannel udpChannel(); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/ConnectionRequest.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.ReliabilityMessage; 7 | 8 | public class ConnectionRequest implements ReliabilityMessage { 9 | 10 | public long clientGuid; 11 | public long requestTime; 12 | 13 | @Override 14 | public MessageIdentifier getId() { 15 | return MessageIdentifier.ID_CONNECTION_REQUEST; 16 | } 17 | 18 | @Override 19 | public void encode(ByteBuf buf) { 20 | buf.writeLong(clientGuid); 21 | buf.writeLong(requestTime); 22 | buf.writeBoolean(false); //TODO: security 23 | } 24 | 25 | @Override 26 | public void decode(ByteBuf buf) { 27 | clientGuid = buf.readLong(); 28 | requestTime = buf.readLong(); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return new ToStringBuilder(this) 34 | .append("clientGuid", clientGuid) 35 | .append("requestTime", requestTime) 36 | .toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/RakServerChannelOption.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | import io.netty.channel.ChannelOption; 4 | import org.nukkit.raknetty.handler.codec.offline.OfflinePingResponse; 5 | 6 | public class RakServerChannelOption extends ChannelOption { 7 | 8 | // Shared options 9 | public static final ChannelOption RAKNET_GUID 10 | = RakChannelOption.RAKNET_GUID; 11 | public static final ChannelOption RAKNET_NUMBER_OF_INTERNAL_IDS 12 | = RakChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS; 13 | public static final ChannelOption RAKNET_PROTOCOL_VERSION 14 | = RakChannelOption.RAKNET_PROTOCOL_VERSION; 15 | 16 | // Reactor options 17 | public static final ChannelOption RAKNET_MAX_CONNECTIONS 18 | = ChannelOption.valueOf("RAKNET_MAX_CONNECTION"); 19 | public static final ChannelOption RAKNET_MAX_MTU_SIZE 20 | = ChannelOption.valueOf("RAKNET_MAX_MTU_SIZE"); 21 | public static final ChannelOption RAKNET_OFFLINE_RESPONSE 22 | = ChannelOption.valueOf("RAKNET_OFFLINE_RESPONSE_BUILDER"); 23 | 24 | @SuppressWarnings("deprecation") 25 | protected RakServerChannelOption(String name) { 26 | super(null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/UnconnectedPing.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 7 | 8 | public class UnconnectedPing implements OfflineMessage { 9 | 10 | public long sendPingTime; 11 | public long senderGuid; 12 | 13 | @Override 14 | public MessageIdentifier getId() { 15 | return MessageIdentifier.ID_UNCONNECTED_PING; 16 | } 17 | 18 | @Override 19 | public void encode(ByteBuf buf) { 20 | buf.writeLong(sendPingTime); 21 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 22 | buf.writeLong(senderGuid); 23 | } 24 | 25 | @Override 26 | public void decode(ByteBuf buf) { 27 | sendPingTime = buf.readLong(); 28 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 29 | senderGuid = buf.readLong(); 30 | } 31 | 32 | 33 | @Override 34 | public String toString() { 35 | return new ToStringBuilder(this) 36 | .append("sendPingTime", sendPingTime) 37 | .append("senderGuid", senderGuid) 38 | .toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/IncompatibleProtocolVersion.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 7 | 8 | public class IncompatibleProtocolVersion implements OfflineMessage { 9 | 10 | public int protocol; 11 | public long senderGuid; 12 | 13 | @Override 14 | public MessageIdentifier getId() { 15 | return MessageIdentifier.ID_INCOMPATIBLE_PROTOCOL_VERSION; 16 | } 17 | 18 | @Override 19 | public void encode(ByteBuf buf) { 20 | buf.writeByte(protocol); 21 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 22 | buf.writeLong(senderGuid); 23 | } 24 | 25 | @Override 26 | public void decode(ByteBuf buf) { 27 | protocol = buf.readByte(); 28 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 29 | senderGuid = buf.readLong(); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return new ToStringBuilder(this) 35 | .append("protocol", protocol) 36 | .append("senderGuid", senderGuid) 37 | .toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/example/ExampleBedrockPingResponse.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.example; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import org.nukkit.raknetty.channel.RakServerChannel; 6 | import org.nukkit.raknetty.handler.codec.offline.OfflinePingResponse; 7 | import org.nukkit.raknetty.util.RakNetUtil; 8 | 9 | public class ExampleBedrockPingResponse implements OfflinePingResponse { 10 | 11 | private int numOfPlayers = -1; 12 | private ByteBuf data; 13 | //MCPE;Dedicated Server;448;1.17.10;0;10;11877191924423115074;Bedrock level;Survival;1;19132;19133; 14 | private static final String format = "MCPE;RakNetty Server;448;1.17.10;%d;%d;%d;Bedrock level;Survival;1;%d;0;"; 15 | 16 | @Override 17 | public ByteBuf get(RakServerChannel channel) { 18 | int numOfConnections = channel.numberOfConnections(); 19 | if (data == null) { 20 | data = Unpooled.buffer(); 21 | } 22 | 23 | if (numOfConnections != numOfPlayers) { 24 | data.clear(); 25 | int maxConnections = channel.config().getMaximumConnections(); 26 | long guid = channel.localGuid(); 27 | int port = channel.localAddress().getPort(); 28 | String msg = String.format(format, numOfConnections, maxConnections, guid, port); 29 | RakNetUtil.writeString(data, msg); 30 | } 31 | return data; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/PacketPriority.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | public enum PacketPriority { 4 | /** 5 | * The highest possible priority. These message trigger sends immediately, and are generally not buffered 6 | * or aggregated into a single datagram. 7 | */ 8 | IMMEDIATE_PRIORITY, 9 | 10 | /** 11 | * For every 2 {@link PacketPriority#IMMEDIATE_PRIORITY} messages, 1 {@link PacketPriority#HIGH_PRIORITY} 12 | * will be sent. 13 | * Messages at this priority and lower are buffered to be sent in groups at 10 millisecond intervals 14 | * to reduce UDP overhead and better measure congestion control. 15 | */ 16 | HIGH_PRIORITY, 17 | 18 | /** 19 | * For every 2 {@link PacketPriority#HIGH_PRIORITY} messages, 1 {@link PacketPriority#MEDIUM_PRIORITY} will be sent. 20 | * Messages at this priority and lower are buffered to be sent in groups at 10 millisecond intervals 21 | * to reduce UDP overhead and better measure congestion control. 22 | */ 23 | MEDIUM_PRIORITY, 24 | 25 | /** 26 | * For every 2 {@link PacketPriority#MEDIUM_PRIORITY} messages, 1 {@link PacketPriority#LOW_PRIORITY} will be sent. 27 | * Messages at this priority and lower are buffered to be sent in groups at 10 millisecond intervals 28 | * to reduce UDP overhead and better measure congestion control. 29 | */ 30 | LOW_PRIORITY, 31 | 32 | NUMBER_OF_PRIORITIES 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/RakChannelOption.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | import io.netty.channel.ChannelOption; 4 | 5 | public class RakChannelOption extends ChannelOption { 6 | 7 | // Shared options 8 | public static final ChannelOption RAKNET_GUID = ChannelOption.valueOf("RAKNET_GUID"); 9 | public static final ChannelOption RAKNET_NUMBER_OF_INTERNAL_IDS = ChannelOption.valueOf("RAKNET_NUMBER_OF_INTERNAL_IDS"); 10 | public static final ChannelOption RAKNET_PROTOCOL_VERSION = ChannelOption.valueOf("RAKNET_PROTOCOL_VERSION"); 11 | 12 | // Channel options 13 | public static final ChannelOption RAKNET_CONNECT_MTU_SIZES = ChannelOption.valueOf("RAKNET_CONNECT_MTU_SIZES"); 14 | public static final ChannelOption RAKNET_CONNECT_INTERVAL = ChannelOption.valueOf("RAKNET_CONNECT_INTERVAL"); 15 | public static final ChannelOption RAKNET_CONNECT_ATTEMPTS = ChannelOption.valueOf("RAKNET_CONNECT_ATTEMPTS"); 16 | public static final ChannelOption RAKNET_CONNECT_TIMEOUT = ChannelOption.valueOf("RAKNET_CONNECT_TIMEOUT"); 17 | public static final ChannelOption RAKNET_UNRELIABLE_TIMEOUT = ChannelOption.valueOf("RAKNET_UNRELIABLE_TIMEOUT"); 18 | public static final ChannelOption RAKNET_TIMEOUT = ChannelOption.valueOf("RAKNET_TIMEOUT"); 19 | 20 | 21 | @SuppressWarnings("deprecation") 22 | protected RakChannelOption() { 23 | super(null); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/OpenConnectionRequest1.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.Message; 6 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 7 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 8 | import org.nukkit.raknetty.util.RakNetUtil; 9 | 10 | public class OpenConnectionRequest1 implements OfflineMessage { 11 | 12 | public int protocol; 13 | public int mtuSize; 14 | 15 | @Override 16 | public MessageIdentifier getId() { 17 | return MessageIdentifier.ID_OPEN_CONNECTION_REQUEST_1; 18 | } 19 | 20 | @Override 21 | public void encode(ByteBuf buf) { 22 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 23 | buf.writeByte(protocol); 24 | RakNetUtil.padWithZero(buf, mtuSize - Message.UDP_HEADER_SIZE); 25 | } 26 | 27 | @Override 28 | public void decode(ByteBuf buf) { 29 | mtuSize = buf.readableBytes() + 1 + Message.UDP_HEADER_SIZE; 30 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 31 | protocol = buf.readByte(); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return new ToStringBuilder(this) 37 | .append("protocol", protocol) 38 | .append("mtuSize", mtuSize) 39 | .toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/OpenConnectionReply1.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 7 | 8 | public class OpenConnectionReply1 implements OfflineMessage { 9 | 10 | public long serverGuid; 11 | public final boolean hasSecurity = false; // TODO: implement security 12 | public int mtuSize; 13 | 14 | @Override 15 | public MessageIdentifier getId() { 16 | return MessageIdentifier.ID_OPEN_CONNECTION_REPLY_1; 17 | } 18 | 19 | @Override 20 | public void encode(ByteBuf buf) { 21 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 22 | buf.writeLong(serverGuid); 23 | buf.writeBoolean(hasSecurity); 24 | buf.writeShort(mtuSize); 25 | } 26 | 27 | @Override 28 | public void decode(ByteBuf buf) { 29 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 30 | serverGuid = buf.readLong(); 31 | buf.skipBytes(1); // TODO: implement security 32 | mtuSize = buf.readShort(); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return new ToStringBuilder(this) 38 | .append("serverGuid", serverGuid) 39 | .append("hasSecurity", hasSecurity) 40 | .append("mtuSize", mtuSize) 41 | .toString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/UnconnectedPong.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufUtil; 5 | import org.apache.commons.lang3.builder.ToStringBuilder; 6 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 7 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 8 | 9 | public class UnconnectedPong implements OfflineMessage { 10 | 11 | public long sendPingTime; 12 | public long senderGuid; 13 | public ByteBuf response; 14 | 15 | @Override 16 | public MessageIdentifier getId() { 17 | return MessageIdentifier.ID_UNCONNECTED_PONG; 18 | } 19 | 20 | @Override 21 | public void encode(ByteBuf buf) { 22 | buf.writeLong(sendPingTime); 23 | buf.writeLong(senderGuid); 24 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 25 | buf.writeBytes(response, response.readerIndex(), response.readableBytes()); 26 | } 27 | 28 | @Override 29 | public void decode(ByteBuf buf) { 30 | sendPingTime = buf.readLong(); 31 | senderGuid = buf.readLong(); 32 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 33 | response = buf.slice(); 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return new ToStringBuilder(this) 39 | .append("sendPingTime", sendPingTime) 40 | .append("senderGuid", senderGuid) 41 | .append("response", ByteBufUtil.prettyHexDump(response)) 42 | .toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/OpenConnectionRequest2.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 7 | import org.nukkit.raknetty.util.RakNetUtil; 8 | 9 | import java.net.InetSocketAddress; 10 | 11 | public class OpenConnectionRequest2 implements OfflineMessage { 12 | 13 | public InetSocketAddress serverAddress; 14 | public int mtuSize; 15 | public long clientGuid; 16 | 17 | @Override 18 | public MessageIdentifier getId() { 19 | return MessageIdentifier.ID_OPEN_CONNECTION_REQUEST_2; 20 | } 21 | 22 | @Override 23 | public void encode(ByteBuf buf) { 24 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 25 | RakNetUtil.writeAddress(buf, serverAddress); 26 | buf.writeShort(mtuSize); 27 | buf.writeLong(clientGuid); 28 | } 29 | 30 | @Override 31 | public void decode(ByteBuf buf) { 32 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 33 | serverAddress = RakNetUtil.readAddress(buf); 34 | mtuSize = buf.readShort(); 35 | clientGuid = buf.readLong(); 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return new ToStringBuilder(this) 41 | .append("serverAddress", serverAddress) 42 | .append("mtuSize", mtuSize) 43 | .append("clientGuid", clientGuid) 44 | .toString(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/DatagramHistory.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import java.util.HashMap; 4 | import java.util.LinkedList; 5 | import java.util.Map; 6 | 7 | public class DatagramHistory { 8 | 9 | public static final int DATAGRAM_MESSAGE_ID_ARRAY_LENGTH = 512; 10 | 11 | private int datagramHistoryIndex = 0; 12 | private final Map histories = new HashMap<>(); 13 | 14 | public Node get(int datagramNumber) { 15 | if (datagramNumber < datagramHistoryIndex) { 16 | return null; 17 | } 18 | 19 | return histories.get(datagramNumber); 20 | } 21 | 22 | public void remove(int datagramNumber) { 23 | histories.remove(datagramNumber); 24 | } 25 | 26 | public void add(int datagramNumber, int messageNumber) { 27 | Node node = histories.get(datagramNumber); 28 | 29 | if (node == null) { 30 | 31 | if (histories.size() > DATAGRAM_MESSAGE_ID_ARRAY_LENGTH) { 32 | histories.remove(datagramHistoryIndex); 33 | datagramHistoryIndex++; 34 | } 35 | 36 | long timeSent = System.nanoTime(); 37 | node = new Node(timeSent); 38 | histories.put(datagramNumber, node); 39 | } 40 | 41 | node.messages.add(messageNumber); 42 | } 43 | 44 | public static final class Node { 45 | public LinkedList messages = new LinkedList<>(); 46 | public long timeSent; 47 | 48 | public Node(long timeSent) { 49 | this.timeSent = timeSent; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/OpenConnectionReply2.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 7 | import org.nukkit.raknetty.util.RakNetUtil; 8 | 9 | import java.net.InetSocketAddress; 10 | 11 | public class OpenConnectionReply2 implements OfflineMessage { 12 | 13 | public long serverGuid; 14 | public InetSocketAddress clientAddress; 15 | public int mtuSize; 16 | public final boolean hasSecurity = false; // TODO: implement security 17 | 18 | @Override 19 | public MessageIdentifier getId() { 20 | return MessageIdentifier.ID_OPEN_CONNECTION_REPLY_2; 21 | } 22 | 23 | @Override 24 | public void encode(ByteBuf buf) { 25 | buf.writeBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID); 26 | buf.writeLong(serverGuid); 27 | RakNetUtil.writeAddress(buf, clientAddress); 28 | buf.writeShort(mtuSize); 29 | buf.writeBoolean(hasSecurity); 30 | } 31 | 32 | @Override 33 | public void decode(ByteBuf buf) { 34 | buf.skipBytes(OfflineMessage.OFFLINE_MESSAGE_DATA_ID.length); 35 | serverGuid = buf.readLong(); 36 | clientAddress = RakNetUtil.readAddress(buf); 37 | mtuSize = buf.readShort(); 38 | buf.skipBytes(1); // TODO: implement security 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return new ToStringBuilder(this) 44 | .append("serverGuid", serverGuid) 45 | .append("clientAddress", clientAddress) 46 | .append("mtuSize", mtuSize) 47 | .append("hasSecurity", hasSecurity) 48 | .toString(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/NewIncomingConnection.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.ReliabilityMessage; 7 | import org.nukkit.raknetty.util.RakNetUtil; 8 | 9 | import java.net.InetSocketAddress; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class NewIncomingConnection implements ReliabilityMessage { 14 | 15 | public InetSocketAddress serverAddress; 16 | public InetSocketAddress[] clientAddresses; 17 | public long pingTime; 18 | public long pongTime; 19 | 20 | @Override 21 | public MessageIdentifier getId() { 22 | return MessageIdentifier.ID_NEW_INCOMING_CONNECTION; 23 | } 24 | 25 | @Override 26 | public void encode(ByteBuf buf) { 27 | RakNetUtil.writeAddress(buf, serverAddress); 28 | for (int i = 0; i < clientAddresses.length; i++) { 29 | RakNetUtil.writeAddress(buf, clientAddresses[i]); 30 | } 31 | buf.writeLong(pingTime); 32 | buf.writeLong(pongTime); 33 | } 34 | 35 | @Override 36 | public void decode(ByteBuf buf) { 37 | serverAddress = RakNetUtil.readAddress(buf); 38 | List list = new ArrayList<>(); 39 | do { 40 | list.add(RakNetUtil.readAddress(buf)); 41 | } while (buf.readableBytes() > 16); 42 | clientAddresses = list.toArray(new InetSocketAddress[0]); 43 | pingTime = buf.readLong(); 44 | pongTime = buf.readLong(); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return new ToStringBuilder(this) 50 | .append("serverAddress", serverAddress) 51 | .append("clientAddresses", clientAddresses) 52 | .append("pingTime", pingTime) 53 | .append("pongTime", pongTime) 54 | .toString(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/ConnectionRequestAccepted.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.builder.ToStringBuilder; 5 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 6 | import org.nukkit.raknetty.handler.codec.ReliabilityMessage; 7 | import org.nukkit.raknetty.util.RakNetUtil; 8 | 9 | import java.net.InetSocketAddress; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class ConnectionRequestAccepted implements ReliabilityMessage { 14 | 15 | public InetSocketAddress clientAddress; 16 | public int systemIndex = 0; 17 | public InetSocketAddress[] ipList; 18 | public long requestTime; 19 | public long replyTime; 20 | 21 | @Override 22 | public MessageIdentifier getId() { 23 | return MessageIdentifier.ID_CONNECTION_REQUEST_ACCEPTED; 24 | } 25 | 26 | @Override 27 | public void encode(ByteBuf buf) { 28 | RakNetUtil.writeAddress(buf, clientAddress); 29 | buf.writeShort(systemIndex); 30 | for (int i = 0; i < ipList.length; i++) { 31 | RakNetUtil.writeAddress(buf, ipList[i]); 32 | } 33 | buf.writeLong(requestTime); 34 | buf.writeLong(replyTime); 35 | } 36 | 37 | @Override 38 | public void decode(ByteBuf buf) { 39 | clientAddress = RakNetUtil.readAddress(buf); 40 | systemIndex = buf.readShort(); 41 | List list = new ArrayList<>(); 42 | do { 43 | list.add(RakNetUtil.readAddress(buf)); 44 | } while (buf.readableBytes() > 16); 45 | ipList = list.toArray(new InetSocketAddress[0]); 46 | requestTime = buf.readLong(); 47 | replyTime = buf.readLong(); 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return new ToStringBuilder(this) 53 | .append("clientAddress", clientAddress) 54 | .append("systemIndex", systemIndex) 55 | .append("ipList", ipList) 56 | .append("requestTime", requestTime) 57 | .append("replyTime", replyTime) 58 | .toString(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/SplitPacketList.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.util.ReferenceCountUtil; 4 | import org.nukkit.raknetty.handler.codec.InternalPacket; 5 | 6 | import java.util.*; 7 | 8 | public class SplitPacketList { 9 | 10 | private final Map splitChannels = new HashMap<>(); 11 | 12 | public void insert(InternalPacket packet) { 13 | int splitPacketId = packet.splitPacketId; 14 | SplitPacketChannel channel = splitChannels.get(splitPacketId); 15 | 16 | if (channel == null) { 17 | channel = new SplitPacketChannel(); 18 | splitChannels.put(splitPacketId, channel); 19 | } 20 | 21 | channel.packets.put(packet.splitPacketIndex, packet); 22 | // TODO: implement Download Progress Event 23 | } 24 | 25 | public InternalPacket build(int splitPacketId) { 26 | SplitPacketChannel channel = splitChannels.get(splitPacketId); 27 | if (channel == null) return null; 28 | 29 | Collection packets = channel.packets.values(); 30 | if (packets.isEmpty()) return null; 31 | 32 | if (packets.size() == packets.stream().findFirst().get().splitPacketCount) { 33 | InternalPacket packet = build(channel); 34 | splitChannels.remove(splitPacketId); 35 | return packet; 36 | } 37 | 38 | return null; 39 | } 40 | 41 | private InternalPacket build(SplitPacketChannel channel) { 42 | // copy every bytes to the first packet 43 | Iterator iterator = channel.packets.values().iterator(); 44 | InternalPacket packet = iterator.next(); 45 | 46 | while (iterator.hasNext()) { 47 | InternalPacket p = iterator.next(); 48 | packet.data.writeBytes(p.data, p.bodyLength()); 49 | 50 | // release all the other packets except the first one 51 | ReferenceCountUtil.release(p); 52 | } 53 | 54 | return packet; 55 | } 56 | 57 | private static class SplitPacketChannel { 58 | public final Map packets = new TreeMap<>(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/RakChannel.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.socket.DatagramChannel; 7 | import org.nukkit.raknetty.channel.nio.NioRakChannel; 8 | import org.nukkit.raknetty.handler.codec.PacketPriority; 9 | import org.nukkit.raknetty.handler.codec.PacketReliability; 10 | import org.nukkit.raknetty.handler.codec.ReliabilityMessage; 11 | import org.nukkit.raknetty.handler.codec.reliability.SlidingWindow; 12 | 13 | import java.net.InetSocketAddress; 14 | 15 | 16 | public interface RakChannel extends Channel { 17 | 18 | boolean isClient(); 19 | 20 | long localGuid(); 21 | 22 | long remoteGuid(); 23 | 24 | int mtuSize(); 25 | 26 | NioRakChannel mtuSize(int mtuSize); 27 | 28 | ConnectMode connectMode(); 29 | 30 | RakChannel connectMode(ConnectMode mode); 31 | 32 | @Override 33 | RakServerChannel parent(); 34 | 35 | void ping(PacketReliability reliability); 36 | 37 | int averagePing(); 38 | 39 | void send(ReliabilityMessage message, PacketPriority priority, PacketReliability reliability, int orderingChannel); 40 | 41 | void send(ByteBuf message, PacketPriority priority, PacketReliability reliability, int orderingChannel); 42 | 43 | SlidingWindow slidingWindow(); 44 | 45 | @Override 46 | RakChannelConfig config(); 47 | 48 | @Override 49 | InetSocketAddress localAddress(); 50 | 51 | @Override 52 | InetSocketAddress remoteAddress(); 53 | 54 | DatagramChannel udpChannel(); 55 | 56 | enum ConnectMode { 57 | NO_ACTION( 58 | false, false), 59 | DISCONNECT_ASAP( 60 | true, false), 61 | DISCONNECT_ASAP_SILENTLY( 62 | true, false), 63 | DISCONNECT_ON_NO_ACK( 64 | true, false), 65 | REQUESTED_CONNECTION( 66 | false, true), 67 | HANDLING_CONNECTION_REQUEST( 68 | false, true), 69 | UNVERIFIED_SENDER( 70 | false, true), 71 | CONNECTED( 72 | true, true); 73 | 74 | private final boolean isConnected; 75 | private final boolean canDisconnect; 76 | 77 | ConnectMode(boolean isConnected, boolean canDisconnect) { 78 | this.isConnected = isConnected; 79 | this.canDisconnect = canDisconnect; 80 | } 81 | 82 | public boolean isConnected() { 83 | return isConnected; 84 | } 85 | 86 | public boolean canDisconnect() { 87 | return canDisconnect; 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/example/RakNettyClient.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.example; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.nio.NioEventLoopGroup; 6 | import io.netty.handler.logging.LogLevel; 7 | import io.netty.handler.logging.LoggingHandler; 8 | import io.netty.util.internal.logging.InternalLogger; 9 | import io.netty.util.internal.logging.InternalLoggerFactory; 10 | import org.apache.commons.lang3.builder.ToStringBuilder; 11 | import org.apache.commons.lang3.builder.ToStringStyle; 12 | import org.nukkit.raknetty.channel.RakChannelOption; 13 | import org.nukkit.raknetty.channel.nio.NioRakChannel; 14 | 15 | public class RakNettyClient { 16 | static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(RakNettyClient.class); 17 | 18 | static { 19 | ToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE); 20 | } 21 | 22 | public static void main(String... args) throws Exception { 23 | final NioEventLoopGroup workGroup = new NioEventLoopGroup(); 24 | 25 | // Configure the client. 26 | try { 27 | final Bootstrap boot = new Bootstrap(); 28 | boot.group(workGroup) 29 | .channel(NioRakChannel.class) 30 | .option(RakChannelOption.RAKNET_GUID, 654321L) 31 | // consist with the bedrock RakNet configuration 32 | .option(RakChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS, 20) 33 | .option(RakChannelOption.RAKNET_PROTOCOL_VERSION, 10) 34 | .option(RakChannelOption.RAKNET_CONNECT_INTERVAL, 500) 35 | .option(RakChannelOption.RAKNET_CONNECT_ATTEMPTS, 12) 36 | .handler(new LoggingHandler("Connection", LogLevel.INFO)); 37 | 38 | // Connect the client to the server 39 | final ChannelFuture future = boot.connect("localhost", 19132).sync(); 40 | NioRakChannel channel = (NioRakChannel) future.channel(); 41 | LOGGER.info("RakNetty client is connected successfully."); 42 | 43 | // Disconnect the client from the server after a few seconds 44 | Thread.sleep(10 * 1000); 45 | channel.disconnect().sync(); 46 | LOGGER.info("RakNetty client is disconnected."); 47 | 48 | // Wait until the client socket is closed. 49 | channel.closeFuture().sync(); 50 | LOGGER.info("RakNetty client is closed."); 51 | 52 | } finally { 53 | // Shut down all event loops to terminate all threads. 54 | workGroup.shutdownGracefully(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/example/RakNettyServer.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.example; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.nio.NioEventLoopGroup; 6 | import io.netty.handler.logging.LogLevel; 7 | import io.netty.handler.logging.LoggingHandler; 8 | import io.netty.util.concurrent.DefaultThreadFactory; 9 | import io.netty.util.internal.logging.InternalLogger; 10 | import io.netty.util.internal.logging.InternalLoggerFactory; 11 | import org.apache.commons.lang3.builder.ToStringBuilder; 12 | import org.apache.commons.lang3.builder.ToStringStyle; 13 | import org.nukkit.raknetty.channel.RakServerChannelOption; 14 | import org.nukkit.raknetty.channel.nio.NioRakServerChannel; 15 | 16 | import java.util.concurrent.ThreadFactory; 17 | 18 | public class RakNettyServer { 19 | 20 | static final int PORT = Integer.parseInt(System.getProperty("port", "19132")); 21 | static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(RakNettyServer.class); 22 | 23 | static { 24 | ToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE); 25 | } 26 | 27 | public static void main(String... args) throws Exception { 28 | final ThreadFactory acceptFactory = new DefaultThreadFactory("accept"); 29 | final ThreadFactory connectFactory = new DefaultThreadFactory("connect"); 30 | final NioEventLoopGroup acceptGroup = new NioEventLoopGroup(1, acceptFactory); 31 | final NioEventLoopGroup connectGroup = new NioEventLoopGroup(connectFactory); 32 | 33 | // Configure the server. 34 | try { 35 | final ServerBootstrap boot = new ServerBootstrap(); 36 | boot.group(acceptGroup, connectGroup) 37 | .channel(NioRakServerChannel.class) 38 | // reactor channel options 39 | .option(RakServerChannelOption.RAKNET_GUID, 123456L) 40 | .option(RakServerChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS, 20) 41 | .option(RakServerChannelOption.RAKNET_PROTOCOL_VERSION, 10) 42 | .option(RakServerChannelOption.RAKNET_MAX_CONNECTIONS, 15) 43 | .option(RakServerChannelOption.RAKNET_MAX_MTU_SIZE, 1400) 44 | .option(RakServerChannelOption.RAKNET_OFFLINE_RESPONSE, new ExampleBedrockPingResponse()) 45 | .handler(new LoggingHandler("Reactor", LogLevel.INFO)) 46 | .childHandler(new LoggingHandler("Connection", LogLevel.INFO)); 47 | 48 | // Start the server. 49 | final ChannelFuture future = boot.bind(PORT).sync(); 50 | final NioRakServerChannel channel = (NioRakServerChannel) future.channel(); 51 | 52 | // Wait until the server socket is closed. 53 | channel.closeFuture().sync(); 54 | } finally { 55 | // Shut down all event loops to terminate all threads. 56 | acceptGroup.shutdownGracefully(); 57 | connectGroup.shutdownGracefully(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/DefaultClientOfflineHandler.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.socket.DatagramPacket; 5 | import io.netty.util.internal.logging.InternalLogger; 6 | import io.netty.util.internal.logging.InternalLoggerFactory; 7 | import org.nukkit.raknetty.channel.AddressedOfflineMessage; 8 | import org.nukkit.raknetty.channel.RakChannel; 9 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 10 | import org.nukkit.raknetty.handler.codec.PacketPriority; 11 | import org.nukkit.raknetty.handler.codec.PacketReliability; 12 | import org.nukkit.raknetty.handler.codec.reliability.ConnectionRequest; 13 | 14 | import java.net.InetSocketAddress; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | public class DefaultClientOfflineHandler extends AbstractOfflineHandler { 18 | private final static InternalLogger LOGGER = InternalLoggerFactory.getInstance(DefaultClientOfflineHandler.class); 19 | public static final String NAME = "ClientOffline"; 20 | 21 | //private final SocketAddress remoteAddress; 22 | 23 | public DefaultClientOfflineHandler(RakChannel channel) { 24 | super(channel); 25 | } 26 | 27 | @Override 28 | public RakChannel channel() { 29 | return (RakChannel) super.channel(); 30 | } 31 | 32 | @Override 33 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 34 | 35 | if (msg instanceof DatagramPacket) { 36 | DatagramPacket packet = (DatagramPacket) msg; 37 | InetSocketAddress sender = packet.sender(); 38 | 39 | if (!sender.equals(channel().remoteAddress())) { 40 | LOGGER.debug("datagram from unknown sender: {}, expecting: {}", sender, channel().remoteAddress()); 41 | return; 42 | } 43 | } 44 | 45 | super.channelRead(ctx, msg); 46 | } 47 | 48 | @Override 49 | public void readOfflinePacket(ChannelHandlerContext ctx, OfflineMessage msg, InetSocketAddress sender) { 50 | 51 | if (channel().isActive() || channel().connectMode() == RakChannel.ConnectMode.REQUESTED_CONNECTION) { 52 | LOGGER.debug("offline message from a connected system: {}, discarding", sender); 53 | return; 54 | } 55 | 56 | long now = System.nanoTime(); 57 | //LOGGER.debug("READ: {}", msg); 58 | 59 | if (msg instanceof OpenConnectionReply1) { 60 | OpenConnectionReply1 in = (OpenConnectionReply1) msg; 61 | 62 | OpenConnectionRequest2 out = new OpenConnectionRequest2(); 63 | out.serverAddress = sender; 64 | out.mtuSize = in.mtuSize; 65 | out.clientGuid = channel().localGuid(); 66 | 67 | ctx.writeAndFlush(new AddressedOfflineMessage(out, sender)); 68 | 69 | } else if (msg instanceof OpenConnectionReply2) { 70 | OpenConnectionReply2 in = (OpenConnectionReply2) msg; 71 | channel().connectMode(RakChannel.ConnectMode.REQUESTED_CONNECTION); 72 | 73 | // update the channel's mtu size 74 | channel().mtuSize(in.mtuSize); 75 | 76 | ConnectionRequest out = new ConnectionRequest(); 77 | out.clientGuid = channel().localGuid(); 78 | out.requestTime = TimeUnit.NANOSECONDS.toMillis(now); 79 | 80 | channel().send(out, PacketPriority.IMMEDIATE_PRIORITY, PacketReliability.RELIABLE, 0); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | oss-parent 7 | org.nukkit 8 | 1.1 9 | 10 | 11 | raknetty 12 | 1.1-SNAPSHOT 13 | 14 | RakNetty 15 | https://nukkit.org/ 16 | RakNetty is a high performance framework for servers and clients using RakNet protocol. 17 | 18 | 19 | scm:git@github.com:NukkitReborn/raknetty.git 20 | scm:git:git@github.com:NukkitReborn/raknetty.git 21 | https://github.com/NukkitReborn/raknetty 22 | HEAD 23 | 24 | 25 | 26 | 27 | nukkit-releases 28 | https://nukkit.org/nexus/repository/maven-releases/ 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.nukkit 36 | nukkit-bom 37 | 1.1 38 | import 39 | pom 40 | 41 | 42 | 43 | 44 | 45 | 46 | io.netty 47 | netty-all 48 | 49 | 50 | 51 | org.apache.commons 52 | commons-lang3 53 | 54 | 55 | 56 | org.apache.logging.log4j 57 | log4j-core 58 | true 59 | 60 | 61 | 62 | org.apache.logging.log4j 63 | log4j-api 64 | true 65 | 66 | 67 | 68 | org.apache.logging.log4j 69 | log4j-slf4j-impl 70 | true 71 | 72 | 73 | 74 | org.slf4j 75 | slf4j-api 76 | true 77 | 78 | 79 | 80 | junit 81 | junit 82 | test 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-jar-plugin 91 | 92 | 93 | org/nukkit/raknetty/example/** 94 | log4j2.xml 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/example/TCPTest.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.example; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.channel.*; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.channel.socket.nio.NioSocketChannel; 12 | import io.netty.handler.logging.LogLevel; 13 | import io.netty.handler.logging.LoggingHandler; 14 | import io.netty.util.internal.logging.InternalLogger; 15 | import io.netty.util.internal.logging.InternalLoggerFactory; 16 | 17 | import java.nio.charset.StandardCharsets; 18 | 19 | public class TCPTest { 20 | 21 | static final int PORT = 25560; 22 | static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(TCPTest.class); 23 | 24 | public static void main(String[] args) throws Exception { 25 | startServer(); 26 | startClient(); 27 | } 28 | 29 | public static void startServer() throws Exception { 30 | // Configure the server. 31 | NioEventLoopGroup boss = new NioEventLoopGroup(); 32 | NioEventLoopGroup worker = new NioEventLoopGroup(); 33 | 34 | final ServerBootstrap boot = new ServerBootstrap(); 35 | boot.group(boss, worker) 36 | .channel(NioServerSocketChannel.class) 37 | .option(ChannelOption.SO_BACKLOG, 128) 38 | .childOption(ChannelOption.SO_KEEPALIVE, true) 39 | .handler(new LoggingHandler("ServerLogger", LogLevel.INFO)) 40 | .childHandler(new ChannelInitializer() { 41 | @Override 42 | public void initChannel(final SocketChannel ch) throws Exception { 43 | ch.pipeline().addLast(new LoggingHandler("ServerChannelLogger", LogLevel.INFO)); 44 | 45 | ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { 46 | @Override 47 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 48 | String message = ((ByteBuf) msg).toString(StandardCharsets.UTF_8); 49 | LOGGER.debug("MESSAGE: {}", msg); 50 | if (message.equals("CLOSE")) { 51 | ctx.channel().close(); 52 | } 53 | } 54 | }); 55 | } 56 | }); 57 | // Start the server. 58 | boot.bind(PORT).sync(); 59 | } 60 | 61 | public static void startClient() throws Exception { 62 | final Bootstrap boot = new Bootstrap(); 63 | NioEventLoopGroup group = new NioEventLoopGroup(); 64 | 65 | boot.group(group) 66 | .channel(NioSocketChannel.class) 67 | .option(ChannelOption.SO_KEEPALIVE, true) 68 | .handler(new ChannelInitializer() { 69 | @Override 70 | protected void initChannel(SocketChannel ch) throws Exception { 71 | ch.pipeline().addLast(new LoggingHandler("ClientChannelLogger", LogLevel.INFO)); 72 | } 73 | }); 74 | 75 | ChannelFuture f = boot 76 | .connect("127.0.0.1", PORT) 77 | .sync(); 78 | Channel ch = f.channel(); 79 | 80 | ch.writeAndFlush(Unpooled.copiedBuffer("MSG1", StandardCharsets.UTF_8)).sync(); 81 | Thread.sleep(200); 82 | ch.writeAndFlush(Unpooled.copiedBuffer("MSG2", StandardCharsets.UTF_8)).sync(); 83 | Thread.sleep(200); 84 | //ch.writeAndFlush(Unpooled.copiedBuffer("CLOSE", StandardCharsets.UTF_8)).sync(); 85 | 86 | ch.disconnect(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/ipfilter/BannedIpFilter.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.ipfilter; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelInboundHandlerAdapter; 5 | import io.netty.channel.socket.DatagramPacket; 6 | import io.netty.util.ReferenceCountUtil; 7 | import io.netty.util.internal.logging.InternalLogger; 8 | import io.netty.util.internal.logging.InternalLoggerFactory; 9 | import org.apache.commons.lang3.Validate; 10 | import org.nukkit.raknetty.channel.AddressedOfflineMessage; 11 | import org.nukkit.raknetty.channel.RakServerChannel; 12 | import org.nukkit.raknetty.handler.codec.offline.ConnectionBanned; 13 | 14 | import java.net.InetAddress; 15 | import java.net.InetSocketAddress; 16 | import java.util.Map; 17 | import java.util.concurrent.ConcurrentHashMap; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | public class BannedIpFilter extends ChannelInboundHandlerAdapter { 21 | 22 | private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(BannedIpFilter.class); 23 | 24 | private final Map banned = new ConcurrentHashMap<>(); 25 | private final RakServerChannel channel; 26 | 27 | public BannedIpFilter(RakServerChannel channel) { 28 | this.channel = channel; 29 | } 30 | 31 | @Override 32 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 33 | boolean release = true; 34 | 35 | try { 36 | if (msg instanceof DatagramPacket) { 37 | InetSocketAddress sender = ((DatagramPacket) msg).sender(); 38 | 39 | if (isBanned(sender)) { 40 | LOGGER.debug("discarding a datagram packet from {}: ip address is banned", sender); 41 | 42 | ConnectionBanned out = new ConnectionBanned(); 43 | out.senderGuid = channel.localGuid(); 44 | ctx.writeAndFlush(new AddressedOfflineMessage(out, sender)); 45 | return; 46 | } 47 | 48 | // if the address is not banned, proceed to the next handler 49 | release = false; 50 | ctx.fireChannelRead(msg); 51 | } 52 | } finally { 53 | if (release) { 54 | ReferenceCountUtil.release(msg); 55 | } 56 | } 57 | } 58 | 59 | public boolean isBanned(InetSocketAddress remoteAddress) { 60 | 61 | InetAddress address = remoteAddress.getAddress(); 62 | Long expiredTime = banned.get(address); 63 | 64 | if (expiredTime != null) { 65 | long currentTime = System.nanoTime(); 66 | 67 | if (expiredTime > 0 && currentTime - expiredTime > 0) { 68 | banned.remove(address); 69 | } else { 70 | // not expired or a permanent ban 71 | return true; 72 | } 73 | } 74 | 75 | return false; 76 | } 77 | 78 | /** 79 | * Bans an IP from connecting. Banned IPs persist between connections. 80 | * 81 | * @param remoteAddress address to be banned 82 | * @param milliseconds milliseconds for a temporary ban. Use 0 for permanent ban 83 | */ 84 | public void add(InetSocketAddress remoteAddress, long milliseconds) { 85 | 86 | Validate.notNull(remoteAddress, "The address to be banned is null"); 87 | Validate.isTrue(milliseconds >= 0, "A negative time is not allowed"); 88 | 89 | if (milliseconds == 0) { 90 | banned.put(remoteAddress.getAddress(), 0L); 91 | } else { 92 | long time = System.nanoTime(); 93 | banned.put(remoteAddress.getAddress(), time + TimeUnit.NANOSECONDS.convert(milliseconds, TimeUnit.MILLISECONDS)); 94 | } 95 | } 96 | 97 | public void remove(InetSocketAddress remoteAddress) { 98 | 99 | Validate.notNull(remoteAddress, "The address to be unbanned is null"); 100 | banned.remove(remoteAddress.getAddress()); 101 | } 102 | 103 | public void clear() { 104 | banned.clear(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/DatagramHeader.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.Validate; 5 | import org.apache.commons.lang3.builder.ToStringBuilder; 6 | 7 | public final class DatagramHeader implements Cloneable { 8 | 9 | public static final int HEADER_LENGTH_BYTES = 2 + 3 + 4; // 2 + 3 + sizeof(float) * 1 10 | 11 | public enum Type { 12 | NAK, 13 | ACK, 14 | DATA 15 | } 16 | 17 | public static DatagramHeader getHeader(Type type) { 18 | Validate.notNull(type); 19 | switch (type) { 20 | case NAK: 21 | return new DatagramHeader(0xA0); 22 | case ACK: 23 | return new DatagramHeader(0xC0); 24 | case DATA: 25 | return new DatagramHeader(0x80); 26 | default: 27 | throw new IllegalStateException("Should not reach here"); 28 | } 29 | } 30 | 31 | public boolean isValid; 32 | public boolean isAck; 33 | public boolean isNak; 34 | public boolean isPacketPair; 35 | public boolean hasBandAs; 36 | public boolean isContinuousSend; 37 | public boolean needsBandAs; 38 | 39 | // optional field 40 | public float As; 41 | public int datagramNumber = -1; 42 | 43 | public DatagramHeader() { 44 | 45 | } 46 | 47 | public DatagramHeader(int header) { 48 | decode((byte) header); 49 | } 50 | 51 | private byte toByte() { 52 | byte header = 0; 53 | 54 | // isValid always true when encoding 55 | header |= (1 << 7); 56 | 57 | if (isAck) { 58 | header |= (1 << 6); 59 | if (hasBandAs) { 60 | header |= (1 << 5); 61 | } 62 | 63 | } else if (isNak) { 64 | header |= (1 << 5); 65 | 66 | } else { 67 | if (isPacketPair) header |= (1 << 4); 68 | if (isContinuousSend) header |= (1 << 3); 69 | if (needsBandAs) header |= (1 << 2); 70 | } 71 | return header; 72 | } 73 | 74 | public void encode(ByteBuf buf) { 75 | byte header = toByte(); 76 | 77 | buf.writeByte(header); 78 | 79 | if (isAck && hasBandAs) { 80 | buf.writeFloat(As); 81 | } 82 | 83 | if (!isAck && !isNak) { 84 | buf.writeMediumLE(datagramNumber); 85 | } 86 | } 87 | 88 | private void decode(byte header) { 89 | isValid = (header & (1 << 7)) > 0; 90 | isAck = (header & (1 << 6)) > 0; 91 | 92 | if (isAck) { 93 | isNak = false; 94 | isPacketPair = false; 95 | hasBandAs = (header & (1 << 5)) > 0; 96 | } else { 97 | isNak = (header & (1 << 5)) > 0; 98 | 99 | if (isNak) { 100 | isPacketPair = false; 101 | 102 | } else { 103 | isPacketPair = (header & (1 << 4)) > 0; 104 | isContinuousSend = (header & (1 << 3)) > 0; 105 | needsBandAs = (header & (1 << 2)) > 0; 106 | } 107 | } 108 | } 109 | 110 | public void decode(ByteBuf buf) { 111 | byte header = buf.readByte(); 112 | decode(header); 113 | 114 | if (isAck && hasBandAs) { 115 | As = buf.readFloat(); 116 | } 117 | 118 | if (!isAck && !isNak) { 119 | datagramNumber = buf.readMediumLE(); 120 | } 121 | } 122 | 123 | @Override 124 | protected DatagramHeader clone() throws CloneNotSupportedException { 125 | return (DatagramHeader) super.clone(); 126 | } 127 | 128 | @Override 129 | public String toString() { 130 | return new ToStringBuilder(this) 131 | .append("isValid", isValid) 132 | .append("isAck", isAck) 133 | .append("isNak", isNak) 134 | .append("isPacketPair", isPacketPair) 135 | .append("hasBandAs", hasBandAs) 136 | .append("isContinuousSend", isContinuousSend) 137 | .append("needsBandAs", needsBandAs) 138 | .append("As", As) 139 | .append("datagramNumber", datagramNumber) 140 | .toString(); 141 | } 142 | } -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/AbstractOfflineHandler.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelDuplexHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.socket.DatagramPacket; 8 | import io.netty.util.ReferenceCountUtil; 9 | import io.netty.util.internal.logging.InternalLogger; 10 | import io.netty.util.internal.logging.InternalLoggerFactory; 11 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 12 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 13 | 14 | import java.net.InetSocketAddress; 15 | 16 | public abstract class AbstractOfflineHandler extends ChannelDuplexHandler { 17 | 18 | private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(AbstractOfflineHandler.class); 19 | 20 | private final Channel channel; 21 | 22 | public AbstractOfflineHandler(Channel channel) { 23 | this.channel = channel; 24 | } 25 | 26 | public Channel channel() { 27 | return this.channel; 28 | } 29 | 30 | @Override 31 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 32 | 33 | DatagramPacket packet = (DatagramPacket) msg; 34 | ByteBuf buf = packet.content(); 35 | InetSocketAddress sender = packet.sender(); 36 | 37 | boolean isOffline = true; 38 | 39 | try { 40 | buf.markReaderIndex(); 41 | MessageIdentifier id = MessageIdentifier.readFrom(buf); 42 | if (id == null) { 43 | // it is not an offline message, proceed to the next handler 44 | isOffline = false; 45 | return; 46 | } 47 | 48 | OfflineMessage in; 49 | switch (id) { 50 | case ID_UNCONNECTED_PING: 51 | in = new UnconnectedPing(); 52 | break; 53 | case ID_UNCONNECTED_PING_OPEN_CONNECTIONS: 54 | in = new UnconnectedPingOpenConnections(); 55 | break; 56 | case ID_UNCONNECTED_PONG: 57 | in = new UnconnectedPong(); 58 | break; 59 | case ID_OPEN_CONNECTION_REQUEST_1: 60 | in = new OpenConnectionRequest1(); 61 | break; 62 | case ID_OPEN_CONNECTION_REQUEST_2: 63 | in = new OpenConnectionRequest2(); 64 | break; 65 | case ID_OPEN_CONNECTION_REPLY_1: 66 | in = new OpenConnectionReply1(); 67 | break; 68 | case ID_OPEN_CONNECTION_REPLY_2: 69 | in = new OpenConnectionReply2(); 70 | break; 71 | case ID_CONNECTION_ATTEMPT_FAILED: 72 | case ID_NO_FREE_INCOMING_CONNECTIONS: 73 | case ID_CONNECTION_BANNED: 74 | case ID_ALREADY_CONNECTED: 75 | case ID_INVALID_PASSWORD: 76 | case ID_IP_RECENTLY_CONNECTED: 77 | case ID_INCOMPATIBLE_PROTOCOL_VERSION: 78 | connectionAttemptFailed(ctx, id); 79 | return; 80 | default: 81 | isOffline = false; 82 | return; 83 | } 84 | 85 | // an offline message, decode it and process it 86 | in.decode(buf); 87 | readOfflinePacket(ctx, in, sender); 88 | } finally { 89 | if (isOffline) { 90 | ReferenceCountUtil.release(msg); 91 | } else { 92 | buf.resetReaderIndex(); 93 | ctx.fireChannelRead(msg); 94 | } 95 | } 96 | } 97 | 98 | public void connectionAttemptFailed(ChannelHandlerContext ctx, MessageIdentifier reason) { 99 | // NOOP 100 | } 101 | 102 | @Override 103 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 104 | LOGGER.debug("Exception caught when handling a message", cause); 105 | } 106 | 107 | public abstract void readOfflinePacket(ChannelHandlerContext ctx, OfflineMessage msg, InetSocketAddress sender); 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/PacketReliability.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | public enum PacketReliability { 4 | /** 5 | * Same as regular UDP, except that it will also discard duplicate datagrams. 6 | * RakNet adds (6 to 17) + 21 bits of overhead, 16 of which is used to detect duplicate packets 7 | * and 6 to 17 of which is used for message length. 8 | */ 9 | UNRELIABLE(false, false, false, false), 10 | 11 | /** 12 | * Regular UDP with a sequence counter. Out of order messages will be discarded. 13 | * Sequenced and ordered messages sent on the same channel will arrive in the order sent. 14 | */ 15 | UNRELIABLE_SEQUENCED(false, true, true, false), 16 | 17 | /** 18 | * The message is sent reliably, but not necessarily in any order. 19 | * Same overhead as {@link PacketReliability#UNRELIABLE}. 20 | */ 21 | RELIABLE(true, false, false, false), 22 | 23 | /** 24 | * This message is reliable and will arrive in the order you sent it. 25 | * Messages will be delayed while waiting for out of order messages. 26 | * Same overhead as {@link PacketReliability#UNRELIABLE_SEQUENCED}. 27 | * Sequenced and ordered messages sent on the same channel will arrive in the order sent. 28 | */ 29 | RELIABLE_ORDERED(true, true, false, false), 30 | 31 | /** 32 | * This message is reliable and will arrive in the sequence you sent it. 33 | * Out or order messages will be dropped. 34 | * Same overhead as {@link PacketReliability#UNRELIABLE_SEQUENCED}. 35 | * Sequenced and ordered messages sent on the same channel will arrive in the order sent. 36 | */ 37 | RELIABLE_SEQUENCED(true, true, true, false), 38 | 39 | /** 40 | * Same as {@link PacketReliability#UNRELIABLE}, 41 | * however the user will get either ID_SND_RECEIPT_ACKED or ID_SND_RECEIPT_LOSS 42 | * based on the result of sending this message when calling RakPeerInterface::Receive(). 43 | * Bytes 1-4 will contain the number returned from the Send() function. 44 | * On disconnect or shutdown, all messages not previously acked should be considered lost. 45 | */ 46 | UNRELIABLE_WITH_ACK_RECEIPT(false, false, false, true), 47 | 48 | /** 49 | * Same as {@link PacketReliability#RELIABLE}. 50 | * The user will also get ID_SND_RECEIPT_ACKED after the message is delivered 51 | * when calling RakPeerInterface::Receive(). 52 | * ID_SND_RECEIPT_ACKED is returned when the message arrives, not necessarily the order when it was sent. 53 | * Bytes 1-4 will contain the number returned from the Send() function. 54 | * On disconnect or shutdown, all messages not previously acked should be considered lost. 55 | * This does not return ID_SND_RECEIPT_LOSS. 56 | */ 57 | RELIABLE_WITH_ACK_RECEIPT(true, false, false, true), 58 | 59 | /** 60 | * Same as {@link PacketReliability#RELIABLE_ORDERED}. 61 | * The user will also get ID_SND_RECEIPT_ACKED after the message is delivered 62 | * when calling RakPeerInterface::Receive(). 63 | * ID_SND_RECEIPT_ACKED is returned when the message arrives, not necessarily the order when it was sent. 64 | * Bytes 1-4 will contain the number returned from the Send() function. 65 | * On disconnect or shutdown, all messages not previously acked should be considered lost. 66 | * This does not return ID_SND_RECEIPT_LOSS. 67 | */ 68 | RELIABLE_ORDERED_WITH_ACK_RECEIPT(true, true, false, true); 69 | 70 | private final boolean isReliable; 71 | private final boolean isOrdered; 72 | private final boolean isSequenced; 73 | private final boolean withAckReceipt; 74 | 75 | PacketReliability(boolean isReliable, boolean isOrdered, boolean isSequenced, boolean withAckReceipt) { 76 | this.isReliable = isReliable; 77 | this.isOrdered = isOrdered; 78 | this.isSequenced = isSequenced; 79 | this.withAckReceipt = withAckReceipt; 80 | } 81 | 82 | public boolean isReliable() { 83 | return isReliable; 84 | } 85 | 86 | public boolean isOrdered() { 87 | return isOrdered; 88 | } 89 | 90 | public boolean isSequenced() { 91 | return isSequenced; 92 | } 93 | 94 | public boolean withAckReceipt() { 95 | return withAckReceipt; 96 | } 97 | 98 | public static final PacketReliability[] RELIABILITIES = PacketReliability.values(); 99 | public static final int NUM_OF_RELIABILITIES = RELIABILITIES.length; 100 | 101 | public static PacketReliability valueOf(int id) { 102 | if (id < 0 || id >= NUM_OF_RELIABILITIES) return null; 103 | 104 | return RELIABILITIES[id]; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/AcknowledgePacket.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.apache.commons.lang3.Range; 5 | import org.apache.commons.lang3.Validate; 6 | import org.apache.commons.lang3.builder.ToStringBuilder; 7 | import org.nukkit.raknetty.handler.codec.InternalPacket; 8 | 9 | import java.util.*; 10 | 11 | public class AcknowledgePacket extends InternalPacket { 12 | 13 | private final Set indices = new HashSet<>(); 14 | 15 | public boolean isEmpty() { 16 | return indices.isEmpty(); 17 | } 18 | 19 | public void add(int index) { 20 | indices.add(index); 21 | } 22 | 23 | public void remove(int index) { 24 | indices.remove(index); 25 | } 26 | 27 | public void clear() { 28 | indices.clear(); 29 | } 30 | 31 | public void encode(ByteBuf buf, int maxBytes, boolean clearWritten) { 32 | 33 | if (indices.isEmpty()) { 34 | buf.writeShort(0); 35 | return; 36 | } 37 | 38 | Collection> ranges = ranges(); 39 | 40 | short countWritten = 0; 41 | int bytesWritten = 0; 42 | // save the writer index before we write the count 43 | int countIndex = buf.writerIndex(); 44 | buf.writeZero(2); // skip two bytes 45 | 46 | Iterator> iterator = ranges.iterator(); 47 | while (iterator.hasNext()) { 48 | if (maxBytes > 0 && 2 + bytesWritten + 1 + 3 * 2 > maxBytes) { 49 | break; 50 | } 51 | 52 | Range range = iterator.next(); 53 | int min = range.getMinimum(); 54 | int max = range.getMaximum(); 55 | boolean maxEqualToMin = min == max; 56 | 57 | buf.writeBoolean(maxEqualToMin); 58 | bytesWritten += 1; 59 | buf.writeMediumLE(min); 60 | bytesWritten += 3; 61 | if (!maxEqualToMin) { 62 | buf.writeMediumLE(max); 63 | bytesWritten += 3; 64 | } 65 | 66 | countWritten++; 67 | } 68 | 69 | buf.markWriterIndex(); 70 | buf.writerIndex(countIndex); 71 | buf.writeShort(countWritten); 72 | buf.resetWriterIndex(); 73 | 74 | // add the unwritten indices back 75 | if (clearWritten && countWritten > 0) { 76 | indices.clear(); 77 | iterator.forEachRemaining(range -> { 78 | int min = range.getMinimum(); 79 | int max = range.getMaximum(); 80 | for (int i = min; i <= max; i++) { 81 | indices.add(i); 82 | } 83 | }); 84 | } 85 | } 86 | 87 | public void encode(ByteBuf buf, int maxBytes) { 88 | encode(buf, maxBytes, false); 89 | } 90 | 91 | @Override 92 | public void encode(ByteBuf buf) { 93 | encode(buf, -1); 94 | } 95 | 96 | @Override 97 | public void decode(ByteBuf buf) { 98 | short count = buf.readShort(); 99 | boolean maxEqualToMin; 100 | int min, max; 101 | 102 | indices.clear(); 103 | for (int i = 0; i < count; i++) { 104 | maxEqualToMin = buf.readBoolean(); 105 | min = buf.readMediumLE(); 106 | 107 | if (!maxEqualToMin) { 108 | max = buf.readMediumLE(); 109 | Validate.isTrue(min <= max, "bad ack: max < min"); 110 | } else { 111 | max = min; 112 | } 113 | 114 | for (int j = min; j <= max; j++) { 115 | indices.add(j); 116 | } 117 | } 118 | } 119 | 120 | public Collection indices() { 121 | return indices; 122 | } 123 | 124 | private Collection> ranges() { 125 | List> ranges = new ArrayList<>(); 126 | 127 | TreeSet indices = new TreeSet<>(this.indices); 128 | Iterator iterator = indices.iterator(); 129 | int lower = iterator.next(); 130 | int upper = lower; 131 | 132 | while (iterator.hasNext()) { 133 | 134 | int index = iterator.next(); 135 | 136 | // is continuous 137 | if (index == upper + 1) { 138 | // increase the upper 139 | upper++; 140 | } else { 141 | // save the previous range 142 | ranges.add(Range.between(lower, upper)); 143 | // reset the lower and upper 144 | lower = index; 145 | upper = index; 146 | } 147 | } 148 | 149 | ranges.add(Range.between(lower, upper)); 150 | return ranges; 151 | } 152 | 153 | @Override 154 | public String toString() { 155 | return new ToStringBuilder(this) 156 | .append("ranges", ranges()) 157 | .toString(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/util/RakNetUtil.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.util; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.util.CharsetUtil; 5 | import org.nukkit.raknetty.handler.codec.InternalPacket; 6 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 7 | import org.nukkit.raknetty.handler.codec.PacketReliability; 8 | 9 | import java.net.*; 10 | import java.util.function.Consumer; 11 | import java.util.function.Supplier; 12 | 13 | public class RakNetUtil { 14 | 15 | public static InetSocketAddress readAddress(ByteBuf buf) { 16 | byte ipVersion = buf.readByte(); 17 | byte[] binary; 18 | int port; 19 | 20 | if (ipVersion == 4) { 21 | binary = new byte[4]; 22 | 23 | for (int i = 0; i < 4; i++) { 24 | binary[i] = (byte) ~buf.readByte(); 25 | } 26 | 27 | port = buf.readUnsignedShort(); 28 | 29 | } else if (ipVersion == 6) { 30 | binary = new byte[16]; 31 | 32 | buf.skipBytes(2); // AF_INET6. 33 | port = buf.readUnsignedShort(); 34 | buf.skipBytes(4); // IPv6 flow information. 35 | buf.readBytes(binary); 36 | buf.skipBytes(4); // IPv6 scope id 37 | 38 | } else { 39 | return null; 40 | } 41 | 42 | try { 43 | return new InetSocketAddress(InetAddress.getByAddress(binary), port); 44 | } catch (UnknownHostException e) { 45 | return null; 46 | } 47 | } 48 | 49 | public static String readString(ByteBuf buf) { 50 | return readString(buf, buf::readShort); 51 | } 52 | 53 | public static String readString(ByteBuf buf, Supplier lengthSupplier) { 54 | int len = lengthSupplier.get().intValue(); 55 | byte[] dst = new byte[len]; 56 | buf.readBytes(dst); 57 | return new String(dst, CharsetUtil.UTF_8); 58 | } 59 | 60 | public static void writeByte(ByteBuf buf, MessageIdentifier id) { 61 | buf.writeByte(id.ordinal()); 62 | } 63 | 64 | public static void writeAddress(ByteBuf buf, InetSocketAddress address) { 65 | InetAddress addr = address.getAddress(); 66 | byte[] binary = addr.getAddress(); 67 | int port = address.getPort(); 68 | 69 | if (addr instanceof Inet4Address) { 70 | // ip version is 4 71 | buf.writeByte(4); 72 | 73 | for (int i = 0; i < 4; i++) { 74 | int b = ~binary[i]; 75 | buf.writeByte(b); 76 | } 77 | 78 | buf.writeShort(port); 79 | 80 | } else if (addr instanceof Inet6Address) { 81 | // ip version is 6 82 | buf.writeByte(6); 83 | 84 | //typedef struct sockaddr_in6 { 85 | // ADDRESS_FAMILY sin6_family; // AF_INET6. 86 | buf.writeShortLE(10); // 10 (linux/socket.h) or 23 (winsocks2.h) 87 | // USHORT sin6_port; // Transport level port number. 88 | buf.writeShort(port); 89 | // ULONG sin6_flowinfo; // IPv6 flow information. 90 | buf.writeInt(0); 91 | // IN6_ADDR sin6_addr; // IPv6 address. 92 | buf.writeBytes(binary); 93 | // ULONG sin6_scope_id; // Set of interfaces for a scope. 94 | buf.writeInt(((Inet6Address) addr).getScopeId()); 95 | //} SOCKADDR_IN6_LH, *PSOCKADDR_IN6_LH, FAR *LPSOCKADDR_IN6_LH; 96 | } 97 | } 98 | 99 | public static void writeString(ByteBuf buf, String str) { 100 | writeString(buf, str, buf::writeShort); 101 | } 102 | 103 | public static void writeString(ByteBuf buf, String str, Consumer lengthConsumer) { 104 | byte[] bytes = str.getBytes(CharsetUtil.UTF_8); 105 | lengthConsumer.accept(bytes.length); 106 | buf.writeBytes(bytes); 107 | } 108 | 109 | public static void padWithZero(ByteBuf buf, int bytes) { 110 | int numToWrite = bytes - buf.writerIndex(); 111 | buf.writeZero(numToWrite); 112 | } 113 | 114 | public static int bitToBytes(int bitLength) { 115 | return (bitLength + 7) >> 3; 116 | } 117 | 118 | public static int bytesToBits(int byteLength) { 119 | return byteLength << 3; 120 | } 121 | 122 | public static int getHeaderLength(InternalPacket packet) { 123 | PacketReliability reliability = packet.reliability; 124 | 125 | int len = 1 + 2; // flag + bit length 126 | 127 | if (reliability.isReliable()) { 128 | len += 3; // reliable index 129 | } 130 | 131 | if (reliability.isSequenced()) { 132 | len += 3; // sequence index 133 | } 134 | 135 | if (reliability.isOrdered()) { 136 | len += 3; // ordering index 137 | len += 1; // ordering channel 138 | } 139 | 140 | if (packet.splitPacketCount > 0) { 141 | len += 4; // split packet count 142 | len += 2; // split packet id 143 | len += 4; // split packet index 144 | } 145 | 146 | return len; 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/AbstractRakDatagramChannel.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.*; 5 | import io.netty.channel.nio.NioEventLoop; 6 | import io.netty.channel.socket.DatagramChannel; 7 | import io.netty.channel.socket.DatagramPacket; 8 | import io.netty.channel.socket.nio.NioDatagramChannel; 9 | import io.netty.util.internal.logging.InternalLogger; 10 | import io.netty.util.internal.logging.InternalLoggerFactory; 11 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 12 | 13 | import java.net.InetSocketAddress; 14 | import java.net.SocketAddress; 15 | 16 | public abstract class AbstractRakDatagramChannel extends AbstractChannel { 17 | 18 | private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(AbstractRakDatagramChannel.class); 19 | 20 | private final DatagramChannel udpChannel; 21 | 22 | public AbstractRakDatagramChannel() { 23 | this(null); 24 | } 25 | 26 | public AbstractRakDatagramChannel(final RakServerChannel parent) { 27 | this(parent, parent == null ? new NioDatagramChannel() : parent.udpChannel()); 28 | } 29 | 30 | protected AbstractRakDatagramChannel(final RakServerChannel parent, final DatagramChannel udpChannel) { 31 | super(parent); 32 | this.udpChannel = udpChannel; 33 | 34 | // reading datagram from DatagramChannel 35 | if (parent() == null) { 36 | udpChannel().pipeline().addLast(new DatagramChannelHandler()); 37 | } 38 | 39 | // passing events to RakChannel 40 | pipeline().addLast(new RakChannelOutbound()); 41 | } 42 | 43 | protected abstract boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception; 44 | 45 | protected abstract void doFinishConnect(); 46 | 47 | @Override 48 | protected void doBind(SocketAddress localAddress) throws Exception { 49 | udpChannel().bind(localAddress); 50 | } 51 | 52 | @Override 53 | protected void doRegister() throws Exception { 54 | // register the udp channel 55 | if (!udpChannel().isRegistered()) 56 | eventLoop().register(udpChannel()); 57 | } 58 | 59 | @Override 60 | protected void doDeregister() throws Exception { 61 | if (parent() == null) { 62 | udpChannel().deregister(); 63 | } 64 | } 65 | 66 | @Override 67 | protected final void doBeginRead() throws Exception { 68 | throw new UnsupportedOperationException(); 69 | } 70 | 71 | @Override 72 | protected final void doWrite(ChannelOutboundBuffer in) throws Exception { 73 | throw new UnsupportedOperationException(); 74 | } 75 | 76 | @Override 77 | protected final boolean isCompatible(EventLoop loop) { 78 | return loop instanceof NioEventLoop; 79 | } 80 | 81 | @Override 82 | public final boolean isWritable() { 83 | return udpChannel().isWritable(); 84 | } 85 | 86 | @Override 87 | public final long bytesBeforeWritable() { 88 | return udpChannel().bytesBeforeWritable(); 89 | } 90 | 91 | @Override 92 | public final long bytesBeforeUnwritable() { 93 | return udpChannel().bytesBeforeUnwritable(); 94 | } 95 | 96 | @Override 97 | public NioEventLoop eventLoop() { 98 | return (NioEventLoop) super.eventLoop(); 99 | } 100 | 101 | public final DatagramChannel udpChannel() { 102 | return udpChannel; 103 | } 104 | 105 | @Override 106 | protected final SocketAddress localAddress0() { 107 | return localAddress(); 108 | } 109 | 110 | @Override 111 | public final InetSocketAddress localAddress() { 112 | return udpChannel().localAddress(); 113 | } 114 | 115 | @Override 116 | protected final SocketAddress remoteAddress0() { 117 | return remoteAddress(); 118 | } 119 | 120 | private class DatagramChannelHandler extends ChannelDuplexHandler { 121 | @Override 122 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 123 | pipeline().fireChannelRead(msg); 124 | } 125 | 126 | @Override 127 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 128 | if (msg instanceof AddressedOfflineMessage) { 129 | AddressedOfflineMessage message = (AddressedOfflineMessage) msg; 130 | OfflineMessage content = message.content(); 131 | 132 | ByteBuf buf = alloc().ioBuffer(); 133 | content.getId().writeTo(buf); 134 | content.encode(buf); 135 | 136 | msg = new DatagramPacket(buf, message.recipient(), message.sender()); 137 | } 138 | 139 | ctx.write(msg, promise); 140 | } 141 | } 142 | 143 | private class RakChannelOutbound extends ChannelOutboundHandlerAdapter { 144 | @Override 145 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 146 | udpChannel().write(msg, udpChannel().newPromise().addListener((ChannelFutureListener) future -> { 147 | if (future.isSuccess()) { 148 | promise.setSuccess(); 149 | } else { 150 | promise.setFailure(future.cause()); 151 | } 152 | })); 153 | } 154 | 155 | @Override 156 | public void flush(ChannelHandlerContext ctx) throws Exception { 157 | udpChannel().flush(); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RakNetty 2 | 3 | RakNetty is a clean and modern implementation (port) of Oculus's [RakNet](https://github.com/facebookarchive/RakNet) in java. 4 | 5 | ## Why RakNetty? 6 | 7 | RakNet is a networking library used by many project, including the renowned Minecraft: Bedrock Edition and Unity Engine. 8 | RakNet uses UDP as its networking protocol, which is connectionless by default and packets are not guaranteed to reach 9 | the destination, in comparison with TCP. RakNet implements a number of algorithms to ensure that packets can be 10 | delivered in an ordered and reliable way. **RakNetty** ports the protocol and algorithms to java. 11 | 12 | **RakNetty** is built based on Netty, a high-performance asynchronous event-driven framework. In comparison with the 13 | original c++ version using Blocking IO, **RakNetty** has the advantages of Non-blocking IO to further improve the overall 14 | performance, which is achieved by dispatching the packets to be handled by multiple threads. In comparison, the original version of RakNet also handles the packets for all connections in a 15 | single thread. 16 | 17 | ## Current status 18 | 19 | Currently, RakNetty is functional and supports most RakNet features, but only trivially tested. The codes are also organised in a Netty-like structure 20 | with Java style conversion rather than a byte-to-byte copy of the original c++ version. 21 | 22 | ## Usage 23 | 24 | ### Maven repository 25 | 26 | ```xml 27 | 28 | 29 | nukkit-releases 30 | https://nukkit.org/nexus/repository/maven-releases/ 31 | 32 | 33 | ``` 34 | 35 | ### Dependency 36 | 37 | ```xml 38 | 39 | org.nukkit 40 | raknetty 41 | 1.0 42 | 43 | ``` 44 | 45 | ### Create a client 46 | 47 | See [Example Client](https://github.com/NukkitReborn/RakNetty/blob/master/src/main/java/org/nukkit/raknetty/example/RakNettyClient.java) 48 | 49 | ### Create a server 50 | 51 | See [Example Server](https://github.com/NukkitReborn/RakNetty/blob/master/src/main/java/org/nukkit/raknetty/example/RakNettyServer.java) 52 | 53 | ## Channel Options 54 | 55 | RakNet defines a number of constants in its original code, which allows the developers to override them by redefining. 56 | Minecraft: Bedrock Edition uses a different version of RakNet and changes some constants. For purpose of general use, 57 | RakNetty supports the override of constants in a different way, by making advantage of Netty's Channel Options. 58 | 59 | ### Server options 60 | 61 | | Option | Description | Default RakNet | Bedrock | 62 | | ------------------------------- | ------------------------------------------ | -------------- | ------------- | 63 | | RAKNET_GUID | Guid of the server | Random | Random | 64 | | RAKNET_NUMBER_OF_INTERNAL_IDS | Size of address list in connection request | 10 | 20 | 65 | | RAKNET_PROTOCOL_VERSION | Version of RakNet protocol | 6 | 10 | 66 | | RAKNET_MAX_CONNECTIONS | Number of maximum connections | User-specific | User-specific | 67 | | RAKNET_MAX_MTU_SIZE | Maximum allowable MTU size | 1492 | 1400 | 68 | | RAKNET_OFFLINE_RESPONSE | Offline response when pinging | String | Server MOTD | 69 | 70 | Usage: 71 | 72 | ```java 73 | ServerBootstrap boot; // your own server bootstrap 74 | 75 | boot.option(RakServerChannelOption.RAKNET_GUID, 123456L) 76 | .option(RakServerChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS, 20) 77 | .option(RakServerChannelOption.RAKNET_PROTOCOL_VERSION, 10) 78 | .option(RakServerChannelOption.RAKNET_MAX_CONNECTIONS, 15) 79 | .option(RakServerChannelOption.RAKNET_MAX_MTU_SIZE, 1400) 80 | .option(RakServerChannelOption.RAKNET_OFFLINE_RESPONSE, new ExampleBedrockPingResponse()); 81 | ``` 82 | 83 | ### Client options 84 | 85 | | Option | Description | Default RakNet | Bedrock | 86 | | ----------------------------- | -------------------------------------------------------- | --------------- | --------------- | 87 | | RAKNET_GUID | Guid of the server | Random | Random | 88 | | RAKNET_NUMBER_OF_INTERNAL_IDS | Size of address list in connection request | 10 | 20 | 89 | | RAKNET_PROTOCOL_VERSION | Version of RakNet protocol | 6 | 10 | 90 | | RAKNET_CONNECT_MTU_SIZES | Sizes for trial to detect the MTU size | 1492, 1200, 576 | 1492, 1200, 576 | 91 | | RAKNET_CONNECT_ATTEMPTS | Attempts to be made before the connection request failed | 6 | 12 | 92 | | RAKNET_CONNECT_INTERVAL | Interval between each connection request | 1000 | 500 | 93 | | RAKNET_CONNECT_TIMEOUT | Timeout of connection request | 0 | 0 | 94 | | RAKNET_UNRELIABLE_TIMEOUT | Timeout of unreliable packets to be discarded | 0 | 0 | 95 | | RAKNET_TIMEOUT | Timeout of connection | 10000 | 10000 | 96 | 97 | Usage: 98 | 99 | ```java 100 | Bootstrap boot; // your own bootstrap 101 | 102 | boot.option(RakChannelOption.RAKNET_GUID, 654321L) 103 | .option(RakChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS, 20) 104 | .option(RakChannelOption.RAKNET_PROTOCOL_VERSION, 10) 105 | .option(RakChannelOption.RAKNET_CONNECT_INTERVAL, 500) 106 | .option(RakChannelOption.RAKNET_CONNECT_ATTEMPTS, 12); 107 | ``` 108 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/DefaultRakServerChannelConfig.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | import io.netty.channel.*; 4 | import io.netty.channel.socket.DatagramChannel; 5 | import io.netty.util.internal.ObjectUtil; 6 | import io.netty.util.internal.ThreadLocalRandom; 7 | import org.nukkit.raknetty.handler.codec.offline.DefaultOfflinePingResponse; 8 | import org.nukkit.raknetty.handler.codec.offline.OfflinePingResponse; 9 | import org.nukkit.raknetty.handler.codec.reliability.SlidingWindow; 10 | 11 | import java.util.Map; 12 | 13 | public class DefaultRakServerChannelConfig extends DefaultChannelConfig implements RakServerChannelConfig { 14 | 15 | private volatile long localGuid = ThreadLocalRandom.current().nextLong(); 16 | private volatile int numberOfInternalIds = 10; 17 | private volatile int rakProtocolVersion = 5; 18 | 19 | private volatile int maxConnections = 20; 20 | private volatile int maxMtuSize = SlidingWindow.MAXIMUM_MTU_SIZE; 21 | private volatile OfflinePingResponse response = new DefaultOfflinePingResponse("Offline Data"); 22 | 23 | private final DatagramChannel udpChannel; 24 | 25 | public DefaultRakServerChannelConfig(Channel channel, DatagramChannel udpChannel) { 26 | super(channel, new FixedRecvByteBufAllocator(2048)); 27 | this.udpChannel = udpChannel; 28 | } 29 | 30 | @Override 31 | public Map, Object> getOptions() { 32 | return getOptions(udpChannel.config().getOptions(), 33 | RakServerChannelOption.RAKNET_GUID, 34 | RakServerChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS, 35 | RakServerChannelOption.RAKNET_PROTOCOL_VERSION, 36 | 37 | RakServerChannelOption.RAKNET_MAX_CONNECTIONS, 38 | RakServerChannelOption.RAKNET_MAX_MTU_SIZE, 39 | RakServerChannelOption.RAKNET_OFFLINE_RESPONSE 40 | ); 41 | } 42 | 43 | @SuppressWarnings("unchecked") 44 | @Override 45 | public T getOption(ChannelOption option) { 46 | if (option == RakServerChannelOption.RAKNET_GUID) { 47 | return (T) (Long) getLocalGuid(); 48 | } else if (option == RakServerChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS) { 49 | return (T) (Integer) getMaximumNumberOfInternalIds(); 50 | } else if (option == RakServerChannelOption.RAKNET_PROTOCOL_VERSION) { 51 | return (T) (Integer) getRakNetProtocolVersion(); 52 | } else if (option == RakServerChannelOption.RAKNET_MAX_CONNECTIONS) { 53 | return (T) (Integer) getMaximumConnections(); 54 | } else if (option == RakServerChannelOption.RAKNET_MAX_MTU_SIZE) { 55 | return (T) (Integer) getMaximumMtuSize(); 56 | } else if (option == RakServerChannelOption.RAKNET_OFFLINE_RESPONSE) { 57 | return (T) getOfflinePingResponse(); 58 | } 59 | return udpChannel.config().getOption(option); 60 | } 61 | 62 | @Override 63 | public boolean setOption(ChannelOption option, T value) { 64 | validate(option, value); 65 | 66 | if (option == RakServerChannelOption.RAKNET_GUID) { 67 | setLocalGuid((long) value); 68 | } else if (option == RakServerChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS) { 69 | setMaximumNumberOfInternalIds((int) value); 70 | } else if (option == RakServerChannelOption.RAKNET_PROTOCOL_VERSION) { 71 | setRakNetProtocolVersion((int) value); 72 | } else if (option == RakServerChannelOption.RAKNET_MAX_CONNECTIONS) { 73 | setMaximumConnections((int) value); 74 | } else if (option == RakServerChannelOption.RAKNET_MAX_MTU_SIZE) { 75 | setMaximumMtuSize((int) value); 76 | } else if (option == RakServerChannelOption.RAKNET_OFFLINE_RESPONSE) { 77 | setOfflinePingResponseBuilder((OfflinePingResponse) value); 78 | } else { 79 | return udpChannel.config().setOption(option, value); 80 | } 81 | return true; 82 | } 83 | 84 | // Shared Properties 85 | @Override 86 | public long getLocalGuid() { 87 | return localGuid; 88 | } 89 | 90 | @Override 91 | public RakServerChannelConfig setLocalGuid(long guid) { 92 | this.localGuid = guid; 93 | return this; 94 | } 95 | 96 | @Override 97 | public int getMaximumNumberOfInternalIds() { 98 | return numberOfInternalIds; 99 | } 100 | 101 | @Override 102 | public RakServerChannelConfig setMaximumNumberOfInternalIds(int numberOfInternalIds) { 103 | ObjectUtil.checkPositive(numberOfInternalIds, "numberOfInternalIds"); 104 | this.numberOfInternalIds = numberOfInternalIds; 105 | return this; 106 | } 107 | 108 | @Override 109 | public int getRakNetProtocolVersion() { 110 | return rakProtocolVersion; 111 | } 112 | 113 | @Override 114 | public RakServerChannelConfig setRakNetProtocolVersion(int protocolVersion) { 115 | this.rakProtocolVersion = protocolVersion; 116 | return this; 117 | } 118 | 119 | 120 | // Server Properties 121 | @Override 122 | public int getMaximumConnections() { 123 | return maxConnections; 124 | } 125 | 126 | @Override 127 | public RakServerChannelConfig setMaximumConnections(int maxConnections) { 128 | ObjectUtil.checkPositiveOrZero(maxConnections, "maxConnections"); 129 | this.maxConnections = maxConnections; 130 | return this; 131 | } 132 | 133 | @Override 134 | public int getMaximumMtuSize() { 135 | return maxMtuSize; 136 | } 137 | 138 | @Override 139 | public RakServerChannelConfig setMaximumMtuSize(int maxMtuSize) { 140 | ObjectUtil.checkPositiveOrZero(maxMtuSize, "maxMtuSize"); 141 | this.maxMtuSize = maxMtuSize; 142 | return this; 143 | } 144 | 145 | @Override 146 | public OfflinePingResponse getOfflinePingResponse() { 147 | return response; 148 | } 149 | 150 | @Override 151 | public RakServerChannelConfig setOfflinePingResponseBuilder(OfflinePingResponse response) { 152 | this.response = response; 153 | return this; 154 | } 155 | 156 | @Override 157 | public boolean isAutoRead() { 158 | return false; 159 | } 160 | 161 | @Override 162 | public ChannelConfig setAutoRead(boolean autoRead) { 163 | throw new UnsupportedOperationException(); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/nio/NioRakServerChannel.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel.nio; 2 | 3 | import io.netty.channel.*; 4 | import io.netty.channel.socket.DatagramChannel; 5 | import io.netty.channel.socket.DatagramPacket; 6 | import io.netty.channel.socket.nio.NioDatagramChannel; 7 | import io.netty.util.ReferenceCountUtil; 8 | import io.netty.util.internal.logging.InternalLogger; 9 | import io.netty.util.internal.logging.InternalLoggerFactory; 10 | import org.nukkit.raknetty.channel.*; 11 | import org.nukkit.raknetty.handler.codec.offline.DefaultServerOfflineHandler; 12 | import org.nukkit.raknetty.handler.codec.offline.OpenConnectionRequest2; 13 | import org.nukkit.raknetty.handler.ipfilter.BannedIpFilter; 14 | 15 | import java.net.InetSocketAddress; 16 | import java.net.SocketAddress; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | public class NioRakServerChannel extends AbstractRakDatagramChannel implements RakServerChannel { 21 | 22 | private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(NioRakServerChannel.class); 23 | private static final ChannelMetadata METADATA = new ChannelMetadata(false); 24 | 25 | private final RakServerChannelConfig config; 26 | private final BannedIpFilter banList = new BannedIpFilter(this); 27 | 28 | Map childChannels = new HashMap<>(); 29 | 30 | public NioRakServerChannel() { 31 | this(new NioDatagramChannel()); 32 | } 33 | 34 | protected NioRakServerChannel(DatagramChannel udpChannel) { 35 | super(null, udpChannel); 36 | config = newConfig(); 37 | 38 | pipeline().addLast("BanList", banList); 39 | pipeline().addLast(new DefaultServerOfflineHandler(this)); 40 | pipeline().addLast(new ServerMessageDispatcher()); 41 | } 42 | 43 | protected RakServerChannelConfig newConfig() { 44 | return new DefaultRakServerChannelConfig(this, udpChannel()); 45 | } 46 | 47 | @Override 48 | public void accept(ChannelHandlerContext ctx, OpenConnectionRequest2 request, InetSocketAddress remoteAddress) { 49 | int mtuSize = Math.min(request.mtuSize, config().getMaximumMtuSize()); 50 | 51 | RakChannel channel = newChildChannel(remoteAddress, request.clientGuid) 52 | .mtuSize(mtuSize) 53 | .connectMode(RakChannel.ConnectMode.UNVERIFIED_SENDER); 54 | 55 | childChannels.put(remoteAddress, channel); 56 | ctx.fireChannelRead(channel); 57 | } 58 | 59 | @Override 60 | public int numberOfConnections() { 61 | return (int) childChannels.values().stream() 62 | .filter(channel -> channel.isActive() && channel.connectMode() == RakChannel.ConnectMode.CONNECTED) 63 | .count(); 64 | } 65 | 66 | @Override 67 | public boolean allowNewConnections() { 68 | return numberOfConnections() < config().getMaximumConnections(); 69 | } 70 | 71 | protected RakChannel newChildChannel(InetSocketAddress remoteAddress, long guid) { 72 | return new NioRakChannel(this) 73 | .remoteAddress(remoteAddress) 74 | .remoteGuid(guid); 75 | } 76 | 77 | @Override 78 | public RakChannel getChildChannel(InetSocketAddress address) { 79 | // check if it is called from the thread 80 | if (!eventLoop().inEventLoop()) return null; 81 | return childChannels.get(address); 82 | } 83 | 84 | @Override 85 | public void removeChildChannel(InetSocketAddress address) { 86 | if (!eventLoop().inEventLoop()) { 87 | eventLoop().submit(() -> this.removeChildChannel(address)); 88 | return; 89 | } 90 | 91 | Channel channel = childChannels.remove(address); 92 | LOGGER.debug("Remove child channel: {}", channel); 93 | } 94 | 95 | public RakChannel getChildChannel(long guid) { 96 | // check if it is called from the thread 97 | if (!eventLoop().inEventLoop()) return null; 98 | 99 | return childChannels.values().stream() 100 | .filter(channel -> channel.remoteGuid() == guid) 101 | .findFirst() 102 | .orElse(null); 103 | } 104 | 105 | @Override 106 | public BannedIpFilter banList() { 107 | return banList; 108 | } 109 | 110 | @Override 111 | protected void doClose() throws Exception { 112 | udpChannel().close(); 113 | childChannels.values().forEach(RakChannel::close); 114 | } 115 | 116 | @Override 117 | protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { 118 | throw new UnsupportedOperationException(); 119 | } 120 | 121 | @Override 122 | protected void doFinishConnect() { 123 | throw new UnsupportedOperationException(); 124 | } 125 | 126 | @Override 127 | public long remoteGuid() { 128 | return -1; 129 | } 130 | 131 | @Override 132 | public long localGuid() { 133 | return config().getLocalGuid(); 134 | } 135 | 136 | @Override 137 | protected void doDisconnect() throws Exception { 138 | throw new UnsupportedOperationException(); 139 | } 140 | 141 | @Override 142 | public boolean isActive() { 143 | return udpChannel().isActive(); 144 | } 145 | 146 | @Override 147 | public boolean isOpen() { 148 | return udpChannel().isOpen(); 149 | } 150 | 151 | @Override 152 | public InetSocketAddress remoteAddress() { 153 | return null; 154 | } 155 | 156 | 157 | @Override 158 | public RakServerChannelConfig config() { 159 | return config; 160 | } 161 | 162 | @Override 163 | public ChannelMetadata metadata() { 164 | return METADATA; 165 | } 166 | 167 | @Override 168 | protected AbstractUnsafe newUnsafe() { 169 | return new NioRakServerUnsafe(); 170 | } 171 | 172 | private final class NioRakServerUnsafe extends AbstractUnsafe { 173 | @Override 174 | public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { 175 | safeSetFailure(promise, new UnsupportedOperationException()); 176 | } 177 | } 178 | 179 | private class ServerMessageDispatcher extends ChannelInboundHandlerAdapter { 180 | @Override 181 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 182 | if (msg instanceof DatagramPacket) { 183 | InetSocketAddress address = ((DatagramPacket) msg).sender(); 184 | RakChannel channel = getChildChannel(address); 185 | if (channel != null) { 186 | channel.eventLoop().submit(() -> { 187 | channel.pipeline().fireChannelRead(msg); 188 | }); 189 | } 190 | 191 | } else if (msg instanceof Channel) { 192 | // probably a new RakNettyChannel created from upstream handler, 193 | // proceed it further to the ServerBootstrapAcceptor 194 | ctx.fireChannelRead(msg); 195 | } else { 196 | ReferenceCountUtil.release(msg); 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/SlidingWindow.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.util.internal.logging.InternalLogger; 4 | import io.netty.util.internal.logging.InternalLoggerFactory; 5 | import org.apache.commons.lang3.Validate; 6 | import org.nukkit.raknetty.channel.RakChannel; 7 | import org.nukkit.raknetty.handler.codec.DatagramHeader; 8 | 9 | import java.util.concurrent.TimeUnit; 10 | 11 | public class SlidingWindow { 12 | 13 | private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(SlidingWindow.class); 14 | 15 | public static final int UDP_HEADER_SIZE = 28; 16 | public static final int MAXIMUM_MTU_SIZE = 1492; 17 | public static final long SYN = TimeUnit.MILLISECONDS.toNanos(10); 18 | public static final double UNSET_TIME_NS = -1.0d; 19 | 20 | private final RakChannel channel; 21 | 22 | private int mtuSize; 23 | private double cwnd; 24 | private double ssThresh = 0.0; 25 | 26 | private double lastRtt = UNSET_TIME_NS; 27 | private double estimatedRtt = UNSET_TIME_NS; 28 | private double deviationRtt = UNSET_TIME_NS; 29 | 30 | private long oldestUnsentAck = 0L; 31 | 32 | private int nextDatagramNumber = 0; 33 | private int expectedDatagramNumber = 0; 34 | private int nextCongestionControlBlock = 0; 35 | 36 | private boolean backoff = false; 37 | private boolean speedup = false; 38 | private boolean isContinuousSend = false; 39 | 40 | public SlidingWindow(RakChannel channel, int mtuSize) { 41 | Validate.notNull(channel); 42 | this.channel = channel; 43 | this.setMtu(mtuSize - SlidingWindow.UDP_HEADER_SIZE); 44 | cwnd = getMtu(); 45 | } 46 | 47 | public RakChannel channel() { 48 | return this.channel; 49 | } 50 | 51 | public int getRetransmissionBandwidth(int unackedBytes) { 52 | return unackedBytes; 53 | } 54 | 55 | public int getTransmissionBandwidth(int unackedBytes, boolean isContinuousSend) { 56 | 57 | this.isContinuousSend = isContinuousSend; 58 | 59 | if (unackedBytes <= cwnd) { 60 | return (int) (cwnd - unackedBytes); 61 | } 62 | 63 | return 0; 64 | } 65 | 66 | public boolean shouldSendAck(long currentTime) { 67 | long rto = getSenderRtoForAck(); 68 | 69 | if (rto == (long) UNSET_TIME_NS) { 70 | return true; 71 | } 72 | 73 | return currentTime - (oldestUnsentAck + SYN) >= 0; 74 | } 75 | 76 | public int getNextDatagramNumber() { 77 | return this.nextDatagramNumber; 78 | } 79 | 80 | public int increaseDatagramNumber() { 81 | return nextDatagramNumber++; 82 | } 83 | 84 | /** 85 | * @param datagramNumber Packet datagram number 86 | * @return number of messages skipped 87 | */ 88 | public int onGotPacket(int datagramNumber) { 89 | if (oldestUnsentAck == 0) { 90 | oldestUnsentAck = System.nanoTime(); //TODO: check CCRakNetSlidingWindow.cpp#L135 91 | } 92 | 93 | if (datagramNumber == expectedDatagramNumber) { 94 | expectedDatagramNumber = datagramNumber + 1; 95 | return 0; 96 | 97 | } else if (datagramNumber > expectedDatagramNumber) {//TODO: check CCRakNetSlidingWindow.cpp#L142 98 | int skipped = datagramNumber - expectedDatagramNumber; 99 | if (skipped > 1000) skipped = 1000; 100 | expectedDatagramNumber = datagramNumber + 1; 101 | return skipped; 102 | 103 | } 104 | 105 | return 0; 106 | } 107 | 108 | public void onResend() { 109 | if (isContinuousSend && !backoff && cwnd > mtuSize * 2) { 110 | ssThresh = cwnd / 2; 111 | if (ssThresh < mtuSize) ssThresh = mtuSize; 112 | 113 | cwnd = mtuSize; 114 | 115 | // only backoff once per period 116 | nextCongestionControlBlock = nextDatagramNumber; 117 | backoff = true; 118 | 119 | LOGGER.debug("Enter slow start, cwnd = {}", cwnd); 120 | } 121 | } 122 | 123 | public void onNak() { 124 | if (isContinuousSend && !backoff) { 125 | // Start congestion avoidance 126 | ssThresh = cwnd / 2; 127 | 128 | LOGGER.debug("Set congestion avoidance due to NAK, cwnd = {}", cwnd); 129 | } 130 | } 131 | 132 | public void onAck(long rtt, AcknowledgePacket ack) { 133 | DatagramHeader header = ack.header; 134 | int datagramNumber = header.datagramNumber; 135 | 136 | lastRtt = rtt; 137 | 138 | if (estimatedRtt == UNSET_TIME_NS) { 139 | estimatedRtt = rtt; 140 | deviationRtt = rtt; 141 | 142 | } else { 143 | double diff = rtt - estimatedRtt; 144 | estimatedRtt = estimatedRtt + 0.5d * diff; 145 | deviationRtt = deviationRtt + 0.5d * (Math.abs(diff) - deviationRtt); 146 | } 147 | 148 | this.isContinuousSend = header.isContinuousSend; 149 | if (!isContinuousSend) { 150 | return; 151 | } 152 | 153 | boolean shouldCongestionControl = datagramNumber > nextCongestionControlBlock; 154 | 155 | if (shouldCongestionControl) { 156 | backoff = false; 157 | speedup = false; 158 | nextCongestionControlBlock = nextDatagramNumber; 159 | } 160 | 161 | if (isSlowStart()) { 162 | cwnd += mtuSize; 163 | if (cwnd > ssThresh && ssThresh != 0) { 164 | cwnd = ssThresh + mtuSize * mtuSize / cwnd; 165 | } 166 | 167 | LOGGER.debug("Slow start increase, cwnd = {}", cwnd); 168 | 169 | } else if (shouldCongestionControl) { 170 | cwnd += mtuSize * mtuSize / cwnd; 171 | 172 | LOGGER.debug("Congestion avoidance increase, cwnd = {}", cwnd); 173 | } 174 | } 175 | 176 | public void onSendAck() { 177 | oldestUnsentAck = 0; 178 | } 179 | 180 | public long getRtoForRetransmission() { 181 | 182 | long maxThreshold = TimeUnit.MILLISECONDS.toNanos(2000); 183 | long additionalVariance = TimeUnit.MILLISECONDS.toNanos(30); 184 | 185 | if (estimatedRtt == UNSET_TIME_NS) { 186 | return maxThreshold; 187 | } 188 | 189 | double u = 2.0d; 190 | double q = 4.0d; 191 | 192 | long threshold = (long) ((u * estimatedRtt + q * deviationRtt) + additionalVariance); 193 | 194 | return Math.min(threshold, maxThreshold); 195 | } 196 | 197 | public void setMtu(int mtuSize) { 198 | Validate.isTrue(mtuSize > 0); 199 | Validate.isTrue(mtuSize <= MAXIMUM_MTU_SIZE); 200 | this.mtuSize = mtuSize; 201 | } 202 | 203 | public int getMtu() { 204 | return this.mtuSize; 205 | } 206 | 207 | public int getMtuExcludingMessageHeader() { 208 | return this.mtuSize - DatagramHeader.HEADER_LENGTH_BYTES; 209 | } 210 | 211 | public double getRtt() { 212 | if (lastRtt == UNSET_TIME_NS) { 213 | return 0.0d; 214 | } 215 | 216 | return lastRtt; 217 | } 218 | 219 | public long getSenderRtoForAck() { 220 | if (lastRtt == UNSET_TIME_NS) { 221 | return (long) UNSET_TIME_NS; 222 | } 223 | return (long) (lastRtt + SYN); 224 | } 225 | 226 | public boolean isSlowStart() { 227 | return cwnd <= ssThresh || ssThresh == 0; 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/InternalPacket.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufHolder; 5 | import io.netty.buffer.ByteBufUtil; 6 | import io.netty.buffer.DefaultByteBufHolder; 7 | import org.apache.commons.lang3.Validate; 8 | import org.apache.commons.lang3.builder.ToStringBuilder; 9 | import org.nukkit.raknetty.util.RakNetUtil; 10 | 11 | public class InternalPacket extends AbstractInternalPacket implements ByteBufHolder { 12 | 13 | public static final int NUMBER_OF_ORDERED_STREAMS = 32; 14 | 15 | public int splitPacketCount; 16 | public int splitPacketId; 17 | public int splitPacketIndex; 18 | 19 | public int reliableMessageNumber = -1; 20 | public int sequencingIndex; 21 | public int orderingIndex; 22 | public int orderingChannel; 23 | 24 | public ByteBuf data; 25 | 26 | @Override 27 | public void encode(ByteBuf buf) { 28 | byte flag = 0; 29 | PacketReliability temp; 30 | 31 | switch (this.reliability) { 32 | case UNRELIABLE_WITH_ACK_RECEIPT: 33 | temp = PacketReliability.UNRELIABLE; 34 | break; 35 | case RELIABLE_WITH_ACK_RECEIPT: 36 | temp = PacketReliability.RELIABLE; 37 | break; 38 | case RELIABLE_ORDERED_WITH_ACK_RECEIPT: 39 | temp = PacketReliability.RELIABLE_ORDERED; 40 | break; 41 | default: 42 | temp = this.reliability; 43 | } 44 | 45 | flag |= temp.ordinal() << 5; 46 | 47 | if (hasSplitPacket()) { 48 | flag |= 0b1 << 4; 49 | } 50 | 51 | buf.writeByte(flag); 52 | 53 | int bitLength = bodyLength() * 8; 54 | Validate.isTrue(bitLength < 0xFFFF, "payload too large"); // data bit length should be less than 65535 55 | 56 | buf.writeShort(bitLength); 57 | 58 | if (reliability.isReliable()) { 59 | buf.writeMediumLE(reliableMessageNumber); 60 | } 61 | 62 | if (reliability.isSequenced()) { 63 | buf.writeMediumLE(sequencingIndex); 64 | } 65 | 66 | if (reliability.isOrdered()) { 67 | buf.writeMediumLE(orderingIndex); 68 | buf.writeByte(orderingChannel); 69 | } 70 | 71 | if (hasSplitPacket()) { 72 | buf.writeInt(splitPacketCount); 73 | buf.writeShort(splitPacketId); 74 | buf.writeInt(splitPacketIndex); 75 | } 76 | 77 | buf.writeBytes(data, data.readerIndex(), data.writerIndex()); 78 | } 79 | 80 | @Override 81 | public void decode(ByteBuf buf) { 82 | byte flag = buf.readByte(); 83 | reliability = PacketReliability.valueOf(flag >> 5); 84 | boolean hasSplitPacket = (flag >> 4 & 0b1) != 0; 85 | int bitLength = buf.readShort(); 86 | 87 | Validate.isTrue(!reliability.withAckReceipt(), "ACK_RECEIPT from remote system"); 88 | 89 | if (reliability.isReliable()) { 90 | reliableMessageNumber = buf.readMediumLE(); 91 | } 92 | 93 | if (reliability.isSequenced()) { 94 | sequencingIndex = buf.readMediumLE(); 95 | } 96 | 97 | if (reliability.isOrdered()) { 98 | orderingIndex = buf.readMediumLE(); 99 | orderingChannel = buf.readByte(); 100 | } else { 101 | orderingChannel = 0; 102 | } 103 | 104 | if (hasSplitPacket) { 105 | splitPacketCount = buf.readInt(); 106 | splitPacketId = buf.readShort(); 107 | splitPacketIndex = buf.readInt(); 108 | } else { 109 | splitPacketCount = 0; 110 | } 111 | 112 | // let's check if we are happy with everything 113 | Validate.isTrue(bitLength > 0, "bad packet bit length"); 114 | Validate.isTrue(reliability != null, "bad packet reliability"); 115 | Validate.isTrue(orderingChannel >= 0 && orderingChannel < NUMBER_OF_ORDERED_STREAMS, "bad channel index"); 116 | Validate.isTrue(!hasSplitPacket || (splitPacketIndex >= 0 && splitPacketIndex < splitPacketCount), "bad split index"); 117 | 118 | // we want to make sure the packet have enough bytes left to read 119 | // and it should be smaller than the MTU size 120 | int bodyLength = RakNetUtil.bitToBytes(bitLength); 121 | 122 | Validate.isTrue(bodyLength <= buf.readableBytes(), "not enough bytes to read"); 123 | data = buf.readBytes(bodyLength); 124 | } 125 | 126 | @Override 127 | public int bodyLength() { 128 | return data.writerIndex() - data.readerIndex(); 129 | } 130 | 131 | public boolean hasSplitPacket() { 132 | return splitPacketCount > 0; 133 | } 134 | 135 | @Override 136 | protected InternalPacket clone() throws CloneNotSupportedException { 137 | return (InternalPacket) super.clone(); 138 | } 139 | 140 | @Override 141 | public String toString() { 142 | ToStringBuilder builder = new ToStringBuilder(this) 143 | .append("reliability", reliability) 144 | .append("priority", priority); 145 | 146 | if (hasSplitPacket()) { 147 | builder.append("splitPacketCount", splitPacketCount) 148 | .append("splitPacketId", splitPacketId) 149 | .append("splitPacketIndex", splitPacketIndex); 150 | } 151 | 152 | if (reliability.isReliable()) { 153 | builder.append("reliableIndex", reliableMessageNumber); 154 | 155 | if (reliability.isOrdered()) { 156 | builder.append("orderingIndex", orderingIndex) 157 | .append("orderingChannel", orderingChannel); 158 | } 159 | 160 | if (reliability.isSequenced()) builder.append("sequencingIndex", sequencingIndex); 161 | 162 | } else if (reliability.isSequenced()) { 163 | builder.append("sequencingIndex", sequencingIndex); 164 | } 165 | 166 | return builder.toString(); 167 | } 168 | 169 | 170 | @Override 171 | public ByteBuf content() { 172 | return ByteBufUtil.ensureAccessible(data); 173 | } 174 | 175 | @Override 176 | public ByteBufHolder copy() { 177 | return replace(data.copy()); 178 | } 179 | 180 | @Override 181 | public ByteBufHolder duplicate() { 182 | return replace(data.duplicate()); 183 | } 184 | 185 | @Override 186 | public ByteBufHolder retainedDuplicate() { 187 | return replace(data.retainedDuplicate()); 188 | } 189 | 190 | @Override 191 | public ByteBufHolder replace(ByteBuf content) { 192 | return new DefaultByteBufHolder(content); 193 | } 194 | 195 | @Override 196 | public int refCnt() { 197 | return data.refCnt(); 198 | } 199 | 200 | @Override 201 | public ByteBufHolder retain() { 202 | data.retain(); 203 | return this; 204 | } 205 | 206 | @Override 207 | public ByteBufHolder retain(int increment) { 208 | data.retain(increment); 209 | return this; 210 | } 211 | 212 | @Override 213 | public ByteBufHolder touch() { 214 | data.touch(); 215 | return this; 216 | } 217 | 218 | @Override 219 | public ByteBufHolder touch(Object hint) { 220 | data.touch(hint); 221 | return this; 222 | } 223 | 224 | @Override 225 | public boolean release() { 226 | return data.release(); 227 | } 228 | 229 | @Override 230 | public boolean release(int decrement) { 231 | return data.release(decrement); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/example/BedrockForwarder.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.example; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.*; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.handler.logging.ByteBufFormat; 9 | import io.netty.handler.logging.LogLevel; 10 | import io.netty.handler.logging.LoggingHandler; 11 | import io.netty.util.concurrent.DefaultThreadFactory; 12 | import io.netty.util.internal.logging.InternalLogger; 13 | import io.netty.util.internal.logging.InternalLoggerFactory; 14 | import org.apache.commons.lang3.builder.ToStringBuilder; 15 | import org.apache.commons.lang3.builder.ToStringStyle; 16 | import org.nukkit.raknetty.channel.RakChannel; 17 | import org.nukkit.raknetty.channel.RakChannelOption; 18 | import org.nukkit.raknetty.channel.RakServerChannel; 19 | import org.nukkit.raknetty.channel.RakServerChannelOption; 20 | import org.nukkit.raknetty.channel.nio.NioRakChannel; 21 | import org.nukkit.raknetty.channel.nio.NioRakServerChannel; 22 | import org.nukkit.raknetty.handler.codec.PacketPriority; 23 | import org.nukkit.raknetty.handler.codec.PacketReliability; 24 | 25 | import java.util.concurrent.ThreadFactory; 26 | 27 | public class BedrockForwarder { 28 | 29 | static final int PORT = 19132; 30 | static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(BedrockForwarder.class); 31 | 32 | static RakChannel clientChannel; 33 | static RakChannel serverChildChannel; 34 | static RakServerChannel serverChannel; 35 | 36 | static { 37 | ToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE); 38 | } 39 | 40 | public static void main(String[] args) throws Exception { 41 | startClient(); 42 | startServer(); 43 | } 44 | 45 | public static void startServer() throws Exception { 46 | final ThreadFactory acceptFactory = new DefaultThreadFactory("server-accept"); 47 | final ThreadFactory connectFactory = new DefaultThreadFactory("server-connect"); 48 | final NioEventLoopGroup acceptGroup = new NioEventLoopGroup(2, acceptFactory); 49 | final NioEventLoopGroup connectGroup = new NioEventLoopGroup(connectFactory); 50 | 51 | final ServerBootstrap boot = new ServerBootstrap(); 52 | boot.group(acceptGroup, connectGroup) 53 | .channel(NioRakServerChannel.class) 54 | .option(RakServerChannelOption.RAKNET_GUID, 123456L) 55 | // consist with the bedrock server's RakNet configuration 56 | .option(RakServerChannelOption.RAKNET_MAX_CONNECTIONS, 1) 57 | .option(RakServerChannelOption.RAKNET_MAX_MTU_SIZE, 1400) 58 | .option(RakServerChannelOption.RAKNET_OFFLINE_RESPONSE, new ExampleBedrockPingResponse()) 59 | .handler(new LoggingHandler("RakServerLogger", LogLevel.DEBUG, ByteBufFormat.SIMPLE)) 60 | .childOption(RakChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS, 20) 61 | .childHandler(new ChannelInitializer() { 62 | @Override 63 | public void initChannel(final RakChannel ch) throws Exception { 64 | serverChildChannel = ch; 65 | ch.pipeline().addLast(new ChannelDuplexHandler() { 66 | @Override 67 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 68 | // use RakNetty client to send, use assumed priority and reliability 69 | ByteBuf buf = (ByteBuf) msg; 70 | clientChannel.send(buf, PacketPriority.HIGH_PRIORITY, PacketReliability.RELIABLE_ORDERED, 0); 71 | } 72 | 73 | @Override 74 | public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 75 | clientChannel.disconnect(); 76 | super.close(ctx, promise); 77 | } 78 | }); 79 | ch.pipeline().addLast(new LoggingHandler("ChannelLogger", LogLevel.DEBUG, ByteBufFormat.SIMPLE) { 80 | @Override 81 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 82 | ctx.write(msg, promise); 83 | // disable log here 84 | } 85 | }); 86 | // close the main channel if the child channel is on close 87 | ch.closeFuture().addListener((ChannelFutureListener) future -> serverChannel.close()); 88 | } 89 | }); 90 | // Start the server. 91 | final ChannelFuture future = boot.bind(PORT).sync(); 92 | LOGGER.info("RakNetty server is ready."); 93 | 94 | serverChannel = (NioRakServerChannel) future.channel(); 95 | serverChannel.closeFuture().addListener((ChannelFutureListener) future1 -> { 96 | LOGGER.info("RakNetty server is closed."); 97 | // close the workgroup when shutting down the server 98 | acceptGroup.shutdownGracefully(); 99 | connectGroup.shutdownGracefully(); 100 | // close the client if it is open 101 | clientChannel.close(); 102 | }); 103 | } 104 | 105 | public static void startClient() throws Exception { 106 | final ThreadFactory factory = new DefaultThreadFactory("client"); 107 | final NioEventLoopGroup workGroup = new NioEventLoopGroup(factory); 108 | 109 | final Bootstrap boot = new Bootstrap(); 110 | boot.group(workGroup) 111 | .channel(NioRakChannel.class) 112 | .option(RakChannelOption.RAKNET_GUID, 654321L) 113 | // consist with the bedrock RakNet configuration 114 | .option(RakChannelOption.RAKNET_CONNECT_INTERVAL, 500) 115 | .option(RakChannelOption.RAKNET_CONNECT_ATTEMPTS, 12) 116 | .option(RakChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS, 20) 117 | .handler(new ChannelInitializer() { 118 | @Override 119 | protected void initChannel(NioRakChannel ch) throws Exception { 120 | ch.pipeline().addLast(new ChannelDuplexHandler() { 121 | @Override 122 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 123 | // use RakNetty client to send, use assumed priority and reliability 124 | ByteBuf buf = (ByteBuf) msg; 125 | serverChildChannel.send(buf, PacketPriority.HIGH_PRIORITY, PacketReliability.RELIABLE_ORDERED, 0); 126 | } 127 | 128 | @Override 129 | public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 130 | serverChildChannel.disconnect(); 131 | super.close(ctx, promise); 132 | } 133 | }); 134 | ch.pipeline().addLast(new LoggingHandler("RakLogger", LogLevel.DEBUG, ByteBufFormat.SIMPLE)); 135 | } 136 | }); 137 | // Start the server. 138 | final ChannelFuture future = boot.connect("pe.mineplex.com", 19132).sync(); 139 | LOGGER.info("RakNetty client is connected successfully."); 140 | 141 | clientChannel = (RakChannel) future.channel(); 142 | clientChannel.closeFuture().addListener((ChannelFutureListener) future1 -> { 143 | LOGGER.info("RakNetty client is closed."); 144 | // close the workgroup 145 | workGroup.shutdownGracefully(); 146 | // close the server if it is open 147 | serverChannel.close(); 148 | }); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/offline/DefaultServerOfflineHandler.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.offline; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.util.internal.logging.InternalLogger; 5 | import io.netty.util.internal.logging.InternalLoggerFactory; 6 | import org.nukkit.raknetty.channel.AddressedOfflineMessage; 7 | import org.nukkit.raknetty.channel.RakChannel; 8 | import org.nukkit.raknetty.channel.RakServerChannel; 9 | import org.nukkit.raknetty.handler.codec.OfflineMessage; 10 | 11 | import java.net.InetAddress; 12 | import java.net.InetSocketAddress; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.concurrent.ScheduledFuture; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | public class DefaultServerOfflineHandler extends AbstractOfflineHandler { 19 | 20 | private final static InternalLogger LOGGER = InternalLoggerFactory.getInstance(DefaultServerOfflineHandler.class); 21 | private final static long TIMEOUT_REQUEST = TimeUnit.MILLISECONDS.toNanos(1000); 22 | private final static long TIMEOUT_RECENT = TimeUnit.MILLISECONDS.toNanos(100); 23 | public static final String NAME = "ServerOffline"; 24 | 25 | private final Map recentlyConnected = new HashMap<>(); 26 | private final Map requested = new HashMap<>(); 27 | 28 | private ScheduledFuture updateTask; 29 | 30 | public DefaultServerOfflineHandler(RakServerChannel serverChannel) { 31 | super(serverChannel); 32 | } 33 | 34 | @Override 35 | public RakServerChannel channel() { 36 | return (RakServerChannel) super.channel(); 37 | } 38 | 39 | @Override 40 | public void readOfflinePacket(ChannelHandlerContext ctx, OfflineMessage msg, InetSocketAddress sender) { 41 | OfflineMessage reply = null; 42 | long now = System.nanoTime(); 43 | 44 | try { 45 | if (msg instanceof UnconnectedPing) { 46 | UnconnectedPing in = (UnconnectedPing) msg; 47 | 48 | if (msg instanceof UnconnectedPingOpenConnections && !channel().allowNewConnections()) { 49 | return; 50 | } 51 | 52 | UnconnectedPong out = new UnconnectedPong(); 53 | out.sendPingTime = in.sendPingTime; 54 | out.senderGuid = channel().localGuid(); 55 | out.response = channel().config().getOfflinePingResponse().get(channel()); 56 | reply = out; 57 | return; 58 | } 59 | 60 | //LOGGER.debug("READ: {}", msg); 61 | 62 | if (msg instanceof OpenConnectionRequest1) { 63 | OpenConnectionRequest1 in = (OpenConnectionRequest1) msg; 64 | 65 | // check if we are receiving a connection request from an incompatible client 66 | // if true, close the connection request. 67 | int remoteProtocol = in.protocol; 68 | int localProtocol = channel().config().getRakNetProtocolVersion(); 69 | if (remoteProtocol != localProtocol) { 70 | LOGGER.debug("Rejecting connection request from {}: outdated client", sender); 71 | 72 | IncompatibleProtocolVersion out = new IncompatibleProtocolVersion(); 73 | out.protocol = localProtocol; 74 | out.senderGuid = channel().localGuid(); 75 | 76 | reply = out; 77 | return; 78 | } 79 | 80 | // save the address into the pending list, 81 | // and monitor if the connection attempt is timed out. 82 | requested.put(sender, now); 83 | 84 | // reply to the client with OpenConnectionReply1 85 | OpenConnectionReply1 out = new OpenConnectionReply1(); 86 | out.serverGuid = channel().localGuid(); 87 | out.mtuSize = Math.min(in.mtuSize, channel().config().getMaximumMtuSize()); 88 | 89 | reply = out; 90 | return; 91 | } 92 | 93 | if (msg instanceof OpenConnectionRequest2) { 94 | OpenConnectionRequest2 in = (OpenConnectionRequest2) msg; 95 | 96 | // if the client skipped OpenConnectionRequest1 or the request was timed out 97 | if (!isRequestValid(sender)) { 98 | LOGGER.debug("Rejecting connection request from {}: expecting OpenConnectionRequest1", sender); 99 | 100 | reply = new ConnectionAttemptFailed(); 101 | return; 102 | } else { 103 | // otherwise, we remove the request from pending list 104 | // as it will be either rejected or accepted. 105 | requested.remove(sender); 106 | } 107 | 108 | // if a connection was established from the same ip in the past 100ms 109 | if (isRecentlyConnected(sender)) { 110 | LOGGER.debug("Rejecting connection request from {}: requested too frequently", sender); 111 | IpRecentlyConnected out = new IpRecentlyConnected(); 112 | out.senderGuid = channel().localGuid(); 113 | 114 | reply = out; 115 | return; 116 | } 117 | 118 | // if the server is full 119 | if (!channel().allowNewConnections()) { 120 | LOGGER.debug("Rejecting connection request from {}: server is full", sender); 121 | NoFreeIncomingConnection out = new NoFreeIncomingConnection(); 122 | out.senderGuid = channel().localGuid(); 123 | 124 | reply = out; 125 | return; 126 | } 127 | 128 | // if the address or guid has been taken by someone else 129 | RakChannel conn = channel().getChildChannel(sender); 130 | // TODO: maybe we should check guid too, but i decided not to do that for now 131 | //boolean guidInUse = serverChannel.isGuidInUse(in.clientGuid); 132 | if (conn != null && conn.isActive()) { 133 | LOGGER.debug("Rejecting connection request from {}: already connected", sender); 134 | AlreadyConnected out = new AlreadyConnected(); 135 | out.senderGuid = channel().localGuid(); 136 | reply = out; 137 | return; 138 | } 139 | 140 | // all good, allow connection 141 | LOGGER.debug("Accepting connection request from {}", sender); 142 | OpenConnectionReply2 out = new OpenConnectionReply2(); 143 | out.serverGuid = channel().localGuid(); 144 | out.clientAddress = sender; 145 | out.mtuSize = Math.min(in.mtuSize, channel().config().getMaximumMtuSize()); 146 | reply = out; 147 | 148 | // creating new channel and add the address to the recently connected list 149 | channel().accept(ctx, in, sender); 150 | recentlyConnected.put(sender.getAddress(), now); 151 | } 152 | 153 | } finally { 154 | if (reply != null) { 155 | ctx.writeAndFlush(new AddressedOfflineMessage(reply, sender)); 156 | } 157 | } 158 | } 159 | 160 | private boolean isRequestValid(InetSocketAddress remoteAddress) { 161 | Long requestedTime = requested.get(remoteAddress); 162 | 163 | if (requestedTime != null) { 164 | long currentTime = System.nanoTime(); 165 | 166 | if (currentTime - requestedTime >= TIMEOUT_REQUEST) { 167 | LOGGER.debug("{} >= {}", currentTime - requestedTime, TIMEOUT_REQUEST); 168 | requested.remove(remoteAddress); 169 | } else { 170 | return true; 171 | } 172 | } 173 | 174 | return false; 175 | } 176 | 177 | private boolean isRecentlyConnected(InetSocketAddress remoteAddress) { 178 | InetAddress address = remoteAddress.getAddress(); 179 | Long connectedTime = recentlyConnected.get(address); 180 | 181 | if (connectedTime != null) { 182 | long currentTime = System.nanoTime(); 183 | 184 | if (currentTime - connectedTime >= TIMEOUT_RECENT) { 185 | recentlyConnected.remove(address); 186 | } else { 187 | return true; 188 | } 189 | } 190 | return false; 191 | } 192 | 193 | @Override 194 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 195 | super.channelActive(ctx); 196 | 197 | if (updateTask == null) { 198 | // run the task every 60 seconds to remove all time-out requests 199 | // and trim recently connected list 200 | updateTask = ctx.executor().scheduleAtFixedRate( 201 | () -> { 202 | long currentTime = System.nanoTime(); 203 | recentlyConnected.values().removeIf(time -> currentTime - time >= TIMEOUT_RECENT); 204 | requested.values().removeIf(time -> currentTime - time >= TIMEOUT_REQUEST); 205 | } 206 | , 0, 60, TimeUnit.SECONDS); 207 | } 208 | } 209 | 210 | @Override 211 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 212 | super.channelInactive(ctx); 213 | 214 | if (updateTask != null) { 215 | updateTask.cancel(false); 216 | updateTask = null; 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/DefaultRakChannelConfig.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel; 2 | 3 | import io.netty.channel.ChannelConfig; 4 | import io.netty.channel.ChannelOption; 5 | import io.netty.channel.DefaultChannelConfig; 6 | import io.netty.channel.FixedRecvByteBufAllocator; 7 | import io.netty.channel.socket.DatagramChannel; 8 | import io.netty.util.internal.ObjectUtil; 9 | import io.netty.util.internal.ThreadLocalRandom; 10 | import org.apache.commons.lang3.Validate; 11 | import org.nukkit.raknetty.handler.codec.reliability.SlidingWindow; 12 | 13 | import java.util.Arrays; 14 | import java.util.Comparator; 15 | import java.util.Map; 16 | 17 | 18 | public class DefaultRakChannelConfig extends DefaultChannelConfig implements RakChannelConfig { 19 | 20 | private volatile long localGuid = ThreadLocalRandom.current().nextLong(); 21 | private volatile int numberOfInternalIds = 10; 22 | private volatile int rakProtocolVersion = 6; 23 | 24 | private volatile int[] mtuSizes = {SlidingWindow.MAXIMUM_MTU_SIZE, 1200, 576}; 25 | private volatile int connectAttempts = 6; 26 | private volatile int connectInterval = 1000; 27 | private volatile int connectTimeout = 0; 28 | private volatile int unreliableTimeout = 0; 29 | private volatile int timeout = 10000; 30 | 31 | 32 | private final DatagramChannel udpChannel; 33 | private final RakChannel rakChannel; 34 | 35 | public DefaultRakChannelConfig(RakChannel channel, DatagramChannel udpChannel) { 36 | super(channel, new FixedRecvByteBufAllocator(2048)); 37 | this.udpChannel = udpChannel; 38 | this.rakChannel = channel; 39 | } 40 | 41 | @Override 42 | public Map, Object> getOptions() { 43 | return getOptions(udpChannel.config().getOptions(), 44 | // Shared 45 | RakChannelOption.RAKNET_GUID, 46 | RakChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS, 47 | RakChannelOption.RAKNET_PROTOCOL_VERSION, 48 | // Channel 49 | RakChannelOption.RAKNET_CONNECT_MTU_SIZES, 50 | RakChannelOption.RAKNET_CONNECT_ATTEMPTS, 51 | RakChannelOption.RAKNET_CONNECT_INTERVAL, 52 | RakChannelOption.RAKNET_CONNECT_TIMEOUT, 53 | RakChannelOption.RAKNET_UNRELIABLE_TIMEOUT, 54 | RakChannelOption.RAKNET_TIMEOUT 55 | ); 56 | } 57 | 58 | @SuppressWarnings("unchecked") 59 | @Override 60 | public T getOption(ChannelOption option) { 61 | if (option == RakChannelOption.RAKNET_GUID) { 62 | return (T) (Long) getLocalGuid(); 63 | } else if (option == RakChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS) { 64 | return (T) (Integer) getMaximumNumberOfInternalIds(); 65 | } else if (option == RakChannelOption.RAKNET_PROTOCOL_VERSION) { 66 | return (T) (Integer) getRakNetProtocolVersion(); 67 | } else if (option == RakChannelOption.RAKNET_CONNECT_MTU_SIZES) { 68 | return (T) getConnectMtuSizes(); 69 | } else if (option == RakChannelOption.RAKNET_CONNECT_ATTEMPTS) { 70 | return (T) (Integer) getConnectAttempts(); 71 | } else if (option == RakChannelOption.RAKNET_CONNECT_INTERVAL) { 72 | return (T) (Integer) getConnectIntervalMillis(); 73 | } else if (option == RakChannelOption.RAKNET_CONNECT_TIMEOUT) { 74 | return (T) (Integer) getConnectTimeoutMillis(); 75 | } else if (option == RakChannelOption.RAKNET_UNRELIABLE_TIMEOUT) { 76 | return (T) (Integer) getUnreliableTimeoutMillis(); 77 | } else if (option == RakChannelOption.RAKNET_TIMEOUT) { 78 | return (T) (Integer) getTimeoutMillis(); 79 | } 80 | 81 | return udpChannel.config().getOption(option); 82 | } 83 | 84 | @Override 85 | public boolean setOption(ChannelOption option, T value) { 86 | validate(option, value); 87 | 88 | if (option == RakChannelOption.RAKNET_GUID) { 89 | setLocalGuid((long) value); 90 | } else if (option == RakChannelOption.RAKNET_NUMBER_OF_INTERNAL_IDS) { 91 | setMaximumNumberOfInternalIds((int) value); 92 | } else if (option == RakChannelOption.RAKNET_PROTOCOL_VERSION) { 93 | setRakNetProtocolVersion((int) value); 94 | } else if (option == RakChannelOption.RAKNET_CONNECT_MTU_SIZES) { 95 | setConnectMtuSizes((int[]) value); 96 | } else if (option == RakChannelOption.RAKNET_CONNECT_ATTEMPTS) { 97 | setConnectAttempts((int) value); 98 | } else if (option == RakChannelOption.RAKNET_CONNECT_INTERVAL) { 99 | setConnectIntervalMillis((int) value); 100 | } else if (option == RakChannelOption.RAKNET_CONNECT_TIMEOUT) { 101 | setConnectTimeoutMillis((int) value); 102 | } else if (option == RakChannelOption.RAKNET_UNRELIABLE_TIMEOUT) { 103 | setUnreliableTimeoutMillis((int) value); 104 | } else if (option == RakChannelOption.RAKNET_TIMEOUT) { 105 | setTimeoutMillis((int) value); 106 | } else { 107 | return udpChannel.config().setOption(option, value); 108 | } 109 | 110 | return true; 111 | } 112 | 113 | // Shared Properties 114 | @Override 115 | public long getLocalGuid() { 116 | if (rakChannel.isClient()) { 117 | return localGuid; 118 | } else { 119 | return rakChannel.parent().config().getLocalGuid(); 120 | } 121 | } 122 | 123 | @Override 124 | public RakChannelConfig setLocalGuid(long guid) { 125 | Validate.isTrue(rakChannel.isClient(), "Synchronised with server."); 126 | this.localGuid = guid; 127 | return this; 128 | } 129 | 130 | @Override 131 | public int getMaximumNumberOfInternalIds() { 132 | if (rakChannel.isClient()) { 133 | return numberOfInternalIds; 134 | } else { 135 | return rakChannel.parent().config().getMaximumNumberOfInternalIds(); 136 | } 137 | } 138 | 139 | @Override 140 | public RakChannelConfig setMaximumNumberOfInternalIds(int numberOfInternalIds) { 141 | Validate.isTrue(rakChannel.isClient(), "Synchronised with server."); 142 | ObjectUtil.checkPositive(numberOfInternalIds, "numberOfInternalIds"); 143 | this.numberOfInternalIds = numberOfInternalIds; 144 | return this; 145 | } 146 | 147 | @Override 148 | public int getRakNetProtocolVersion() { 149 | if (rakChannel.isClient()) { 150 | return rakProtocolVersion; 151 | } else { 152 | return rakChannel.parent().config().getRakNetProtocolVersion(); 153 | } 154 | } 155 | 156 | @Override 157 | public RakChannelConfig setRakNetProtocolVersion(int protocolVersion) { 158 | Validate.isTrue(rakChannel.isClient(), "Synchronised with server."); 159 | this.rakProtocolVersion = protocolVersion; 160 | return this; 161 | } 162 | 163 | 164 | // Client Properties 165 | @Override 166 | public int[] getConnectMtuSizes() { 167 | return mtuSizes; 168 | } 169 | 170 | @Override 171 | public RakChannelConfig setConnectMtuSizes(int[] mtuSizes) { 172 | this.mtuSizes = Arrays.stream(mtuSizes).boxed() 173 | .sorted(Comparator.reverseOrder()) 174 | .mapToInt(i -> i) 175 | .toArray(); 176 | return this; 177 | } 178 | 179 | @Override 180 | public int getConnectAttempts() { 181 | return connectAttempts; 182 | } 183 | 184 | @Override 185 | public RakChannelConfig setConnectAttempts(int connectAttempts) { 186 | ObjectUtil.checkPositive(connectAttempts, "connectAttempts"); 187 | this.connectAttempts = connectAttempts; 188 | return this; 189 | } 190 | 191 | @Override 192 | public int getConnectIntervalMillis() { 193 | return connectInterval; 194 | } 195 | 196 | @Override 197 | public RakChannelConfig setConnectIntervalMillis(int connectIntervalMillis) { 198 | ObjectUtil.checkPositive(connectIntervalMillis, "connectIntervalMillis"); 199 | this.connectInterval = connectIntervalMillis; 200 | return this; 201 | } 202 | 203 | @Override 204 | public int getConnectTimeoutMillis() { 205 | return connectTimeout; 206 | } 207 | 208 | @Override 209 | public RakChannelConfig setConnectTimeoutMillis(int connectTimeoutMillis) { 210 | ObjectUtil.checkPositiveOrZero(connectTimeoutMillis, "connectTimeoutMillis"); 211 | this.connectTimeout = connectTimeoutMillis; 212 | return this; 213 | } 214 | 215 | @Override 216 | public int getTimeoutMillis() { 217 | return timeout; 218 | } 219 | 220 | @Override 221 | public RakChannelConfig setTimeoutMillis(int timeoutMillis) { 222 | ObjectUtil.checkPositiveOrZero(timeoutMillis, "timeoutMillis"); 223 | this.timeout = timeoutMillis; 224 | return this; 225 | } 226 | 227 | @Override 228 | public int getUnreliableTimeoutMillis() { 229 | return unreliableTimeout; 230 | } 231 | 232 | @Override 233 | public RakChannelConfig setUnreliableTimeoutMillis(int unreliableTimeoutMillis) { 234 | ObjectUtil.checkPositiveOrZero(unreliableTimeoutMillis, "unreliableTimeoutMillis"); 235 | this.unreliableTimeout = unreliableTimeoutMillis; 236 | return this; 237 | } 238 | 239 | @Override 240 | public boolean isAutoRead() { 241 | return false; 242 | } 243 | 244 | @Override 245 | public ChannelConfig setAutoRead(boolean autoRead) { 246 | throw new UnsupportedOperationException(); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/ReliabilityMessageHandler.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelDuplexHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.util.ReferenceCountUtil; 7 | import io.netty.util.internal.logging.InternalLogger; 8 | import io.netty.util.internal.logging.InternalLoggerFactory; 9 | import org.nukkit.raknetty.channel.RakChannel; 10 | import org.nukkit.raknetty.channel.RakChannel.ConnectMode; 11 | import org.nukkit.raknetty.handler.codec.MessageIdentifier; 12 | import org.nukkit.raknetty.handler.codec.PacketPriority; 13 | import org.nukkit.raknetty.handler.codec.PacketReliability; 14 | 15 | import java.net.InetAddress; 16 | import java.net.InetSocketAddress; 17 | import java.net.NetworkInterface; 18 | import java.util.Arrays; 19 | import java.util.Collections; 20 | import java.util.List; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | public class ReliabilityMessageHandler extends ChannelDuplexHandler { 24 | 25 | private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(ReliabilityMessageHandler.class); 26 | public static final String NAME = "ReliabilityMessageHandler"; 27 | private static final int PING_TIMES_ARRAY_SIZE = 5; 28 | public static final int MAX_PING = 0xffff; 29 | 30 | private final RakChannel channel; 31 | 32 | private int lowestPing = MAX_PING; 33 | private final int[] pingTime = new int[PING_TIMES_ARRAY_SIZE]; 34 | private final long[] clockDiff = new long[PING_TIMES_ARRAY_SIZE]; 35 | private int pingArrayIndex = 0; 36 | private InetSocketAddress[] ipList; 37 | 38 | public ReliabilityMessageHandler(RakChannel channel) { 39 | this.channel = channel; 40 | Arrays.fill(pingTime, MAX_PING); 41 | } 42 | 43 | public int lowestPing() { 44 | return lowestPing; 45 | } 46 | 47 | public int averagePing() { 48 | if (!channel.isActive()) return -1; 49 | long sum = 0; 50 | int num = 0; 51 | 52 | for (int i = 0; i < PING_TIMES_ARRAY_SIZE; i++) { 53 | if (pingTime[i] == MAX_PING) break; 54 | 55 | sum += pingTime[i]; 56 | num++; 57 | } 58 | 59 | if (num > 0) return (int) (sum / num) & 0xffff; 60 | 61 | return -1; 62 | } 63 | 64 | public long clockDifference() { 65 | if (!channel.isActive()) return 0; 66 | 67 | int min = MAX_PING; 68 | long diff = 0; 69 | 70 | for (int i = 0; i < PING_TIMES_ARRAY_SIZE; i++) { 71 | if (pingTime[i] == MAX_PING) break; 72 | 73 | if (pingTime[i] < min) { 74 | diff = clockDiff[i]; 75 | min = pingTime[i]; 76 | } 77 | } 78 | 79 | return diff; 80 | } 81 | 82 | @Override 83 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 84 | ByteBuf buf = (ByteBuf) msg; 85 | boolean release = true; 86 | 87 | buf.markReaderIndex(); 88 | MessageIdentifier id = MessageIdentifier.readFrom(buf); 89 | ConnectMode connectMode = channel.connectMode(); 90 | 91 | // try to decode 92 | try { 93 | // For unknown senders we only accept a few specific packets 94 | if (connectMode == ConnectMode.UNVERIFIED_SENDER) { 95 | if (id != MessageIdentifier.ID_CONNECTION_REQUEST) { 96 | channel.close(); 97 | 98 | LOGGER.debug("Temporarily banning {} for sending bad data", channel.remoteAddress()); 99 | channel.parent().banList().add(channel.remoteAddress(), channel.config().getTimeoutMillis()); 100 | 101 | return; 102 | } 103 | } 104 | 105 | // if we cannot look up for a valid id, then it might be a custom packet, better pass it to the user 106 | if (id == null) { 107 | release = false; 108 | return; 109 | } 110 | 111 | switch (id) { 112 | case ID_CONNECTION_REQUEST: { 113 | if (connectMode == ConnectMode.UNVERIFIED_SENDER || connectMode == ConnectMode.REQUESTED_CONNECTION) { 114 | ConnectionRequest in = new ConnectionRequest(); 115 | in.decode(buf); 116 | channel.connectMode(ConnectMode.HANDLING_CONNECTION_REQUEST); 117 | 118 | //LOGGER.debug("CONNECTING: {}", in); 119 | 120 | ConnectionRequestAccepted out = new ConnectionRequestAccepted(); 121 | out.clientAddress = channel.remoteAddress(); 122 | out.requestTime = in.requestTime; 123 | out.replyTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); 124 | out.ipList = ipList; 125 | 126 | //LOGGER.debug("SEND: {}", out); 127 | 128 | channel.send(out, PacketPriority.IMMEDIATE_PRIORITY, PacketReliability.RELIABLE_ORDERED, 0); 129 | } 130 | break; 131 | } 132 | case ID_NEW_INCOMING_CONNECTION: { 133 | if (channel.connectMode() == ConnectMode.HANDLING_CONNECTION_REQUEST) { 134 | 135 | // set channel state to CONNECTED 136 | channel.connectMode(ConnectMode.CONNECTED); 137 | 138 | // ping the remote peer 139 | channel.ping(PacketReliability.UNRELIABLE); 140 | 141 | NewIncomingConnection in = new NewIncomingConnection(); 142 | in.decode(buf); 143 | 144 | //LOGGER.debug("CONNECTED, {}", in); 145 | 146 | onConnectedPong(in.pingTime, in.pongTime); 147 | } 148 | break; 149 | } 150 | case ID_CONNECTED_PONG: { 151 | ConnectedPong in = new ConnectedPong(); 152 | in.decode(buf); 153 | 154 | onConnectedPong(in.pingTime, in.pongTime); 155 | 156 | LOGGER.debug("{} PING: {} ms", channel, averagePing()); 157 | break; 158 | } 159 | case ID_CONNECTED_PING: { 160 | ConnectedPing in = new ConnectedPing(); 161 | in.decode(buf); 162 | 163 | long currentTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); 164 | 165 | //LOGGER.debug("PING_RECV"); 166 | 167 | ConnectedPong out = new ConnectedPong(); 168 | out.pingTime = in.pingTime; 169 | out.pongTime = currentTime; 170 | 171 | channel.send(out, PacketPriority.IMMEDIATE_PRIORITY, PacketReliability.UNRELIABLE, 0); 172 | break; 173 | } 174 | case ID_DISCONNECTION_NOTIFICATION: { 175 | LOGGER.debug("ID_DISCONNECTION_NOTIFICATION"); 176 | // do not close the channel immediately as we need to ack the ID_DISCONNECTION_NOTIFICATION 177 | channel.connectMode(ConnectMode.DISCONNECT_ON_NO_ACK); 178 | break; 179 | } 180 | // case ID_DETECT_LOST_CONNECTIONS: 181 | // case ID_INVALID_PASSWORD: 182 | case ID_CONNECTION_REQUEST_ACCEPTED: { 183 | 184 | // if we are in a situation to wait to be connected 185 | boolean canConnect = connectMode == ConnectMode.HANDLING_CONNECTION_REQUEST || 186 | connectMode == ConnectMode.REQUESTED_CONNECTION; 187 | 188 | // if we are already connected 189 | boolean hasConnected = connectMode == ConnectMode.HANDLING_CONNECTION_REQUEST; 190 | 191 | if (canConnect) { 192 | 193 | ConnectionRequestAccepted in = new ConnectionRequestAccepted(); 194 | in.decode(buf); 195 | 196 | onConnectedPong(in.requestTime, in.replyTime); 197 | 198 | //LOGGER.debug("CONNECTED: {}", in); 199 | 200 | NewIncomingConnection out = new NewIncomingConnection(); 201 | out.serverAddress = channel.remoteAddress(); 202 | out.pingTime = in.replyTime; 203 | out.pongTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); 204 | out.clientAddresses = ipList; 205 | 206 | //LOGGER.debug("SEND: {}", out); 207 | 208 | channel.send(out, PacketPriority.IMMEDIATE_PRIORITY, PacketReliability.RELIABLE_ORDERED, 0); 209 | 210 | if (!hasConnected) { 211 | channel.ping(PacketReliability.UNRELIABLE); 212 | } 213 | 214 | // set channel state to CONNECTED 215 | channel.connectMode(ConnectMode.CONNECTED); 216 | } 217 | break; 218 | } 219 | default: { 220 | // give the rest to the user 221 | release = false; 222 | } 223 | } 224 | } catch (Exception e) { 225 | LOGGER.debug("READ: bad packet {} from {}", id, channel.remoteAddress()); 226 | 227 | } finally { 228 | if (release) { 229 | ReferenceCountUtil.release(msg); 230 | } else { 231 | buf.resetReaderIndex(); 232 | ctx.fireChannelRead(buf); 233 | } 234 | } 235 | } 236 | 237 | private void onConnectedPong(long pingTime, long pongTime) { 238 | long currentTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); 239 | int ping = (int) Math.max(currentTime - pingTime, 0) & MAX_PING; 240 | 241 | this.pingTime[pingArrayIndex] = ping; 242 | this.clockDiff[pingArrayIndex] = pongTime - (currentTime / 2 + pingTime / 2); 243 | 244 | lowestPing = Math.min(ping, lowestPing) & MAX_PING; 245 | 246 | pingArrayIndex = (pingArrayIndex + 1) % PING_TIMES_ARRAY_SIZE; 247 | } 248 | 249 | @Override 250 | public void channelRegistered(ChannelHandlerContext ctx) throws Exception { 251 | ipList = new InetSocketAddress[channel.config().getMaximumNumberOfInternalIds()]; 252 | Arrays.fill(ipList, new InetSocketAddress(0)); 253 | 254 | try { 255 | List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); 256 | int index = 0; 257 | for (NetworkInterface netIf : interfaces) { 258 | // comment out the following due to poor performance 259 | //if (netIf.isLoopback()) continue; 260 | List addresses = Collections.list(netIf.getInetAddresses()); 261 | 262 | for (InetAddress address : addresses) { 263 | if (address.isLoopbackAddress()) break; 264 | if (index + 1 == ipList.length) return; 265 | 266 | ipList[index++] = new InetSocketAddress(address, channel.localAddress().getPort()); 267 | } 268 | } 269 | } catch (Exception ignored) { 270 | } 271 | 272 | super.channelRegistered(ctx); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/handler/codec/reliability/ReliabilityInboundHandler.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.handler.codec.reliability; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import io.netty.channel.socket.DatagramPacket; 7 | import io.netty.util.ReferenceCountUtil; 8 | import io.netty.util.internal.logging.InternalLogger; 9 | import io.netty.util.internal.logging.InternalLoggerFactory; 10 | import org.apache.commons.lang3.Validate; 11 | import org.nukkit.raknetty.channel.RakChannel; 12 | import org.nukkit.raknetty.handler.codec.DatagramHeader; 13 | import org.nukkit.raknetty.handler.codec.InternalPacket; 14 | import org.nukkit.raknetty.handler.codec.PacketReliability; 15 | 16 | import java.util.HashSet; 17 | import java.util.PriorityQueue; 18 | import java.util.Set; 19 | 20 | import static org.nukkit.raknetty.handler.codec.InternalPacket.NUMBER_OF_ORDERED_STREAMS; 21 | 22 | public class ReliabilityInboundHandler extends ChannelInboundHandlerAdapter { 23 | 24 | private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(ReliabilityInboundHandler.class); 25 | public static final String NAME = "ReliabilityIn"; 26 | 27 | private final RakChannel channel; 28 | private ReliabilityOutboundHandler out; 29 | 30 | /** 31 | * The packet number we are expecting 32 | */ 33 | private int receivedBaseNumber = 0; 34 | 35 | /** 36 | * The maximum packet number we have ever received 37 | */ 38 | private int receivedTopNumber = 0; 39 | 40 | private final PriorityQueue[] orderingHeaps = new PriorityQueue[NUMBER_OF_ORDERED_STREAMS]; 41 | private final SplitPacketList splitPacketList = new SplitPacketList(); 42 | private final int[] heapIndexOffsets = new int[NUMBER_OF_ORDERED_STREAMS]; 43 | private final int[] orderedReadIndex = new int[NUMBER_OF_ORDERED_STREAMS]; 44 | private final int[] sequencedReadIndex = new int[NUMBER_OF_ORDERED_STREAMS]; 45 | 46 | private final Set hasReceived = new HashSet<>(); 47 | private boolean needBandAs; 48 | private int receivedCount = 0; 49 | private long lastArrived = System.nanoTime(); 50 | 51 | public ReliabilityInboundHandler(RakChannel channel) { 52 | this.channel = channel; 53 | 54 | for (int i = 0; i < NUMBER_OF_ORDERED_STREAMS; i++) { 55 | orderingHeaps[i] = new PriorityQueue<>(64); 56 | } 57 | } 58 | 59 | @Override 60 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 61 | // ReliabilityLayer::HandleSocketReceiveFromConnectedPlayer 62 | 63 | if (out == null) { 64 | out = (ReliabilityOutboundHandler) ctx.pipeline().get(ReliabilityOutboundHandler.NAME); 65 | } 66 | 67 | long timeRead = System.nanoTime(); 68 | lastArrived = timeRead; 69 | 70 | try { 71 | DatagramPacket packet = (DatagramPacket) msg; 72 | ByteBuf buf = packet.content(); 73 | 74 | DatagramHeader header = new DatagramHeader(); 75 | header.decode(buf); 76 | 77 | // happens if first byte of packet is less than 0x80 78 | if (!header.isValid) { 79 | return; 80 | } 81 | 82 | // if received an ACK packet 83 | if (header.isAck) { 84 | AcknowledgePacket ack = new AcknowledgePacket(); 85 | ack.header = header; 86 | ack.headerLength = buf.readerIndex(); 87 | ack.decode(buf); 88 | out.onAck(ack, timeRead); 89 | receivedCount++; 90 | return; 91 | } 92 | 93 | // if received an NAK packet 94 | if (header.isNak) { 95 | AcknowledgePacket nak = new AcknowledgePacket(); 96 | nak.header = header; 97 | nak.headerLength = buf.readerIndex(); 98 | nak.decode(buf); 99 | out.onNak(nak, timeRead); 100 | receivedCount++; 101 | return; 102 | } 103 | 104 | // if it is general connected packet 105 | { 106 | int skippedDatagram = channel.slidingWindow().onGotPacket(header.datagramNumber); 107 | 108 | // Note: packet pair is no longer used 109 | // if (header.isPacketPair) 110 | 111 | // insert missing indices to NAK list 112 | for (int i = skippedDatagram; i > 0; i--) { 113 | out.sendNak(header.datagramNumber - i); 114 | } 115 | 116 | // flag if the remote system asked for B and As 117 | needBandAs = header.needsBandAs; 118 | 119 | // insert received indices to ACK list 120 | out.sendAck(header.datagramNumber); 121 | 122 | while (buf.isReadable()) { 123 | InternalPacket internalPacket = new InternalPacket(); 124 | internalPacket.header = header; 125 | //internalPacket.headerLength = buf.readerIndex(); 126 | internalPacket.decode(buf); 127 | readPacket(ctx, internalPacket); 128 | receivedCount++; 129 | } 130 | } 131 | 132 | } finally { 133 | ReferenceCountUtil.release(msg); 134 | } 135 | } 136 | 137 | public void readPacket(ChannelHandlerContext ctx, InternalPacket packet) { 138 | 139 | // check if we missed some reliable messages? 140 | if (packet.reliability.isReliable()) { 141 | int holeIndex = packet.reliableMessageNumber - receivedBaseNumber; 142 | int holeSize = receivedTopNumber - receivedBaseNumber + 1; 143 | 144 | if (holeIndex == 0) { 145 | // got a packet that we were expecting 146 | // Reliability.cpp#L956 hasReceivedPacketQueue.Pop(); 147 | hasReceived.remove(packet.reliableMessageNumber); 148 | // move the base index 149 | ++receivedBaseNumber; 150 | // fill the hole 151 | --holeSize; 152 | 153 | } else if (holeIndex < 0) { 154 | // got a duplicate packet we already got 155 | ReferenceCountUtil.release(packet); 156 | return; 157 | 158 | } else if (holeIndex < holeSize) { 159 | // got a packet that was missing in the sequence 160 | boolean received = hasReceived.contains(holeIndex); 161 | 162 | if (!received) { 163 | // this is a hole, let's fill it 164 | hasReceived.add(holeIndex); 165 | } else { 166 | // duplicate packet 167 | ReferenceCountUtil.release(packet); 168 | return; 169 | } 170 | 171 | } else { 172 | // holeIndex >= holeSize 173 | Validate.isTrue(holeIndex < 100000, "hole count too high"); 174 | // expand the hole 175 | receivedTopNumber = packet.reliableMessageNumber; 176 | hasReceived.add(receivedTopNumber); 177 | } 178 | 179 | while (holeSize > 0) { 180 | boolean received = hasReceived.contains(receivedBaseNumber); 181 | if (!received) break; 182 | 183 | hasReceived.remove(receivedBaseNumber); 184 | ++receivedBaseNumber; 185 | --holeSize; 186 | } 187 | 188 | // keep top always >= base 189 | receivedTopNumber = Math.max(receivedBaseNumber, receivedTopNumber); 190 | } 191 | 192 | // reassemble if this is a split packet 193 | if (packet.splitPacketCount > 0) { 194 | 195 | //LOGGER.debug("READ: Split packet #{} {}/{}", packet.splitPacketId, packet.splitPacketIndex, packet.splitPacketCount); 196 | 197 | if (!packet.reliability.isOrdered()) { 198 | packet.orderingChannel = 255; 199 | } 200 | 201 | splitPacketList.insert(packet); 202 | packet = splitPacketList.build(packet.splitPacketId); 203 | 204 | if (packet == null) { 205 | // we don't have every pieces yet 206 | return; 207 | } 208 | 209 | //LOGGER.debug("READ: Split packet #{} ready, size={}", packet.splitPacketId, packet.data.writerIndex()); 210 | 211 | // send ACKs immediately, because for large files this can take a long time 212 | out.doSendAck(); 213 | } 214 | 215 | // ordering packet 216 | if (packet.reliability.isOrdered()) { 217 | 218 | int orderingChannel = packet.orderingChannel; 219 | 220 | if (packet.orderingIndex == orderedReadIndex[orderingChannel]) { 221 | // got the ordered packet we are expecting 222 | 223 | if (packet.reliability.isSequenced()) { 224 | // got a sequenced packet 225 | if (!isOlderPacket(packet.sequencingIndex, sequencedReadIndex[orderingChannel])) { 226 | // expected or higher sequence value 227 | sequencedReadIndex[orderingChannel] = packet.sequencingIndex + 1; 228 | 229 | } else { 230 | // discard: lower sequence value 231 | ReferenceCountUtil.release(packet); 232 | return; 233 | } 234 | 235 | } else { 236 | // got a ordered but NOT sequenced packet, with the expecting order 237 | fireChannelRead(ctx, packet); 238 | 239 | // increment by 1 for every ordered message sent 240 | orderedReadIndex[orderingChannel]++; 241 | // reset to 0 when every ordered message sent 242 | sequencedReadIndex[orderingChannel] = 0; 243 | 244 | // check the ordering heap and write those ready to next handler 245 | PriorityQueue heap = orderingHeaps[orderingChannel]; 246 | while (heap.size() > 0) { 247 | if (heap.peek().packet.orderingIndex != orderedReadIndex[orderingChannel]) { 248 | break; 249 | } 250 | 251 | packet = heap.poll().packet; 252 | fireChannelRead(ctx, packet); 253 | 254 | if (packet.reliability == PacketReliability.RELIABLE_ORDERED) { 255 | orderedReadIndex[orderingChannel]++; 256 | 257 | } else { 258 | sequencedReadIndex[orderingChannel] = packet.sequencingIndex; 259 | } 260 | } 261 | } 262 | 263 | return; 264 | } else if (!isOlderPacket(packet.orderingIndex, orderedReadIndex[orderingChannel])) { 265 | // orderingIndex is greater 266 | 267 | // If a message has a greater ordering index, and is sequenced or ordered, buffer it 268 | // Sequenced has a lower heap weight, ordered has max sequenced weight 269 | 270 | PriorityQueue heap = orderingHeaps[orderingChannel]; 271 | if (heap.size() == 0) { 272 | heapIndexOffsets[orderingChannel] = orderedReadIndex[orderingChannel]; 273 | } 274 | 275 | int orderedHoleCount = packet.orderingIndex - heapIndexOffsets[orderingChannel]; 276 | int weight = orderedHoleCount * 0x100000; 277 | 278 | if (packet.reliability.isSequenced()) { 279 | weight += packet.sequencingIndex; 280 | } else { 281 | weight += (0x100000 - 1); 282 | } 283 | 284 | heap.offer(new HeapedPacket(weight, packet)); 285 | 286 | // buffered, return 287 | return; 288 | 289 | } else { 290 | // out of order packet, discard it 291 | ReferenceCountUtil.release(packet); 292 | return; 293 | } 294 | } 295 | 296 | // Nothing special about this packet. Move it to the next handler 297 | fireChannelRead(ctx, packet); 298 | } 299 | 300 | private void fireChannelRead(ChannelHandlerContext ctx, InternalPacket packet) { 301 | ctx.fireChannelRead(packet.data); 302 | //ctx.fireChannelRead(new ReliabilityByteEnvelop(packet.data, packet.priority, packet.reliability, packet.orderingChannel)); 303 | } 304 | 305 | private boolean isOlderPacket(int actualIndex, int expectingIndex) { 306 | // ReliabilityLayer.cpp#L2901 307 | return actualIndex < expectingIndex; 308 | } 309 | 310 | public long lastArrived() { 311 | return lastArrived; 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/main/java/org/nukkit/raknetty/channel/nio/NioRakChannel.java: -------------------------------------------------------------------------------- 1 | package org.nukkit.raknetty.channel.nio; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.*; 5 | import io.netty.channel.socket.DatagramChannel; 6 | import io.netty.channel.socket.nio.NioDatagramChannel; 7 | import io.netty.util.ReferenceCountUtil; 8 | import io.netty.util.concurrent.Future; 9 | import io.netty.util.internal.logging.InternalLogger; 10 | import io.netty.util.internal.logging.InternalLoggerFactory; 11 | import org.apache.commons.lang3.Validate; 12 | import org.nukkit.raknetty.channel.*; 13 | import org.nukkit.raknetty.handler.codec.*; 14 | import org.nukkit.raknetty.handler.codec.offline.DefaultClientOfflineHandler; 15 | import org.nukkit.raknetty.handler.codec.offline.OpenConnectionRequest1; 16 | import org.nukkit.raknetty.handler.codec.reliability.*; 17 | 18 | import java.net.ConnectException; 19 | import java.net.InetSocketAddress; 20 | import java.net.SocketAddress; 21 | import java.nio.channels.ConnectionPendingException; 22 | import java.util.concurrent.ScheduledFuture; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | import static org.nukkit.raknetty.handler.codec.InternalPacket.NUMBER_OF_ORDERED_STREAMS; 26 | import static org.nukkit.raknetty.handler.codec.reliability.ReliabilityMessageHandler.MAX_PING; 27 | 28 | public class NioRakChannel extends AbstractRakDatagramChannel implements RakChannel { 29 | 30 | private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(NioRakChannel.class); 31 | private static final ChannelMetadata METADATA = new ChannelMetadata(true); 32 | 33 | private boolean isOpen = true; 34 | 35 | private final RakChannelConfig config; 36 | private SlidingWindow slidingWindow; 37 | private ConnectMode connectMode = ConnectMode.NO_ACTION; 38 | private long timeConnected; 39 | 40 | private ChannelPromise connectPromise; 41 | private Future connectTask; 42 | private ScheduledFuture connectTimeoutFuture; 43 | 44 | private InetSocketAddress remoteAddress; 45 | private long remoteGuid; 46 | private int mtuSize; 47 | private Future updateTask; 48 | 49 | private long nextPingTime = 0; 50 | 51 | private final ReliabilityInboundHandler reliabilityIn = new ReliabilityInboundHandler(this); 52 | private final ReliabilityOutboundHandler reliabilityOut = new ReliabilityOutboundHandler(this); 53 | private final ReliabilityMessageHandler messageHandler = new ReliabilityMessageHandler(this); 54 | 55 | public NioRakChannel() { 56 | this(null); 57 | } 58 | 59 | protected NioRakChannel(final RakServerChannel parent) { 60 | this(parent, parent == null ? new NioDatagramChannel() : parent.udpChannel()); 61 | } 62 | 63 | protected NioRakChannel(final RakServerChannel parent, final DatagramChannel udpChannel) { 64 | super(parent, udpChannel); 65 | config = newConfig(); 66 | 67 | if (isClient()) { 68 | DefaultClientOfflineHandler offlineHandler = new DefaultClientOfflineHandler(this) { 69 | @Override 70 | public void connectionAttemptFailed(ChannelHandlerContext ctx, MessageIdentifier reason) { 71 | if (connectPromise != null) { 72 | connectPromise.tryFailure(new ConnectException(reason.name())); 73 | } 74 | } 75 | }; 76 | pipeline().addLast(DefaultClientOfflineHandler.NAME, offlineHandler); 77 | } 78 | 79 | pipeline().addLast(ReliabilityInboundHandler.NAME, reliabilityIn); 80 | pipeline().addLast(ReliabilityOutboundHandler.NAME, reliabilityOut); 81 | pipeline().addLast(ReliabilityMessageHandler.NAME, messageHandler); 82 | } 83 | 84 | protected RakChannelConfig newConfig() { 85 | return new DefaultRakChannelConfig(this, udpChannel()); 86 | } 87 | 88 | @Override 89 | public final boolean isClient() { 90 | return parent() == null; 91 | } 92 | 93 | @Override 94 | public int averagePing() { 95 | if (!isActive()) return -1; 96 | return messageHandler.averagePing(); 97 | } 98 | 99 | @Override 100 | public boolean isActive() { 101 | return isOpen && connectMode.isConnected(); 102 | } 103 | 104 | @Override 105 | public boolean isOpen() { 106 | return isOpen; 107 | } 108 | 109 | @Override 110 | protected void doClose() throws Exception { 111 | isOpen = false; 112 | 113 | if (isClient()) { 114 | udpChannel().close(); 115 | } else { 116 | parent().removeChildChannel(remoteAddress()); 117 | } 118 | 119 | if (connectPromise != null) { 120 | connectPromise.tryFailure(new ConnectTimeoutException()); 121 | connectPromise = null; 122 | } 123 | 124 | if (updateTask != null) { 125 | updateTask.cancel(false); 126 | updateTask = null; 127 | } 128 | } 129 | 130 | @Override 131 | protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { 132 | if (!isClient()) throw new UnsupportedOperationException(); 133 | 134 | if (localAddress == null) { 135 | localAddress = new InetSocketAddress(0); 136 | } 137 | doBind(localAddress); 138 | 139 | // already connected 140 | if (isActive()) return true; 141 | 142 | connectTask = eventLoop().submit(new ConnectionRequestTask()); 143 | return false; 144 | } 145 | 146 | @Override 147 | protected void doFinishConnect() { 148 | if (connectPromise != null) { 149 | connectPromise.trySuccess(); 150 | } 151 | } 152 | 153 | protected void doFinishConnect(Throwable cause) { 154 | if (connectPromise != null) { 155 | connectPromise.tryFailure(cause); 156 | } 157 | } 158 | 159 | @Override 160 | public ChannelFuture disconnect() { 161 | return disconnect(newPromise()); 162 | } 163 | 164 | @Override 165 | public ChannelFuture disconnect(ChannelPromise promise) { 166 | if (isOpen() && connectMode.canDisconnect()) { 167 | 168 | // send notification 169 | DisconnectionNotification out = new DisconnectionNotification(); 170 | send(out, PacketPriority.LOW_PRIORITY, PacketReliability.RELIABLE_ORDERED, 0); 171 | updateImmediately(); 172 | 173 | // wait until the disconnect notification is acknowledged 174 | connectMode(ConnectMode.DISCONNECT_ASAP); 175 | return super.disconnect(promise); 176 | } 177 | return promise; 178 | } 179 | 180 | @Override 181 | protected void doDisconnect() throws Exception { 182 | // NOOP 183 | } 184 | 185 | @Override 186 | protected void doRegister() throws Exception { 187 | super.doRegister(); 188 | 189 | // start update loop 190 | updateTask = eventLoop().submit(new UpdateCycleTask()); 191 | } 192 | 193 | private void updateImmediately() { 194 | if (!isRegistered() || updateTask == null) { 195 | // channel has not yet registered. 196 | return; 197 | } 198 | 199 | // cancel the current loop and start a new one 200 | updateTask.cancel(false); 201 | updateTask = eventLoop().submit(new UpdateCycleTask()); 202 | } 203 | 204 | public void ping(PacketReliability reliability) { 205 | //LOGGER.debug("PING"); 206 | ConnectedPing ping = new ConnectedPing(); 207 | ping.pingTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); 208 | send(ping, PacketPriority.IMMEDIATE_PRIORITY, reliability, 0); 209 | } 210 | 211 | @Override 212 | public void send(final ReliabilityMessage message, final PacketPriority priority, final PacketReliability reliability, final int orderingChannel) { 213 | if (!eventLoop().inEventLoop()) { 214 | eventLoop().execute(() -> NioRakChannel.this.send(message, priority, reliability, orderingChannel)); 215 | 216 | } else { 217 | // create with a slightly larger capacity to optimise for most split packets 218 | ByteBuf buf = alloc().ioBuffer(2048); 219 | try { 220 | message.getId().writeTo(buf); 221 | message.encode(buf); 222 | send(buf, priority, reliability, orderingChannel); 223 | 224 | } catch (Exception e) { 225 | ReferenceCountUtil.release(buf); 226 | pipeline().fireExceptionCaught(e); 227 | } 228 | } 229 | } 230 | 231 | @Override 232 | public void send(final ByteBuf message, final PacketPriority priority, final PacketReliability reliability, final int orderingChannel) { 233 | if (!eventLoop().inEventLoop()) { 234 | eventLoop().execute(() -> NioRakChannel.this.send(message, priority, reliability, orderingChannel)); 235 | 236 | } else { 237 | InternalPacket packet = new InternalPacket(); 238 | packet.data = message; 239 | 240 | packet.reliability = (reliability == null ? PacketReliability.RELIABLE : reliability); 241 | packet.priority = (priority == null ? PacketPriority.HIGH_PRIORITY : priority); 242 | packet.orderingChannel = (orderingChannel < 0 || orderingChannel > NUMBER_OF_ORDERED_STREAMS ? 0 : orderingChannel); 243 | 244 | pipeline().context(messageHandler).write(packet); 245 | //pipeline().write(packet); 246 | 247 | if (priority == PacketPriority.IMMEDIATE_PRIORITY) { 248 | updateImmediately(); 249 | } 250 | } 251 | } 252 | 253 | @Override 254 | public ConnectMode connectMode() { 255 | return connectMode; 256 | } 257 | 258 | @Override 259 | public NioRakChannel connectMode(ConnectMode mode) { 260 | boolean wasActive = isActive(); 261 | connectMode = mode; 262 | 263 | if (mode == ConnectMode.UNVERIFIED_SENDER || mode == ConnectMode.REQUESTED_CONNECTION) { 264 | // update time 265 | if (timeConnected <= 0) timeConnected = System.nanoTime(); 266 | } 267 | 268 | if (!wasActive && mode == ConnectMode.CONNECTED) { 269 | pipeline().fireChannelActive(); 270 | doFinishConnect(); 271 | } 272 | return this; 273 | } 274 | 275 | @Override 276 | public int mtuSize() { 277 | return mtuSize; 278 | } 279 | 280 | @Override 281 | public NioRakChannel mtuSize(int mtuSize) { 282 | Validate.isTrue(slidingWindow == null, "mtu size is immutable and cannot be changed."); 283 | Validate.isTrue(mtuSize > 0, "mtu size must be positive."); 284 | Validate.isTrue(mtuSize < SlidingWindow.MAXIMUM_MTU_SIZE, "mtu size too big."); 285 | 286 | this.mtuSize = mtuSize; 287 | slidingWindow = new SlidingWindow(this, mtuSize); 288 | LOGGER.debug("Sliding window is created (mtu = {}).", mtuSize); 289 | 290 | return this; 291 | } 292 | 293 | @Override 294 | public long localGuid() { 295 | return isClient() ? config().getLocalGuid() : parent().localGuid(); 296 | } 297 | 298 | @Override 299 | public long remoteGuid() { 300 | return remoteGuid; 301 | } 302 | 303 | public NioRakChannel remoteGuid(long remoteGuid) { 304 | this.remoteGuid = remoteGuid; 305 | return this; 306 | } 307 | 308 | @Override 309 | public SlidingWindow slidingWindow() { 310 | return slidingWindow; 311 | } 312 | 313 | @Override 314 | public RakServerChannel parent() { 315 | return (RakServerChannel) super.parent(); 316 | } 317 | 318 | @Override 319 | public RakChannelConfig config() { 320 | return config; 321 | } 322 | 323 | @Override 324 | public InetSocketAddress remoteAddress() { 325 | return remoteAddress; 326 | } 327 | 328 | protected NioRakChannel remoteAddress(InetSocketAddress address) { 329 | Validate.isTrue(remoteAddress == null, "remote address has already been assigned"); 330 | remoteAddress = address; 331 | return this; 332 | } 333 | 334 | @Override 335 | public ChannelMetadata metadata() { 336 | return METADATA; 337 | } 338 | 339 | @Override 340 | protected AbstractUnsafe newUnsafe() { 341 | return new NioRakUnsafe(); 342 | } 343 | 344 | private final class NioRakUnsafe extends AbstractUnsafe { 345 | 346 | @Override 347 | public void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { 348 | if (!isClient()) { 349 | throw new UnsupportedOperationException(); 350 | } 351 | 352 | if (!promise.setUncancellable() || !ensureOpen(promise)) { 353 | return; 354 | } 355 | 356 | try { 357 | if (connectPromise != null) { 358 | // Already a connect in process. 359 | throw new ConnectionPendingException(); 360 | } 361 | 362 | if (!(remoteAddress instanceof InetSocketAddress)) { 363 | promise.tryFailure(new ConnectException("remote address is not a subclass of InetSocketAddress")); 364 | return; 365 | } 366 | 367 | boolean wasActive = isActive(); 368 | if (doConnect(remoteAddress, localAddress)) { 369 | fulfillConnectPromise(promise, wasActive); 370 | 371 | } else { 372 | connectPromise = promise; 373 | NioRakChannel.this.remoteAddress = (InetSocketAddress) remoteAddress; 374 | 375 | // Schedule connect timeout. 376 | int connectTimeoutMillis = config().getConnectTimeoutMillis(); 377 | if (connectTimeoutMillis > 0) { 378 | connectTimeoutFuture = eventLoop().schedule(() -> { 379 | ChannelPromise connectPromise = NioRakChannel.this.connectPromise; 380 | 381 | if (connectPromise != null && !connectPromise.isDone() 382 | && connectPromise.tryFailure(new ConnectTimeoutException( 383 | "connection timed out: " + remoteAddress))) { 384 | close(voidPromise()); 385 | } 386 | }, connectTimeoutMillis, TimeUnit.MILLISECONDS); 387 | } 388 | 389 | promise.addListener((ChannelFutureListener) future -> { 390 | if (future.isDone()) { 391 | if (connectTimeoutFuture != null) { 392 | connectTimeoutFuture.cancel(false); 393 | } 394 | 395 | if (connectTask != null) { 396 | connectTask.cancel(false); 397 | } 398 | 399 | connectPromise = null; 400 | 401 | if (future.isSuccess()) { 402 | LOGGER.debug("{} CONNECTED", NioRakChannel.this); 403 | } else { 404 | // connection is not successful 405 | if (isOpen()) { 406 | close(voidPromise()); 407 | } 408 | 409 | if (future.isCancelled()) { 410 | LOGGER.debug("{} CONNECT: CANCELLED", NioRakChannel.this); 411 | } else { 412 | LOGGER.debug("{} CONNECT: FAILED", NioRakChannel.this); 413 | } 414 | } 415 | } 416 | }); 417 | } 418 | } catch (Throwable t) { 419 | promise.tryFailure(annotateConnectException(t, remoteAddress)); 420 | closeIfClosed(); 421 | } 422 | } 423 | 424 | private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) { 425 | if (promise == null) { 426 | return; 427 | } 428 | 429 | boolean promiseSet = promise.trySuccess(); 430 | 431 | if (!promiseSet) { 432 | close(voidPromise()); 433 | } 434 | } 435 | } 436 | 437 | private final class ConnectionRequestTask implements Runnable { 438 | 439 | private int requestsMade = 0; 440 | 441 | @Override 442 | public void run() { 443 | 444 | int attempts = config().getConnectAttempts(); 445 | int[] mtuSizes = config().getConnectMtuSizes(); 446 | int mtuNum = mtuSizes.length; 447 | 448 | if (connectMode() == ConnectMode.REQUESTED_CONNECTION || connectMode() == ConnectMode.CONNECTED) { 449 | return; 450 | } 451 | 452 | if (requestsMade >= attempts) { 453 | connectPromise.tryFailure(new ConnectTimeoutException()); 454 | return; 455 | } 456 | 457 | int mtuIndex = requestsMade / (attempts / mtuNum); 458 | if (mtuIndex >= mtuNum) mtuIndex = mtuNum - 1; 459 | 460 | requestsMade++; 461 | 462 | // schedule next connection request 463 | connectTask = eventLoop().schedule(this, config().getConnectIntervalMillis(), TimeUnit.MILLISECONDS); 464 | 465 | if (remoteAddress() != null) { 466 | OpenConnectionRequest1 request = new OpenConnectionRequest1(); 467 | request.protocol = config().getRakNetProtocolVersion(); 468 | request.mtuSize = mtuSizes[mtuIndex]; 469 | 470 | udpChannel().writeAndFlush(new AddressedOfflineMessage(request, remoteAddress())); 471 | } 472 | } 473 | } 474 | 475 | private final class UpdateCycleTask implements Runnable { 476 | @Override 477 | public void run() { 478 | 479 | boolean closeConnection = false; 480 | 481 | try { 482 | long currentTime = System.nanoTime(); 483 | 484 | if (currentTime - reliabilityOut.lastReliableSend() > TimeUnit.MILLISECONDS.toNanos(config().getTimeoutMillis() / 2) 485 | && connectMode() == ConnectMode.CONNECTED) { 486 | // send a ping when the resend list is empty so that disconnection is noticed. 487 | if (!reliabilityOut.isOutboundDataWaiting()) { 488 | ping(PacketReliability.RELIABLE); 489 | } 490 | } 491 | 492 | // check ACK timeout 493 | if (reliabilityOut.isAckTimeout(currentTime)) { 494 | // connection is dead 495 | LOGGER.debug("ACK timed out."); 496 | closeConnection = true; 497 | 498 | } else { 499 | // check failure condition 500 | switch (connectMode) { 501 | case DISCONNECT_ASAP: 502 | case DISCONNECT_ASAP_SILENTLY: 503 | if (!reliabilityOut.isOutboundDataWaiting()) { 504 | closeConnection = true; 505 | } 506 | break; 507 | 508 | case DISCONNECT_ON_NO_ACK: 509 | if (!reliabilityOut.isAckWaiting()) { 510 | closeConnection = true; 511 | } 512 | break; 513 | 514 | case REQUESTED_CONNECTION: 515 | case HANDLING_CONNECTION_REQUEST: 516 | case UNVERIFIED_SENDER: 517 | if (currentTime - timeConnected > TimeUnit.SECONDS.toNanos(10)) { 518 | closeConnection = true; 519 | } 520 | break; 521 | 522 | default: 523 | break; 524 | } 525 | } 526 | 527 | if (closeConnection) { 528 | // TODO: replace with disconnect event 529 | /* 530 | ByteBuf closeMessage = alloc().ioBuffer(1, 1); 531 | switch (connectMode) { 532 | case REQUESTED_CONNECTION: 533 | ByteUtil.writeByte(closeMessage, MessageIdentifier.ID_CONNECTION_ATTEMPT_FAILED); 534 | break; 535 | case CONNECTED: 536 | ByteUtil.writeByte(closeMessage, MessageIdentifier.ID_CONNECTION_LOST); 537 | break; 538 | case DISCONNECT_ASAP: 539 | case DISCONNECT_ON_NO_ACK: 540 | ByteUtil.writeByte(closeMessage, MessageIdentifier.ID_DISCONNECTION_NOTIFICATION); 541 | break; 542 | default: 543 | ReferenceCountUtil.release(closeMessage); 544 | break; 545 | } 546 | 547 | if (closeMessage.isReadable()) { 548 | pipeline().context(messageHandler).fireChannelRead(closeMessage); 549 | }*/ 550 | 551 | if (isOpen()) { 552 | close(); 553 | } 554 | 555 | return; 556 | } 557 | 558 | // handle the resend queue 559 | reliabilityOut.update(); 560 | 561 | // ping if it is time to do so 562 | if (connectMode == ConnectMode.CONNECTED 563 | && currentTime - nextPingTime > 0 564 | && messageHandler.lowestPing() == MAX_PING) { 565 | 566 | nextPingTime = currentTime + TimeUnit.SECONDS.toNanos(5); // ping every 5 seconds 567 | ping(PacketReliability.UNRELIABLE); 568 | } 569 | 570 | } catch (Exception e) { 571 | LOGGER.debug(e); 572 | 573 | } finally { 574 | // schedule for next update 575 | if (!closeConnection) { 576 | updateTask = eventLoop().schedule(this, 10, TimeUnit.MILLISECONDS); 577 | } 578 | } 579 | } 580 | } 581 | } 582 | --------------------------------------------------------------------------------