26
26
import net .jodah .expiringmap .ExpirationPolicy ;
27
27
import net .jodah .expiringmap .ExpiringMap ;
28
28
import org .cloudburstmc .netty .channel .raknet .RakChildChannel ;
29
- import org .cloudburstmc .netty .channel .raknet .RakConstants ;
30
29
import org .cloudburstmc .netty .channel .raknet .RakPing ;
31
30
import org .cloudburstmc .netty .channel .raknet .RakServerChannel ;
32
31
import org .cloudburstmc .netty .channel .raknet .config .RakChannelOption ;
33
32
import org .cloudburstmc .netty .channel .raknet .config .RakServerMetrics ;
34
33
import org .cloudburstmc .netty .handler .codec .raknet .AdvancedChannelInboundHandler ;
35
34
import org .cloudburstmc .netty .util .RakUtils ;
35
+ import org .cloudburstmc .netty .util .SecureAlgorithmProvider ;
36
36
37
37
import java .net .Inet6Address ;
38
38
import java .net .InetSocketAddress ;
39
+ import java .security .NoSuchAlgorithmException ;
40
+ import java .security .SecureRandom ;
39
41
import java .util .Arrays ;
40
42
import java .util .concurrent .TimeUnit ;
41
43
@@ -46,7 +48,15 @@ public class RakServerOfflineHandler extends AdvancedChannelInboundHandler<Datag
46
48
47
49
private static final InternalLogger log = InternalLoggerFactory .getInstance (RakServerOfflineHandler .class );
48
50
49
- private final ExpiringMap <InetSocketAddress , Integer > pendingConnections = ExpiringMap .builder ()
51
+ private final ThreadLocal <SecureRandom > random = ThreadLocal .withInitial (() -> {
52
+ try {
53
+ return SecureRandom .getInstance (SecureAlgorithmProvider .getSecurityAlgorithm ());
54
+ } catch (NoSuchAlgorithmException e ) {
55
+ return new SecureRandom ();
56
+ }
57
+ });
58
+
59
+ private final ExpiringMap <InetSocketAddress , PendingConnection > pendingConnections = ExpiringMap .builder ()
50
60
.expiration (10 , TimeUnit .SECONDS )
51
61
.expirationPolicy (ExpirationPolicy .CREATED )
52
62
.expirationListener ((key , value ) -> ReferenceCountUtil .release (value ))
@@ -163,16 +173,31 @@ private void onOpenConnectionRequest1(ChannelHandlerContext ctx, DatagramPacket
163
173
// TODO: banned address check?
164
174
// TODO: max connections check?
165
175
166
- Integer version = this .pendingConnections .put (sender , protocolVersion );
167
- if (version != null && log .isTraceEnabled ()) {
176
+
177
+ boolean sendCookie = ctx .channel ().config ().getOption (RakChannelOption .RAK_SEND_COOKIE );
178
+ int cookie ;
179
+
180
+ if (sendCookie ) {
181
+ cookie = this .random .get ().nextInt ();
182
+ } else {
183
+ cookie = 0 ;
184
+ }
185
+
186
+ PendingConnection connection = this .pendingConnections .put (sender , new PendingConnection (protocolVersion , cookie ));
187
+ if (connection != null && log .isTraceEnabled ()) {
168
188
log .trace ("Received duplicate open connection request 1 from {}" , sender );
169
189
}
170
190
171
- ByteBuf replyBuffer = ctx .alloc ().ioBuffer (28 , 28 );
191
+ int bufferCapacity = sendCookie ? 32 : 28 ; // 4 byte cookie
192
+
193
+ ByteBuf replyBuffer = ctx .alloc ().ioBuffer (bufferCapacity , bufferCapacity );
172
194
replyBuffer .writeByte (ID_OPEN_CONNECTION_REPLY_1 );
173
195
replyBuffer .writeBytes (magicBuf , magicBuf .readerIndex (), magicBuf .readableBytes ());
174
196
replyBuffer .writeLong (guid );
175
- replyBuffer .writeBoolean (false ); // Security
197
+ replyBuffer .writeBoolean (sendCookie ); // Security
198
+ if (sendCookie ) {
199
+ replyBuffer .writeInt (cookie );
200
+ }
176
201
replyBuffer .writeShort (RakUtils .clamp (mtu , ctx .channel ().config ().getOption (RakChannelOption .RAK_MIN_MTU ), ctx .channel ().config ().getOption (RakChannelOption .RAK_MAX_MTU )));
177
202
ctx .writeAndFlush (new DatagramPacket (replyBuffer , sender ));
178
203
}
@@ -183,18 +208,31 @@ private void onOpenConnectionRequest2(ChannelHandlerContext ctx, DatagramPacket
183
208
// Skip already verified magic
184
209
buffer .skipBytes (magicBuf .readableBytes ());
185
210
186
- Integer version = this . pendingConnections . remove ( sender );
187
- if ( version == null ) {
188
- // We can't determine the version without the previous request, so assume it's the wrong version.
211
+
212
+ PendingConnection connection = this . pendingConnections . remove ( sender );
213
+ if ( connection == null ) {
189
214
if (log .isTraceEnabled ()) {
190
215
log .trace ("Received open connection request 2 from {} without open connection request 1" , sender );
191
216
}
192
- int [] supportedProtocols = ctx .channel ().config ().getOption (RakChannelOption .RAK_SUPPORTED_PROTOCOLS );
193
- int latestVersion = supportedProtocols == null ? RakConstants .RAKNET_PROTOCOL_VERSION : supportedProtocols [supportedProtocols .length - 1 ];
194
- this .sendIncompatibleVersion (ctx , sender , latestVersion , magicBuf , guid );
217
+ // Don't respond yet as we cannot verify the connection source IP
195
218
return ;
196
219
}
197
220
221
+ boolean sendCookie = ctx .channel ().config ().getOption (RakChannelOption .RAK_SEND_COOKIE );
222
+ if (sendCookie ) {
223
+ int cookie = buffer .readInt ();
224
+ int expectedCookie = connection .getCookie ();
225
+ if (expectedCookie != cookie ) {
226
+ if (log .isTraceEnabled ()) {
227
+ log .trace ("Received open connection request 2 from {} with invalid cookie (expected {}, but received {})" , sender , expectedCookie , cookie );
228
+ }
229
+ // Incorrect cookie provided
230
+ // This is likely source IP spoofing so we will not reply
231
+ return ;
232
+ }
233
+ buffer .readBoolean (); // Client wrote challenge
234
+ }
235
+
198
236
// TODO: Verify serverAddress matches?
199
237
InetSocketAddress serverAddress = RakUtils .readAddress (buffer );
200
238
int mtu = buffer .readUnsignedShort ();
@@ -207,7 +245,7 @@ private void onOpenConnectionRequest2(ChannelHandlerContext ctx, DatagramPacket
207
245
}
208
246
209
247
RakServerChannel serverChannel = (RakServerChannel ) ctx .channel ();
210
- RakChildChannel channel = serverChannel .createChildChannel (sender , clientGuid , version , mtu );
248
+ RakChildChannel channel = serverChannel .createChildChannel (sender , clientGuid , connection . getProtocolVersion () , mtu );
211
249
if (channel == null ) {
212
250
// Already connected
213
251
this .sendAlreadyConnected (ctx , sender , magicBuf , guid );
@@ -240,4 +278,22 @@ private void sendAlreadyConnected(ChannelHandlerContext ctx, InetSocketAddress s
240
278
buffer .writeLong (guid );
241
279
ctx .writeAndFlush (new DatagramPacket (buffer , sender ));
242
280
}
281
+
282
+ private class PendingConnection {
283
+ private final int protocolVersion ;
284
+ private final int cookie ;
285
+
286
+ public PendingConnection (int protocolVersion , int cookie ) {
287
+ this .protocolVersion = protocolVersion ;
288
+ this .cookie = cookie ;
289
+ }
290
+
291
+ public int getProtocolVersion () {
292
+ return this .protocolVersion ;
293
+ }
294
+
295
+ public int getCookie () {
296
+ return this .cookie ;
297
+ }
298
+ }
243
299
}
0 commit comments