Skip to content

Commit 9595762

Browse files
authored
Detect Network Settings Game Packet in compatibility mode (#1)
* Initial work on handler to detect client request network settings Signed-off-by: GitHub <noreply@github.com> * Add handler Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Add handler Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Detect Network Settings Game Packet in compatibility mode Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Release 1.2.0 --------- Signed-off-by: GitHub <noreply@github.com> Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com>
1 parent 553e97e commit 9595762

File tree

9 files changed

+336
-113
lines changed

9 files changed

+336
-113
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ You can join the [Discord](https://discord.gg/5z4GuSnqmQ) for help with this for
99
- New incoming connection batches additional packets to more closely imitate the vanilla client:
1010
- A `Connected Ping`
1111
- The first game packet, `Request Network Settings Packet`
12+
- Attempts to detect it from the connection, but uses `RakChannelOption.RAK_CLIENT_BEDROCK_PROTOCOL_VERSION` if it is not detected in the pipeline
1213
- Allows for resetting security state if `Open Connection Reply 1` is resent by the server
1314
- Only do retries with `Open Connection Request 1`, and reserve `Open Connection Request 2` only as a direct response to `Open Connection Reply 1`
1415
- Allows using datagram channel factories for raknet (from [@AlexProgrammerDE](https://github.com/AlexProgrammerDE))

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# Only update version on publishing to Maven Central
2-
version=1.1.0
2+
version=1.2.0

transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelOption.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public class RakChannelOption<T> extends ChannelOption<T> {
190190
valueOf(RakChannelOption.class, "RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS");
191191

192192
/**
193-
* The protocol version of the RakNet client for sending RequestNetworkSettingsPacket in compatibility mode.
193+
* The fllback protocol version of the RakNet client for sending RequestNetworkSettingsPacket in compatibility mode if one is not found in the pipeline.
194194
*/
195195
public static final ChannelOption<Integer> RAK_CLIENT_BEDROCK_PROTOCOL_VERSION =
196196
valueOf(RakChannelOption.class, "RAK_CLIENT_BEDROCK_PROTOCOL_VERSION");
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.cloudburstmc.netty.handler.codec.raknet.client;
2+
3+
import io.netty.buffer.ByteBuf;
4+
import io.netty.channel.ChannelHandlerContext;
5+
import io.netty.channel.ChannelOutboundHandlerAdapter;
6+
import io.netty.channel.ChannelPromise;
7+
import io.netty.util.AttributeKey;
8+
import io.netty.util.internal.logging.InternalLogger;
9+
import io.netty.util.internal.logging.InternalLoggerFactory;
10+
import org.cloudburstmc.netty.channel.raknet.RakChannel;
11+
import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption;
12+
13+
import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_GAME_PACKET;
14+
15+
public class RakClientNetworkSettingsHandler extends ChannelOutboundHandlerAdapter {
16+
public static final String NAME = "rak-client-network-settings-handler";
17+
public static final AttributeKey<ByteBuf> NETWORK_SETTINGS_PAYLOAD = AttributeKey.valueOf("network-settings-payload");
18+
private static final InternalLogger log = InternalLoggerFactory.getInstance(RakClientNetworkSettingsHandler.class);
19+
20+
private final RakChannel channel;
21+
22+
public RakClientNetworkSettingsHandler(RakChannel channel) {
23+
this.channel = channel;
24+
}
25+
26+
@Override
27+
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
28+
if (!(msg instanceof ByteBuf)) {
29+
ctx.write(msg, promise);
30+
return;
31+
}
32+
33+
ByteBuf packet = (ByteBuf) msg;
34+
35+
if (packet.capacity() < 4) {
36+
ctx.write(msg, promise);
37+
return;
38+
}
39+
40+
if (packet.getByte(0) != (byte) ID_GAME_PACKET) {
41+
ctx.write(msg, promise);
42+
return;
43+
}
44+
45+
int rakVersion = this.channel.config().getOption(RakChannelOption.RAK_PROTOCOL_VERSION);
46+
47+
switch (rakVersion) {
48+
case 11:
49+
case 10:
50+
case 9:
51+
if (packet.getByte(1) != (byte) 0x06) break;
52+
if (packet.getByte(2) != (byte) 0xc1) break;
53+
if ((packet.getByte(3) & (byte) 0b10000111) != (byte) 0b00000001) break;
54+
onNetworkSettings(ctx, packet);
55+
return;
56+
case 8:
57+
if (packet.getByte(1) != (byte) 0x07) break;
58+
if (packet.getByte(2) != 0xc1) break;
59+
onNetworkSettings(ctx, packet);
60+
return;
61+
case 7:
62+
if (packet.getByte(1) != (byte) 0x05) break;
63+
if (packet.getByte(2) != (byte) 0xc1) break;
64+
onNetworkSettings(ctx, packet);
65+
return;
66+
default:
67+
throw new UnsupportedOperationException("Unsupported protocol version: " + rakVersion);
68+
}
69+
70+
ctx.write(msg, promise);
71+
}
72+
73+
private void onNetworkSettings(ChannelHandlerContext ctx, ByteBuf packet) {
74+
log.info("Detected network settings packet, removing handler");
75+
ctx.channel().attr(NETWORK_SETTINGS_PAYLOAD).set(packet.retain());
76+
ctx.channel().pipeline().remove(RakClientNetworkSettingsHandler.NAME);
77+
}
78+
}

transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandler.java

Lines changed: 75 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,16 @@ public RakClientOfflineHandler(RakChannel rakChannel, ChannelPromise promise) {
5555
@Override
5656
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
5757
Channel channel = ctx.channel();
58-
long timeout = this.rakChannel.config().getOption(RakChannelOption.RAK_CONNECT_TIMEOUT);
58+
long timeout = this.rakChannel().config().getOption(RakChannelOption.RAK_CONNECT_TIMEOUT);
5959
this.timeoutFuture = channel.eventLoop().schedule(this::onTimeout, timeout, TimeUnit.MILLISECONDS);
6060
this.retryFuture = channel.eventLoop().scheduleAtFixedRate(() -> this.onRetryAttempt(channel), 0,
61-
this.rakChannel.config().getOption(RakChannelOption.RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS), TimeUnit.MILLISECONDS);
62-
this.successPromise.addListener(future -> safeCancel(this.timeoutFuture, channel));
63-
this.successPromise.addListener(future -> safeCancel(this.retryFuture, channel));
61+
this.rakChannel().config().getOption(RakChannelOption.RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS), TimeUnit.MILLISECONDS);
62+
this.successPromise().addListener(future -> safeCancel(this.timeoutFuture, channel));
63+
this.successPromise().addListener(future -> safeCancel(this.retryFuture, channel));
6464

6565
this.retryFuture.addListener(future -> {
6666
if (future.cause() != null) {
67-
this.successPromise.tryFailure(future.cause());
67+
this.successPromise().tryFailure(future.cause());
6868
}
6969
});
7070
}
@@ -75,32 +75,25 @@ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
7575
safeCancel(this.retryFuture, ctx.channel());
7676
}
7777

78-
private void onRetryAttempt(Channel channel) {
79-
if (this.rakChannel.config().getOption(RakChannelOption.RAK_COMPATIBILITY_MODE)) {
80-
if (this.state != RakOfflineState.HANDSHAKE_COMPLETED) {
78+
void onRetryAttempt(Channel channel) {
79+
switch (this.state()) {
80+
case HANDSHAKE_1:
8181
this.sendOpenConnectionRequest1(channel);
82-
this.connectionAttempts++;
83-
}
84-
} else {
85-
switch (this.state) {
86-
case HANDSHAKE_1:
87-
this.sendOpenConnectionRequest1(channel);
88-
this.connectionAttempts++;
89-
break;
90-
case HANDSHAKE_2:
91-
this.sendOpenConnectionRequest2(channel);
92-
break;
93-
}
82+
this.incrementConnectionAttempts();
83+
break;
84+
case HANDSHAKE_2:
85+
this.sendOpenConnectionRequest2(channel);
86+
break;
9487
}
9588
}
9689

97-
private void onTimeout() {
98-
this.successPromise.tryFailure(new ConnectTimeoutException());
90+
void onTimeout() {
91+
this.successPromise().tryFailure(new ConnectTimeoutException());
9992
}
10093

101-
private void onSuccess(ChannelHandlerContext ctx) {
94+
void onSuccess(ChannelHandlerContext ctx) {
10295
// Create new session which decodes RakDatagramPacket to RakMessage
103-
RakSessionCodec sessionCodec = new RakSessionCodec(this.rakChannel);
96+
RakSessionCodec sessionCodec = new RakSessionCodec(this.rakChannel());
10497
ctx.pipeline().addAfter(NAME, RakDatagramCodec.NAME, new RakDatagramCodec());
10598
ctx.pipeline().addAfter(RakDatagramCodec.NAME, RakAcknowledgeHandler.NAME, new RakAcknowledgeHandler(sessionCodec));
10699
ctx.pipeline().addAfter(RakAcknowledgeHandler.NAME, RakSessionCodec.NAME, sessionCodec);
@@ -109,8 +102,7 @@ private void onSuccess(ChannelHandlerContext ctx) {
109102
ctx.pipeline().addAfter(ConnectedPongHandler.NAME, DisconnectNotificationHandler.NAME, DisconnectNotificationHandler.INSTANCE);
110103
// Replicate server behavior, and transform unhandled encapsulated packets to rakMessage
111104
ctx.pipeline().addAfter(DisconnectNotificationHandler.NAME, EncapsulatedToMessageHandler.NAME, EncapsulatedToMessageHandler.INSTANCE);
112-
ctx.pipeline().addAfter(DisconnectNotificationHandler.NAME, RakClientOnlineInitialHandler.NAME, new RakClientOnlineInitialHandler(this.rakChannel, this.successPromise));
113-
ctx.pipeline().fireChannelActive();
105+
ctx.pipeline().addAfter(DisconnectNotificationHandler.NAME, RakClientOnlineInitialHandler.NAME, new RakClientOnlineInitialHandler(this.rakChannel(), this.successPromise()));
114106
}
115107

116108
@Override
@@ -119,16 +111,16 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Excep
119111
return; // Empty packet?
120112
}
121113

122-
if (this.state == RakOfflineState.HANDSHAKE_COMPLETED) {
114+
if (this.state() == RakOfflineState.HANDSHAKE_COMPLETED) {
123115
// Forward open connection messages if handshake was completed
124116
ctx.fireChannelRead(buf.retain());
125117
return;
126118
}
127119

128120
short packetId = buf.readUnsignedByte();
129-
ByteBuf magicBuf = this.rakChannel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC);
121+
ByteBuf magicBuf = this.rakChannel().config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC);
130122
if (!buf.isReadable(magicBuf.readableBytes()) || !ByteBufUtil.equals(buf.readSlice(magicBuf.readableBytes()), magicBuf)) {
131-
this.successPromise.tryFailure(new CorruptedFrameException("RakMagic does not match"));
123+
this.successPromise().tryFailure(new CorruptedFrameException("RakMagic does not match"));
132124
return;
133125
}
134126

@@ -139,27 +131,28 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Excep
139131
case ID_OPEN_CONNECTION_REPLY_2:
140132
this.onOpenConnectionReply2(ctx, buf);
141133
this.onSuccess(ctx);
134+
ctx.pipeline().fireChannelActive();
142135
return;
143136
case ID_INCOMPATIBLE_PROTOCOL_VERSION:
144-
this.rakChannel.pipeline().fireUserEventTriggered(RakDisconnectReason.INCOMPATIBLE_PROTOCOL_VERSION);
145-
this.successPromise.tryFailure(new IllegalStateException("Incompatible raknet version"));
137+
this.rakChannel().pipeline().fireUserEventTriggered(RakDisconnectReason.INCOMPATIBLE_PROTOCOL_VERSION);
138+
this.successPromise().tryFailure(new IllegalStateException("Incompatible raknet version"));
146139
return;
147140
case ID_ALREADY_CONNECTED:
148-
this.rakChannel.pipeline().fireUserEventTriggered(RakDisconnectReason.ALREADY_CONNECTED);
149-
this.successPromise.tryFailure(new ChannelException("Already connected"));
141+
this.rakChannel().pipeline().fireUserEventTriggered(RakDisconnectReason.ALREADY_CONNECTED);
142+
this.successPromise().tryFailure(new ChannelException("Already connected"));
150143
return;
151144
case ID_NO_FREE_INCOMING_CONNECTIONS:
152-
this.rakChannel.pipeline().fireUserEventTriggered(RakDisconnectReason.NO_FREE_INCOMING_CONNECTIONS);
153-
this.successPromise.tryFailure(new ChannelException("No free incoming connections"));
145+
this.rakChannel().pipeline().fireUserEventTriggered(RakDisconnectReason.NO_FREE_INCOMING_CONNECTIONS);
146+
this.successPromise().tryFailure(new ChannelException("No free incoming connections"));
154147
return;
155148
case ID_IP_RECENTLY_CONNECTED:
156-
this.rakChannel.pipeline().fireUserEventTriggered(RakDisconnectReason.IP_RECENTLY_CONNECTED);
157-
this.successPromise.tryFailure(new ChannelException("Address recently connected"));
149+
this.rakChannel().pipeline().fireUserEventTriggered(RakDisconnectReason.IP_RECENTLY_CONNECTED);
150+
this.successPromise().tryFailure(new ChannelException("Address recently connected"));
158151
return;
159152
}
160153
}
161154

162-
private void onOpenConnectionReply1(ChannelHandlerContext ctx, ByteBuf buffer) {
155+
void onOpenConnectionReply1(ChannelHandlerContext ctx, ByteBuf buffer) {
163156
long serverGuid = buffer.readLong();
164157
boolean security = buffer.readBoolean();
165158
if (security) {
@@ -170,38 +163,35 @@ private void onOpenConnectionReply1(ChannelHandlerContext ctx, ByteBuf buffer) {
170163
}
171164
int mtu = buffer.readShort();
172165

173-
this.rakChannel.config().setOption(RakChannelOption.RAK_MTU, mtu);
174-
this.rakChannel.config().setOption(RakChannelOption.RAK_REMOTE_GUID, serverGuid);
166+
this.rakChannel().config().setOption(RakChannelOption.RAK_MTU, mtu);
167+
this.rakChannel().config().setOption(RakChannelOption.RAK_REMOTE_GUID, serverGuid);
175168

176-
this.state = RakOfflineState.HANDSHAKE_2;
169+
this.state(RakOfflineState.HANDSHAKE_2);
177170
this.sendOpenConnectionRequest2(ctx.channel());
178171
}
179172

180-
private void onOpenConnectionReply2(ChannelHandlerContext ctx, ByteBuf buffer) {
173+
void onOpenConnectionReply2(ChannelHandlerContext ctx, ByteBuf buffer) {
181174
buffer.readLong(); // serverGuid
182-
if (this.rakChannel.config().getOption(RakChannelOption.RAK_COMPATIBILITY_MODE)) {
183-
RakUtils.skipAddress(buffer); // serverAddress
184-
} else {
185-
RakUtils.readAddress(buffer); // serverAddress
186-
}
175+
RakUtils.readAddress(buffer); // serverAddress
176+
187177
int mtu = buffer.readShort();
188178
boolean security = buffer.readBoolean(); // security
189179
if (security) {
190-
this.successPromise.tryFailure(new SecurityException());
180+
this.successPromise().tryFailure(new SecurityException());
191181
return;
192182
}
193183

194-
this.rakChannel.config().setOption(RakChannelOption.RAK_MTU, mtu);
195-
this.state = RakOfflineState.HANDSHAKE_COMPLETED;
184+
this.rakChannel().config().setOption(RakChannelOption.RAK_MTU, mtu);
185+
this.state(RakOfflineState.HANDSHAKE_COMPLETED);
196186
}
197187

198-
private void sendOpenConnectionRequest1(Channel channel) {
199-
int mtuSizeIndex = Math.min(this.connectionAttempts / 4, this.rakChannel.config().getOption(RakChannelOption.RAK_MTU_SIZES).length - 1);
200-
int mtuSize = this.rakChannel.config().getOption(RakChannelOption.RAK_MTU_SIZES)[mtuSizeIndex];
188+
void sendOpenConnectionRequest1(Channel channel) {
189+
int mtuSizeIndex = Math.min(this.connectionAttempts() / 4, this.rakChannel().config().getOption(RakChannelOption.RAK_MTU_SIZES).length - 1);
190+
int mtuSize = this.rakChannel().config().getOption(RakChannelOption.RAK_MTU_SIZES)[mtuSizeIndex];
201191

202-
ByteBuf magicBuf = this.rakChannel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC);
203-
int rakVersion = this.rakChannel.config().getOption(RakChannelOption.RAK_PROTOCOL_VERSION);
204-
InetSocketAddress address = (InetSocketAddress) this.rakChannel.remoteAddress();
192+
ByteBuf magicBuf = this.rakChannel().config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC);
193+
int rakVersion = this.rakChannel().config().getOption(RakChannelOption.RAK_PROTOCOL_VERSION);
194+
InetSocketAddress address = (InetSocketAddress) this.rakChannel().remoteAddress();
205195

206196
ByteBuf request = channel.alloc().ioBuffer(mtuSize);
207197
request.writeByte(ID_OPEN_CONNECTION_REQUEST_1);
@@ -212,9 +202,9 @@ private void sendOpenConnectionRequest1(Channel channel) {
212202
channel.writeAndFlush(request);
213203
}
214204

215-
private void sendOpenConnectionRequest2(Channel channel) {
216-
int mtuSize = this.rakChannel.config().getOption(RakChannelOption.RAK_MTU);
217-
ByteBuf magicBuf = this.rakChannel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC);
205+
void sendOpenConnectionRequest2(Channel channel) {
206+
int mtuSize = this.rakChannel().config().getOption(RakChannelOption.RAK_MTU);
207+
ByteBuf magicBuf = this.rakChannel().config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC);
218208

219209
ByteBuf request = channel.alloc().ioBuffer(this.security ? 39 : 34);
220210
request.writeByte(ID_OPEN_CONNECTION_REQUEST_2);
@@ -225,11 +215,35 @@ private void sendOpenConnectionRequest2(Channel channel) {
225215
}
226216
RakUtils.writeAddress(request, (InetSocketAddress) channel.remoteAddress());
227217
request.writeShort(mtuSize);
228-
request.writeLong(this.rakChannel.config().getOption(RakChannelOption.RAK_GUID));
218+
request.writeLong(this.rakChannel().config().getOption(RakChannelOption.RAK_GUID));
229219
channel.writeAndFlush(request);
230220
}
231221

232-
private static void safeCancel(ScheduledFuture<?> future, Channel channel) {
222+
RakOfflineState state() {
223+
return this.state;
224+
}
225+
226+
void state(RakOfflineState state) {
227+
this.state = state;
228+
}
229+
230+
int connectionAttempts() {
231+
return this.connectionAttempts;
232+
}
233+
234+
void incrementConnectionAttempts() {
235+
this.connectionAttempts++;
236+
}
237+
238+
RakChannel rakChannel() {
239+
return this.rakChannel;
240+
}
241+
242+
ChannelPromise successPromise() {
243+
return this.successPromise;
244+
}
245+
246+
static void safeCancel(ScheduledFuture<?> future, Channel channel) {
233247
channel.eventLoop().execute(() -> { // Make sure this is not called at two places at the same time
234248
if (!future.isCancelled()) {
235249
future.cancel(false);

0 commit comments

Comments
 (0)