From b9e3949b141828aacf59e92975f0a1a2b16aa24c Mon Sep 17 00:00:00 2001 From: Li Date: Thu, 17 Nov 2022 02:52:30 +1300 Subject: [PATCH] Improve websocket code. --- .../HISPd/Properties/AssemblyInfo.cs | 4 +- .../HISPd/Resources/DEBIAN/control | 2 +- .../LibHISP/Properties/AssemblyInfo.cs | 4 +- HorseIsleServer/LibHISP/Server/GameClient.cs | 31 +-- HorseIsleServer/LibHISP/Server/GameServer.cs | 2 +- .../LibHISP/Server/Network/Transport.cs | 4 +- .../LibHISP/Server/Network/WebSocket.cs | 214 +++++++++++++----- .../MPN00BS/Properties/AssemblyInfo.cs | 4 +- 8 files changed, 186 insertions(+), 79 deletions(-) diff --git a/HorseIsleServer/HISPd/Properties/AssemblyInfo.cs b/HorseIsleServer/HISPd/Properties/AssemblyInfo.cs index 6c9b07c..f70575a 100755 --- a/HorseIsleServer/HISPd/Properties/AssemblyInfo.cs +++ b/HorseIsleServer/HISPd/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.8.38.0")] -[assembly: AssemblyFileVersion("1.8.38.0")] +[assembly: AssemblyVersion("1.8.39.0")] +[assembly: AssemblyFileVersion("1.8.39.0")] diff --git a/HorseIsleServer/HISPd/Resources/DEBIAN/control b/HorseIsleServer/HISPd/Resources/DEBIAN/control index 8291173..03bb4ab 100755 --- a/HorseIsleServer/HISPd/Resources/DEBIAN/control +++ b/HorseIsleServer/HISPd/Resources/DEBIAN/control @@ -1,5 +1,5 @@ Package: hisp -Version: 1.8.38 +Version: 1.8.39 Depends: coreutils,systemd,mariadb-server,libsqlite3-dev,zlib1g-dev,libicu-dev,libkrb5-dev Maintainer: Li Homepage: https://islehorse.com diff --git a/HorseIsleServer/LibHISP/Properties/AssemblyInfo.cs b/HorseIsleServer/LibHISP/Properties/AssemblyInfo.cs index 3df89e9..2982524 100755 --- a/HorseIsleServer/LibHISP/Properties/AssemblyInfo.cs +++ b/HorseIsleServer/LibHISP/Properties/AssemblyInfo.cs @@ -30,8 +30,8 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.8.38.0")] -[assembly: AssemblyFileVersion("1.8.38.0")] +[assembly: AssemblyVersion("1.8.39.0")] +[assembly: AssemblyFileVersion("1.8.39.0")] diff --git a/HorseIsleServer/LibHISP/Server/GameClient.cs b/HorseIsleServer/LibHISP/Server/GameClient.cs index d60475d..d5ac067 100755 --- a/HorseIsleServer/LibHISP/Server/GameClient.cs +++ b/HorseIsleServer/LibHISP/Server/GameClient.cs @@ -109,29 +109,21 @@ namespace HISP.Server } public static void CreateClient(object sender, SocketAsyncEventArgs e) { -#if !DEBUG - try + do { -#endif - do - { - Socket clientSocket = e.AcceptSocket; + Socket clientSocket = e.AcceptSocket; - if (GameServer.ServerSocket == null) - return; - if (clientSocket == null) - continue; - if (clientSocket.RemoteEndPoint == null) - continue; + if (GameServer.ServerSocket == null) + return; + if (clientSocket == null) + continue; + if (clientSocket.RemoteEndPoint == null) + continue; - connectedClients.Add(new GameClient(clientSocket)); - e.AcceptSocket = null; + connectedClients.Add(new GameClient(clientSocket)); + e.AcceptSocket = null; - } while (!GameServer.ServerSocket.AcceptAsync(e)); -#if !DEBUG - } - catch (ObjectDisposedException ex) { Logger.ErrorPrint("Server shutdown due to " + ex.Message); } // server shutdown -#endif + } while (!GameServer.ServerSocket.AcceptAsync(e)); } private void timeoutTimerTick(object state) { @@ -394,6 +386,7 @@ namespace HISP.Server if (packet.Length < 1) { Logger.ErrorPrint("Received an invalid packet (size: "+packet.Length+")"); + return; } byte identifier = packet[0]; diff --git a/HorseIsleServer/LibHISP/Server/GameServer.cs b/HorseIsleServer/LibHISP/Server/GameServer.cs index f9ebeb7..4c329a4 100755 --- a/HorseIsleServer/LibHISP/Server/GameServer.cs +++ b/HorseIsleServer/LibHISP/Server/GameServer.cs @@ -8264,7 +8264,7 @@ namespace HISP.Server IPAddress hostIP = IPAddress.Parse(ConfigReader.BindIP); IPEndPoint ep = new IPEndPoint(hostIP, ConfigReader.Port); ServerSocket.Bind(ep); - ServerSocket.Listen(0x7fffffff); + ServerSocket.Listen(0xFFFF); gameTimer = new Timer(new TimerCallback(onGameTick), null, gameTickSpeed, gameTickSpeed); minuteTimer = new Timer(new TimerCallback(onMinuteTick), null, oneMinute, oneMinute); Logger.InfoPrint("Binding to ip: " + ConfigReader.BindIP + " On port: " + ConfigReader.Port.ToString()); diff --git a/HorseIsleServer/LibHISP/Server/Network/Transport.cs b/HorseIsleServer/LibHISP/Server/Network/Transport.cs index ea71363..d2e064d 100644 --- a/HorseIsleServer/LibHISP/Server/Network/Transport.cs +++ b/HorseIsleServer/LibHISP/Server/Network/Transport.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Net.Sockets; - +using System.Threading; namespace HISP.Server.Network { @@ -43,6 +43,8 @@ namespace HISP.Server.Network else break; + Thread.Sleep(1000 * 3); + } while (!socket.ReceiveAsync(e)); } diff --git a/HorseIsleServer/LibHISP/Server/Network/WebSocket.cs b/HorseIsleServer/LibHISP/Server/Network/WebSocket.cs index 09c0808..a2fc1bc 100644 --- a/HorseIsleServer/LibHISP/Server/Network/WebSocket.cs +++ b/HorseIsleServer/LibHISP/Server/Network/WebSocket.cs @@ -1,4 +1,6 @@ -using HISP.Security; +#define WEBSOCKET_DEBUG + +using HISP.Security; using HISP.Util; using System; using System.Collections.Generic; @@ -11,16 +13,25 @@ namespace HISP.Server.Network { private const string WEBSOCKET_SEED = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - private const byte WEBASSEMBLY_CONTINUE = 0x0; - private const byte WEBASSEMBLY_TEXT = 0x1; + private const byte WEBSOCKET_CONTINUE = 0x0; + private const byte WEBSOCKET_TEXT = 0x1; + private const byte WEBSOCKET_BINARY = 0x2; - private const byte WEBASSEMBLY_LENGTH_INT16 = 0x7E; - private const byte WEBASSEMBLY_LENGTH_INT64 = 0x7F; + private const byte WEBSOCKET_LENGTH_INT16 = 0x7E; + private const byte WEBSOCKET_LENGTH_INT64 = 0x7F; private List currentMessage = new List(); - private string secWebsocketKey = null; + private int lastOpcode; + private Int64 expectedLength = -1; private bool handshakeDone = false; + private void webSocketLog(string msg) + { +#if WEBSOCKET_DEBUG + foreach(string str in msg.Replace("\r", "").Split("\n")) + Logger.InfoPrint("[WEBSOCKET] " + str); +#endif + } private Dictionary parseHttpHeaders(string httpResponse) { @@ -52,18 +63,21 @@ namespace HISP.Server.Network } private byte[] createHandshakeResponse(string secWebsocketKey) { - return Encoding.UTF8.GetBytes(String.Join("\r\n", new string[] { + string msg = String.Join("\r\n", new string[] { "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", "Upgrade: websocket", "Sec-WebSocket-Accept: " + secWebsocketKey, "", "" - })); + }); + webSocketLog(msg); + return Encoding.UTF8.GetBytes(msg); } private byte[] parseHandshake(string handshakeResponse) { + webSocketLog(handshakeResponse); Dictionary headers = parseHttpHeaders(handshakeResponse); string webSocketKey = null; @@ -90,15 +104,36 @@ namespace HISP.Server.Network public override void ProcessReceivedPackets(int available, byte[] buffer) { - for (int i = 0; i < available; i++) - currentPacket.Add(buffer[i]); - byte[] webAsmMsg = currentPacket.ToArray(); + Int64 received = 0; + // add to current packet + // if current packet is less than size of an expected incoming message + // then keep receiving until full message received. + + Int64 curPacketLength = currentPacket.LongCount(); + for (received = 0; received < available; received++) + { + currentPacket.Add(buffer[received]); + curPacketLength++; + + // i use a <0 value to say there is no expected length + // check if entire packet received + if (expectedLength > 0 && (curPacketLength >= expectedLength)) + break; + } + + if (expectedLength > 0 && (currentPacket.LongCount() < expectedLength)) + return; + else + expectedLength = -1; + + byte[] webSocketMsg = currentPacket.ToArray(); if (!handshakeDone) { - if (IsStartOfHandshake(webAsmMsg) && IsEndOfHandshake(webAsmMsg)) + + if (IsStartOfHandshake(webSocketMsg) && IsEndOfHandshake(webSocketMsg)) { - string httpHandshake = Encoding.UTF8.GetString(webAsmMsg); + string httpHandshake = Encoding.UTF8.GetString(webSocketMsg); byte[] handshakeResponse = parseHandshake(httpHandshake); base.Send(handshakeResponse); @@ -106,76 +141,153 @@ namespace HISP.Server.Network handshakeDone = true; } } - if (currentPacket.Count >= 2) + else if(currentPacket.Count > 2) // else, begin parsing websocket message { - bool finished = (currentPacket[0] & 0b10000000) != 0; - int opcode = (currentPacket[0] & 0b00001111); - bool mask = (currentPacket[1] & 0b10000000) != 0; - UInt64 messageLength = Convert.ToUInt64(currentPacket[1] & 0b01111111); + byte[] unmaskKey = new byte[4]; + + bool finished = (webSocketMsg[0] & 0b10000000) != 0; + + bool rsv1 = (webSocketMsg[0] & 0b01000000) != 0; + bool rsv2 = (webSocketMsg[0] & 0b00100000) != 0; + bool rsv3 = (webSocketMsg[0] & 0b00010000) != 0; + + int opcode = (webSocketMsg[0] & 0b00001111); + + bool mask = (webSocketMsg[1] & 0b10000000) != 0; + Int64 messageLength = Convert.ToInt64(webSocketMsg[1] & 0b01111111); int offset = 2; - if (messageLength == WEBASSEMBLY_LENGTH_INT16) + + if (messageLength == WEBSOCKET_LENGTH_INT16) { - if(currentPacket.Count >= offset + 2) + if (webSocketMsg.LongLength >= offset + 2) { byte[] uint16Bytes = new byte[2]; - Array.ConstrainedCopy(webAsmMsg, offset, uint16Bytes, 0, uint16Bytes.Length); + Array.ConstrainedCopy(webSocketMsg, offset, uint16Bytes, 0, uint16Bytes.Length); uint16Bytes = uint16Bytes.Reverse().ToArray(); messageLength = BitConverter.ToUInt16(uint16Bytes); offset += uint16Bytes.Length; } } - else if (messageLength == WEBASSEMBLY_LENGTH_INT64) + else if (messageLength == WEBSOCKET_LENGTH_INT64) { - if (currentPacket.Count >= offset + 8) + if (webSocketMsg.LongLength >= offset + 8) { - byte[] uint64Bytes = new byte[8]; - Array.ConstrainedCopy(webAsmMsg, offset, uint64Bytes, 0, uint64Bytes.Length); - uint64Bytes = uint64Bytes.Reverse().ToArray(); - messageLength = BitConverter.ToUInt64(uint64Bytes); + byte[] int64Bytes = new byte[8]; + Array.ConstrainedCopy(webSocketMsg, offset, int64Bytes, 0, int64Bytes.Length); + int64Bytes = int64Bytes.Reverse().ToArray(); + messageLength = BitConverter.ToInt64(int64Bytes); - offset += uint64Bytes.Length; + offset += int64Bytes.Length; } } - if (mask) { - switch (opcode) + Array.ConstrainedCopy(webSocketMsg, offset, unmaskKey, 0, unmaskKey.Length); + offset += unmaskKey.Length; + } + + // Handle tcp fragmentation + + Int64 actualLength = (webSocketMsg.LongLength - offset); + + // check if full message received, if not then set expected length + // and return, thus entering the loop at the beginning + if (actualLength < messageLength) + { + expectedLength = messageLength + offset; // set expected length and return + webSocketLog("Partial websocket frame received, expected size: " + messageLength + " got size: " + actualLength); + return; + } + else + { + expectedLength = -1; + currentPacket.Clear(); + } + + // dont care about extensions + if (rsv1 || rsv2 || rsv3) return; + + webSocketLog("Finished: " + finished + "\nRsv1: " + rsv1 + "\nRsv2: " + rsv2 + "\nRsv3: " + rsv3 + "\nOpcode: " + opcode + "\nMask: " + mask + "\nMesssageLength: " + messageLength); + + // websocket packet fully received, begin processing + + // allow for continuing + if (opcode == WEBSOCKET_CONTINUE) + lastOpcode = opcode; + else + opcode = lastOpcode; + + switch (opcode) + { + case WEBSOCKET_BINARY: // writing this almost killed me, because im non-binary :( + case WEBSOCKET_TEXT: + for (Int64 i = 0; i < messageLength; i++) + currentMessage.Add(mask ? Convert.ToByte(webSocketMsg[offset + i] ^ unmaskKey[i % unmaskKey.Length]) : Convert.ToByte(webSocketMsg[offset + i])); + break; + } + + if (finished) + { + if(currentMessage.LongCount() > 0) { - case WEBASSEMBLY_TEXT: + byte[] message = currentMessage.ToArray(); - if (currentPacket.LongCount() >= (offset + 4)) - { - byte[] unmaskKey = new byte[4]; - Array.ConstrainedCopy(buffer, offset, unmaskKey, 0, unmaskKey.Length); - offset += unmaskKey.Length; + if(opcode == WEBSOCKET_TEXT) + webSocketLog(Encoding.UTF8.GetString(message)); - for (int i = 0; i < (currentPacket.Count - offset); i++) - { - currentMessage.Add(Convert.ToByte(currentPacket[offset+ i] ^ unmaskKey[i % unmaskKey.Length])); - } - - currentPacket.Clear(); - } - break; - } - - if (finished) - { - onReceiveCallback(currentMessage.ToArray()); + onReceiveCallback(message); currentMessage.Clear(); - currentPacket.Clear(); } } + + // is there another frame after this one? + if(actualLength > messageLength) + { + Int64 left = (actualLength - messageLength); + Int64 totalSent = left; + + Int64 loc = messageLength + offset; + + while(totalSent > 0) + { + int total = buffer.Length; + if (totalSent < total) + total = Convert.ToInt32(totalSent); + + for (int i = 0; i < total; i++) + buffer[i] = webSocketMsg[loc + i]; + + ProcessReceivedPackets(total, buffer); + + totalSent -= total; + loc += total; + } + + } } - + + // parse remaining data after end. + + if (received < available) + { + int left = Convert.ToInt32(available - received); + byte[] leftData = new byte[left]; + Array.ConstrainedCopy(buffer, available, leftData, 0, left); + Array.Copy(leftData, buffer, leftData.Length); + + ProcessReceivedPackets(available, buffer); + } + } + + public override string Name { get diff --git a/HorseIsleServer/MPN00BS/Properties/AssemblyInfo.cs b/HorseIsleServer/MPN00BS/Properties/AssemblyInfo.cs index 0f160ea..5419dd1 100755 --- a/HorseIsleServer/MPN00BS/Properties/AssemblyInfo.cs +++ b/HorseIsleServer/MPN00BS/Properties/AssemblyInfo.cs @@ -30,8 +30,8 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.8.38.0")] -[assembly: AssemblyFileVersion("1.8.38.0")] +[assembly: AssemblyVersion("1.8.39.0")] +[assembly: AssemblyFileVersion("1.8.39.0")]