1 /* 2 * Copyright (c) 2017-2018 SEL 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 12 * See the GNU Lesser General Public License for more details. 13 * 14 */ 15 /** 16 * Copyright: 2017-2020 sel-project 17 * License: LGPL-3.0 18 * Authors: Kripth 19 * Source: $(HTTP github.com/sel-project/sel-server/sel/server/java.d, sel/server/java.d) 20 */ 21 module sel.server.java; 22 23 debug import core.thread : Thread; 24 25 import std.algorithm : canFind, all; 26 import std.bitmanip : nativeToBigEndian; 27 import std.concurrency : spawn; 28 import std.conv : to; 29 import std.datetime : dur, usecs; 30 import std.datetime.stopwatch : StopWatch; 31 import std.json : JSONValue; 32 import std.socket : Address, Socket, TcpSocket, UdpSocket, SocketOptionLevel, SocketOption; 33 import std.uuid : UUID, randomUUID; 34 35 import sel.net.modifiers : ModifierStream, LengthPrefixedStream, CompressedStream; 36 import sel.net.stream : Stream, TcpStream, UdpStream; 37 import sel.server.client : Client; 38 import sel.server.query : Query; 39 import sel.server.server; 40 41 import sul.protocol.java340.status; 42 import sul.protocol.java340.login; 43 44 import sul.utils.var : varuint; 45 46 debug import std.stdio : writeln; 47 48 public enum string[][uint] javaSupportedProtocols = [ 49 210u: ["1.10", "1.10.1", "1.10.2"], 50 315u: ["1.11", "1.12"], 51 316u: ["1.11.2"], 52 335u: ["1.12"], 53 338u: ["1.12.1"], 54 340u: ["1.12.2"], 55 ]; 56 57 class JavaServer : GenericGameServer { 58 59 public shared this(shared ServerInfo info, uint[] protocols=supportedProtocols.keys, shared Handler handler=new shared Handler()) { 60 super(info, protocols, supportedProtocols.keys, handler); 61 } 62 63 public shared this(shared ServerInfo info, shared Handler handler) { 64 this(info, supportedProtocols.keys, handler); 65 } 66 67 public override shared pure nothrow @property @safe @nogc ushort defaultPort() { 68 return ushort(25565); 69 } 70 71 /** 72 * Starts the server in a new thread. 73 */ 74 protected override shared void startImpl(Address address, shared Query query) { 75 Socket socket = new TcpSocket(address.addressFamily); 76 socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true); 77 socket.blocking = true; 78 socket.bind(address); 79 socket.listen(10); 80 spawn(&this.acceptClients, cast(shared)socket); 81 if(query !is null) { 82 Socket qsocket = new UdpSocket(address.addressFamily); 83 qsocket.blocking = true; 84 qsocket.bind(address); 85 spawn(&this.acceptQueries, cast(shared)qsocket, query); 86 } 87 } 88 89 /** 90 * Starts an UDP socket that is only used for the external 91 * query protocol. 92 */ 93 protected shared void acceptQueries(shared Socket _socket, shared Query query) { 94 debug Thread.getThis().name = "java_server@" ~ (cast()_socket).localAddress.toString() ~ "/accept_queries"; 95 UdpStream stream = new UdpStream(cast()_socket); 96 Query.Handler handler; 97 with(stream.socket.localAddress) handler = (cast()query).new Handler("MINECRAFT", toAddrString(), to!ushort(toPortString())); 98 Address address; 99 while(true) { 100 ubyte[] buffer = stream.receiveFrom(address); 101 if(buffer.length >= 2 && buffer[0] == 254 && buffer[1] == 253) { 102 auto data = handler.handle(buffer[2..$]); 103 if(data.length) { 104 debug writeln(cast(string)data); 105 stream.sendTo(data, address); 106 } 107 } 108 } 109 } 110 111 /** 112 * Accepts new connection and handle in a new thread. 113 */ 114 protected shared void acceptClients(shared Socket _socket) { 115 debug Thread.getThis().name = "java_server@" ~ (cast()_socket).localAddress.toString() ~ "/accept_clients"; 116 Socket socket = cast()_socket; 117 while(true) { 118 //Socket client = socket.accept(); 119 spawn(&this.handleNewClient, cast(shared)socket.accept()); 120 } 121 } 122 123 protected shared void handleNewClient(shared Socket _client) { 124 Socket client = cast()_client; 125 debug Thread.getThis().name = "java_client@" ~ client.remoteAddress.toString() ~ "/handle"; 126 client.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(8)); 127 ubyte[] buffer = new ubyte[96]; 128 auto recv = client.receive(buffer); 129 if(recv > 0) { 130 // valid packet received, handle 131 if(buffer[0] == 254) { 132 //TODO legacy ping 133 } else { 134 // read as a normal minecraft packet (length, id, payload) 135 immutable length = varuint.fromBuffer(buffer); 136 if(length != 0 && length < recv) { 137 if(varuint.fromBuffer(buffer) == Handshake.ID) { 138 Handshake handshake = Handshake.fromBuffer!false(buffer); 139 auto stream = new LengthPrefixedStream!varuint(new TcpStream(client), buffer.length); // first packets should be pretty small 140 if(handshake.next == Handshake.LOGIN) { 141 // keep this thread for the player's session 142 stream.maxLength = 1024; 143 this.handleNewPlayer(stream, handshake); 144 } else { 145 // status 146 ubyte[] request = stream.receive(); 147 if(request.length == 1 && request[0] == Request.ID) { 148 stream.send(new Response(JSONValue(this.getPingResponse(client, handshake.protocol, handshake.serverAddress, handshake.serverPort)).toString()).encode()); 149 // handle optional latency calculation 150 // connection timeout is still set to 8 seconds 151 ubyte[] latency = stream.receive(); 152 if(latency.length == 9 && latency[0] == Latency.ID) { 153 // send back the exact same packet 154 stream.send(latency); 155 } 156 // blocking socket should be closed only after sending everything 157 } 158 } 159 } 160 } 161 } 162 } 163 client.close(); 164 } 165 166 /** 167 * Gets the JSON informations that the client will use to display the server 168 * in its servers list. 169 * This method can be overridden by custom implementations of the server. 170 */ 171 protected shared JSONValue[string] getPingResponse(Socket client, uint protocol, string ip, ushort port) { 172 if(!this.protocols.canFind(protocol)) protocol = this.protocols[$-1]; 173 JSONValue[string] ret; 174 ret["description"] = this.info.motd.java; 175 ret["version"] = ["protocol": JSONValue(protocol), "name": JSONValue(javaSupportedProtocols[protocol][0])]; 176 ret["players"] = ["online": JSONValue(this.info.online), "max": JSONValue(this.info.max)]; 177 if(this.info.favicon.length) ret["favicon"] = this.info.favicon; 178 return ret; 179 } 180 181 /** 182 * Handles a new player connection after the handshake packet with 183 * the status set to "login". 184 * At this state the socket should be blocking with the timeout set to 8 seconds. 185 */ 186 protected shared void handleNewPlayer(Stream stream, Handshake handshake) { 187 // receive the login packet 188 ubyte[] loginp = stream.receive(); 189 if(varuint.fromBuffer(loginp) == LoginStart.ID) { 190 LoginStart login = LoginStart.fromBuffer!false(loginp); 191 // start compression 192 stream.send(new SetCompression(1024).encode()); 193 stream = new CompressedStream!varuint(stream, 1024); 194 // perform validations 195 immutable disconnect = this.validatePlayer(login.username, stream.socket.remoteAddress, handshake.protocol, handshake.serverAddress, handshake.serverPort); 196 if(disconnect.length) { 197 stream.send(new Disconnect(JSONValue(["text": disconnect]).toString()).encode()); 198 //stream.socket.close(); 199 } else { 200 // send a login success 201 //TODO encryption 202 UUID uuid = randomUUID(); 203 stream.send(new LoginSuccess(uuid.toString(), login.username).encode()); 204 // start real session 205 shared JavaClient session = new shared JavaClient(handshake.protocol, stream, login.username, uuid); 206 this.onClientJoin(session); 207 session.start(this); // blocking operation, returns when the session is closed 208 this.onClientLeft(session); 209 } 210 } 211 } 212 213 protected shared string validatePlayer(string username, Address address, uint protocol, string usedIp, ushort usedPort) { 214 if(!this.protocols.canFind(protocol)) return protocol > this.protocols[$-1] ? "Outdated Server!" : "Outdated Client!"; 215 if(username.length < 3 || username.length > 16 || !username.all!(a => a >= '0' && a <= '9' || a >= 'A' && a <= 'Z' || a >= 'a' && a <= 'z' || a == '_')) return "Invalid Username"; 216 return ""; 217 } 218 219 } 220 221 class JavaClient : Client { 222 223 private shared Stream stream; 224 225 private immutable ubyte serverboundKeepAliveId; 226 private immutable ubyte clientboundKeepAliveId; 227 private ubyte[] delegate(uint) encodeKeepAlive; 228 229 public shared this(uint protocol, Stream stream, string username, UUID uuid) { 230 super(JAVA, protocol, stream.socket.remoteAddress, username, uuid, VERSION_JAVA, javaSupportedProtocols[protocol][0]); 231 this.stream = cast(shared)stream; 232 this.serverboundKeepAliveId = getServerboundKeepAliveId(protocol); 233 this.clientboundKeepAliveId = getClientboundKeepAliveId(protocol); 234 this.encodeKeepAlive = getEncodeClientboundKeepAlive(protocol); 235 } 236 237 public shared void start(shared JavaServer server) { 238 Stream stream = cast()this.stream; 239 uint nextKeepAlive = 0; 240 ubyte timeout = 0; 241 StopWatch timer; 242 timer.start(); 243 while(true) { 244 ubyte[] recv = stream.receive(); 245 if(recv.length) { 246 if(varuint.decode(recv, 0) == this.serverboundKeepAliveId) { 247 //server.onLatencyUpdated(this, timer.peek().split!"msecs"().msecs); //TODO call on handler 248 } else { 249 // should never be compressed 250 server.handler.onClientPacket(this, recv); 251 } 252 } else if(stream.lastRecv == 0 || ++timeout == 3) { 253 // connection closed by the client or timed out 254 //TODO call some kind of event 255 stream.socket.close(); 256 break; 257 } 258 // check whether to send keep alive 259 if(timer.peek() > usecs(15_000_000)) { 260 timeout = 0; 261 timer.reset(); 262 stream.send(this.clientboundKeepAliveId ~ this.encodeKeepAlive(++nextKeepAlive)); 263 } 264 } 265 timer.stop(); 266 // thread should be stopped automatically 267 } 268 269 /** 270 * Sends a game packet to the client. 271 */ 272 public override shared void send(ubyte[] packet) { 273 //TODO do compression in another thread but maintain packet order 274 (cast()this.stream).send(packet); 275 } 276 277 /** 278 * Sends a raw packet, without performing eventual compression 279 * or 0-padding. 280 */ 281 public override shared void directSend(ubyte[] payload) { 282 (cast(ModifierStream)this.stream).stream.send(payload); // length is still appended 283 } 284 285 protected override shared void disconnectImpl(string message, bool translation, string[] params) { 286 JSONValue[string] reason; 287 reason[translation ? "translate" : "text"] = message; 288 if(params.length) { 289 JSONValue[] rp; 290 foreach(param ; params) rp ~= JSONValue(["text": param]); 291 reason["with"] = rp; 292 } 293 Stream stream = cast()this.stream; 294 stream.send(encodeClientboundDisconnect(this.protocol, JSONValue(reason).toString())); 295 stream.socket.close(); 296 // let the client close the session when the packet has been received 297 } 298 299 } 300 301 private ubyte getServerboundKeepAliveId(uint protocol) { 302 if(protocol >= 336) return 0x0B; 303 else if(protocol >= 318) return 0x0C; 304 else if(protocol >= 80) return 0x0B; 305 else if(protocol >= 67) return 0x0A; 306 else return 0x00; 307 } 308 309 private ubyte getClientboundKeepAliveId(uint protocol) { 310 if(protocol >= 332) return 0x1F; 311 else if(protocol >= 318) return 0x20; 312 else if(protocol >= 86) return 0x1F; 313 else if(protocol >= 80) return 0x20; 314 else if(protocol >= 67) return 0x1F; 315 else return 0x00; 316 } 317 318 private ubyte[] delegate(uint) getEncodeClientboundKeepAlive(uint protocol) { 319 if(protocol >= 339) { 320 // long 321 return delegate ubyte[] (uint id){ return nativeToBigEndian!ulong(id).dup; }; 322 } else if(protocol >= 32) { 323 // unsigned varint 324 return delegate ubyte[] (uint id){ return varuint.encode(id); }; 325 } else { 326 // int 327 return delegate ubyte[] (uint id){ return nativeToBigEndian!uint(id).dup; }; 328 } 329 } 330 331 private ubyte[] encodeClientboundDisconnect(uint protocol, string json) { 332 ubyte getId() { 333 if(protocol >= 332) return 0x1A; 334 else if(protocol >= 318) return 0x1B; 335 else if(protocol >= 80) return 0x1A; 336 else return 0x19; 337 } 338 // id (varuint), json's length (varuint), json (bytes) 339 return getId() ~ varuint.encode(json.length.to!uint) ~ cast(ubyte[])json; 340 } 341 342 enum string[][uint] supportedProtocols = [ 343 4: ["1.7.1-pre", "1.7.2", "1.7.3-pre", "1.7.4", "1.7.5"], 344 5: ["1.7.6", "1.7.7", "1.7.8", "1.7.9", "1.7.10", "14w02a"], 345 6: ["14w03a"], 346 7: ["14w04a"], 347 8: ["14w04b"], 348 9: ["14w05a"], 349 10: ["14w06a"], 350 11: ["14w07a"], 351 12: ["14w08a"], 352 14: ["14w11a"], 353 15: ["14w17a"], 354 16: ["14w18b"], 355 17: ["14w19a"], 356 18: ["14w20a"], 357 19: ["14w21a"], 358 20: ["14w21b"], 359 21: ["14w25a"], 360 22: ["14w25b"], 361 23: ["14w26a"], 362 24: ["14w26b"], 363 25: ["14w26c"], 364 26: ["14w27a", "14w27b"], 365 27: ["14w28a"], 366 28: ["14w28b"], 367 29: ["14w29a"], 368 30: ["14w30a"], 369 31: ["14w30c"], 370 32: ["14w31a"], 371 33: ["14w32a"], 372 34: ["14w32b"], 373 35: ["14w32c"], 374 36: ["14w32d"], 375 37: ["14w33a"], 376 38: ["14w33b"], 377 39: ["14w33c"], 378 40: ["14w34a"], 379 41: ["14w34b"], 380 42: ["14w34c"], 381 43: ["14w34d"], 382 44: ["1.8-pre1"], 383 45: ["1.8-pre2"], 384 46: ["1.8-pre3"], 385 47: ["1.8", "1.8.1", "1.8.2", "1.8.3", "1.8.4", "1.8.5", "1.8.6", "1.8.7", "1.8.8", "1.8.9"], 386 48: ["15w14a"], 387 49: ["15w31a"], 388 50: ["15w31b"], 389 51: ["15w31c"], 390 52: ["15w32a"], 391 53: ["15w32b"], 392 54: ["15w32c"], 393 55: ["15w33a"], 394 56: ["15w33b"], 395 57: ["15w33c"], 396 58: ["15w34a"], 397 59: ["15w34b"], 398 60: ["15w34c"], 399 61: ["15w34d"], 400 62: ["15w35a"], 401 63: ["15w35b"], 402 64: ["15w35c"], 403 65: ["15w35d"], 404 66: ["15w35e"], 405 67: ["15w36a"], 406 68: ["15w36b"], 407 69: ["15w36c"], 408 70: ["15w36d"], 409 71: ["15w37a"], 410 72: ["15w38a"], 411 73: ["15w38b"], 412 74: ["15w39c"], 413 75: ["15w40a"], 414 76: ["15w40b"], 415 77: ["15w41a"], 416 78: ["15w41b"], 417 79: ["15w42a"], 418 80: ["15w43a"], 419 81: ["15w43b"], 420 82: ["15w43c"], 421 83: ["15w44a"], 422 84: ["15w44b"], 423 85: ["15w45a"], 424 86: ["15w46a"], 425 87: ["15w47a"], 426 88: ["15w47b"], 427 89: ["15w47c"], 428 90: ["15w49a"], 429 91: ["15w49b"], 430 92: ["15w50a"], 431 93: ["15w51a"], 432 94: ["15w51b"], 433 95: ["16w02a"], 434 96: ["16w03a"], 435 97: ["16w04a"], 436 98: ["16w05a"], 437 99: ["16w05b"], 438 100: ["16w06a"], 439 101: ["16w07a"], 440 102: ["16w07b"], 441 103: ["1.9-pre1"], 442 104: ["1.9-pre2"], 443 105: ["1.9-pre3"], 444 106: ["1.9-pre4"], 445 107: ["1.9"], 446 108: ["1.9.1-pre2"], 447 109: ["1.9.2", "16w14a", "16w15a", "16w15b", "1.9.3-pre1", "1.9.3-pre3", "1.9.3", "1.9.4"], 448 110: ["1.9.3-pre2"], 449 201: ["16w20a"], 450 202: ["16w21a"], 451 203: ["16w21b"], 452 204: ["1.10-pre1"], 453 205: ["1.10-pre2"], 454 210: ["1.10", "1.10.1", "1.10.2"], 455 301: ["16w32a"], 456 302: ["16w32b"], 457 303: ["16w33a"], 458 304: ["16w35a"], 459 305: ["16w36a"], 460 306: ["16w38a"], 461 307: ["16w39a"], 462 308: ["16w39b"], 463 309: ["16w39c"], 464 310: ["16w40a"], 465 311: ["16w41a"], 466 312: ["16w42a"], 467 313: ["16w43a", "16w44a"], 468 314: ["1.11-pre1"], 469 315: ["1.11"], 470 316: ["16w50a", "1.11.1", "1.11.2"], 471 317: ["17w06a"], 472 318: ["17w13a"], 473 319: ["17w13b"], 474 320: ["17w14a"], 475 321: ["17w15a"], 476 322: ["17w16a"], 477 323: ["17w16b"], 478 324: ["17w17a"], 479 325: ["17w17b"], 480 326: ["17w18a"], 481 327: ["17w18b"], 482 328: ["1.12-pre1"], 483 329: ["1.12-pre2"], 484 330: ["1.12-pre3"], 485 331: ["1.12-pre4"], 486 332: ["1.12-pre5"], 487 333: ["1.12-pre6"], 488 334: ["1.12-pre7"], 489 335: ["1.12"], 490 336: ["17w31a"], 491 337: ["1.12.1-pre1"], 492 338: ["1.12.1"], 493 339: ["1.12.2-pre1", "1.12.2-pre2"], 494 340: ["1.12.2"], 495 341: ["17w43a"], 496 342: ["17w43b"], 497 343: ["17w45a"], 498 344: ["17w45b"], 499 345: ["17w46a"], 500 346: ["17w47a"], 501 347: ["17w47b"], 502 348: ["17w48a"], 503 349: ["17w49a"], 504 350: ["17w49b"], 505 351: ["17w50a"], 506 352: ["18w01a"], 507 353: ["18w02a"], 508 354: ["18w03a"], 509 355: ["18w03b"], 510 ];