├── 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 |
--------------------------------------------------------------------------------