Add Multiple transports system, begin work on WebSockets

This commit is contained in:
Li 2022-11-16 01:59:32 +13:00
parent e869a23463
commit e74f66a439
22 changed files with 3752 additions and 3301 deletions

View file

@ -0,0 +1,101 @@
using System;
using System.Net.Sockets;
namespace HISP.Server.Network
{
public class Hybrid : Transport
{
Transport actualTransport = null;
public override string Name
{
get
{
if(actualTransport == null)
return "TransportDeterminer";
else
return actualTransport.Name;
}
}
public override bool Disconnected
{
get
{
if (actualTransport == null)
return base.Disconnected;
else
return actualTransport.Disconnected;
}
}
public override string Ip
{
get
{
if (actualTransport == null)
return base.Ip;
else
return actualTransport.Ip;
}
}
public override void Disconnect()
{
if (actualTransport == null)
{
base.Disconnect();
}
else
{
actualTransport.Disconnect();
}
}
public override void ProcessReceivedPackets(int available, byte[] buffer)
{
for (int i = 0; i < available; i++)
base.currentPacket.Add(buffer[i]);
if (currentPacket.Count >= 3)
{
if (ConfigReader.EnableWebSocket && WebSocket.IsStartOfHandshake(currentPacket.ToArray()))
{
Logger.InfoPrint(this.Ip + " Switching to WebSocket");
actualTransport = new WebSocket();
actualTransport.passObjects(this.socket, this.onReceiveCallback, this.onDisconnectCallback);
actualTransport.ProcessReceivedPackets(available, buffer);
actualTransport.Accept(base.socket, base.onReceiveCallback, base.onDisconnectCallback);
}
else
{
Logger.InfoPrint(this.Ip + " Switching to XmlSocket");
actualTransport = new XmlSocket();
actualTransport.passObjects(this.socket, this.onReceiveCallback, this.onDisconnectCallback);
actualTransport.ProcessReceivedPackets(available, buffer);
actualTransport.Accept(base.socket, base.onReceiveCallback, base.onDisconnectCallback);
}
}
}
internal override void receivePackets(object sender, SocketAsyncEventArgs e)
{
if (!base.checkForError(e))
ProcessReceivedPackets(e.BytesTransferred, e.Buffer);
}
public override void Send(byte[] data)
{
if(actualTransport == null)
base.Send(data);
else
actualTransport.Send(data);
}
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.Net.Sockets;
namespace HISP.Server.Network
{
public interface ITransport
{
public string Name { get; }
public bool Disconnected { get; }
public string Ip { get; }
public void Accept(Socket socket, Action<byte[]> onReceive, Action onDisconnect);
public void Send(byte[] data);
public void Disconnect();
}
}

View file

@ -0,0 +1,128 @@
using HISP.Util;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
namespace HISP.Server.Network
{
public abstract class Transport : ITransport
{
internal Socket socket;
internal string remoteIp;
internal Action<byte[]> onReceiveCallback;
internal Action onDisconnectCallback;
internal List<byte> currentPacket = new List<byte>();
internal byte[] workBuffer = new byte[0xFFFF];
internal bool isDisconnecting = false;
public abstract void ProcessReceivedPackets(int available, byte[] buffer);
public abstract string Name { get; }
internal virtual bool checkForError(SocketAsyncEventArgs e)
{
if (isDisconnecting || socket == null || e.BytesTransferred <= 0 || !socket.Connected || e.SocketError != SocketError.Success)
{
Disconnect();
return true;
}
else
{
return false;
}
}
internal virtual void receivePackets(object sender, SocketAsyncEventArgs e)
{
do
{
if (!checkForError(e))
ProcessReceivedPackets(e.BytesTransferred, e.Buffer);
else
break;
} while (!socket.ReceiveAsync(e));
}
public virtual string Ip
{
get
{
return this.remoteIp;
}
}
public virtual bool Disconnected
{
get
{
return this.isDisconnecting;
}
}
internal virtual void passObjects(Socket socket, Action<byte[]> onReceive, Action onDisconnect)
{
socket.SendTimeout = 10 * 1000; // 10sec
socket.ReceiveTimeout = 10 * 1000; // 10sec
this.socket = socket;
this.onReceiveCallback = onReceive;
this.onDisconnectCallback = onDisconnect;
this.remoteIp = Helper.GetIp(socket.RemoteEndPoint);
}
public virtual void Accept(Socket socket, Action<byte[]> onReceive, Action onDisconnect)
{
passObjects(socket, onReceive, onDisconnect);
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.Completed += receivePackets;
e.SetBuffer(workBuffer, 0, workBuffer.Length);
if (!socket.ReceiveAsync(e))
receivePackets(null, e);
}
public virtual void Disconnect()
{
if (this.isDisconnecting)
return;
this.isDisconnecting = true;
// Close Socket
if (socket != null)
{
try
{
socket.Disconnect(false);
socket.Dispose();
socket = null;
}
catch (SocketException e) { }
catch (ObjectDisposedException e) { };
}
onDisconnectCallback();
}
public virtual void Send(byte[] data)
{
if (Disconnected) return;
if (data == null) return;
try
{
socket.Send(data);
}
catch (ObjectDisposedException)
{
if (!Disconnected)
Disconnect();
}
}
}
}

View file

@ -0,0 +1,196 @@
using HISP.Security;
using HISP.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace HISP.Server.Network
{
public class WebSocket : Transport
{
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 WEBASSEMBLY_LENGTH_INT16 = 0x7E;
private const byte WEBASSEMBLY_LENGTH_INT64 = 0x7F;
private List<byte> currentMessage = new List<byte>();
private string secWebsocketKey = null;
private bool handshakeDone = false;
private Dictionary<string, string> parseHttpHeaders(string httpResponse)
{
Dictionary<string, string> httpHeaders = new Dictionary<string, string>();
string[] parts = httpResponse.Replace("\r", "").Split("\n");
foreach (string part in parts)
{
if (part.StartsWith("GET")) continue;
if (part.Contains(":"))
{
string[] keyValuePairs = part.Split(":");
if (keyValuePairs.Length >= 2)
httpHeaders.Add(keyValuePairs[0].Trim().ToLower(), keyValuePairs[1].Trim());
}
else
{
continue;
}
}
return httpHeaders;
}
private string deriveWebsocketSecKey(string webSocketKey)
{
byte[] derivedKey = Authentication.Sha1Digest(Encoding.UTF8.GetBytes(webSocketKey.Trim() + WEBSOCKET_SEED.Trim()));
return Convert.ToBase64String(derivedKey);
}
private byte[] createHandshakeResponse(string secWebsocketKey)
{
return Encoding.UTF8.GetBytes(String.Join("\r\n", new string[] {
"HTTP/1.1 101 Switching Protocols",
"Connection: Upgrade",
"Upgrade: websocket",
"Sec-WebSocket-Accept: " + secWebsocketKey,
"",
""
}));
}
private byte[] parseHandshake(string handshakeResponse)
{
Dictionary<string, string> headers = parseHttpHeaders(handshakeResponse);
string webSocketKey = null;
headers.TryGetValue("sec-websocket-key", out webSocketKey);
if (webSocketKey != null)
{
string secWebsocketKey = deriveWebsocketSecKey(webSocketKey);
return createHandshakeResponse(secWebsocketKey);
}
return createHandshakeResponse("");
}
public static bool IsStartOfHandshake(byte[] data)
{
return Helper.ByteArrayStartsWith(data, Encoding.UTF8.GetBytes("GET"));
}
public static bool IsEndOfHandshake(byte[] data)
{
return Helper.ByteArrayEndsWith(data, Encoding.UTF8.GetBytes("\r\n\r\n"));
}
public override void ProcessReceivedPackets(int available, byte[] buffer)
{
for (int i = 0; i < available; i++)
currentPacket.Add(buffer[i]);
byte[] webAsmMsg = currentPacket.ToArray();
if (!handshakeDone)
{
if (IsStartOfHandshake(webAsmMsg) && IsEndOfHandshake(webAsmMsg))
{
string httpHandshake = Encoding.UTF8.GetString(webAsmMsg);
byte[] handshakeResponse = parseHandshake(httpHandshake);
base.Send(handshakeResponse);
currentPacket.Clear();
handshakeDone = true;
}
}
if (currentPacket.Count >= 2)
{
bool finished = (currentPacket[0] & 0b10000000) != 0;
int opcode = (currentPacket[0] & 0b00001111);
bool mask = (currentPacket[1] & 0b10000000) != 0;
UInt64 messageLength = Convert.ToUInt64(currentPacket[1] & 0b01111111);
int offset = 2;
if (messageLength == WEBASSEMBLY_LENGTH_INT16)
{
if(currentPacket.Count >= offset + 2)
{
byte[] uint16Bytes = new byte[2];
Array.ConstrainedCopy(webAsmMsg, offset, uint16Bytes, 0, uint16Bytes.Length);
uint16Bytes = uint16Bytes.Reverse().ToArray();
messageLength = BitConverter.ToUInt16(uint16Bytes);
offset += uint16Bytes.Length;
}
}
else if (messageLength == WEBASSEMBLY_LENGTH_INT64)
{
if (currentPacket.Count >= offset + 8)
{
byte[] uint64Bytes = new byte[8];
Array.ConstrainedCopy(webAsmMsg, offset, uint64Bytes, 0, uint64Bytes.Length);
uint64Bytes = uint64Bytes.Reverse().ToArray();
messageLength = BitConverter.ToUInt64(uint64Bytes);
offset += uint64Bytes.Length;
}
}
if (mask)
{
switch (opcode)
{
case WEBASSEMBLY_TEXT:
if (currentPacket.LongCount() >= (offset + 4))
{
byte[] unmaskKey = new byte[4];
Array.ConstrainedCopy(buffer, offset, unmaskKey, 0, unmaskKey.Length);
offset += unmaskKey.Length;
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());
currentMessage.Clear();
currentPacket.Clear();
}
}
}
}
public override string Name
{
get
{
return "WebSocket";
}
}
public override void Send(byte[] data)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,38 @@
using HISP.Security;
using HISP.Util;
using System.Text;
namespace HISP.Server.Network
{
public class XmlSocket : Transport
{
public override void ProcessReceivedPackets(int available, byte[] buffer)
{
// In XmlSocket Packets are terminates by 0x00 so we have to read until we receive that terminator
for (int i = 0; i < available; i++)
{
currentPacket.Add(buffer[i]);
if (buffer[i] == PacketBuilder.PACKET_TERMINATOR) // Read until \0...
{
onReceiveCallback(currentPacket.ToArray());
currentPacket.Clear();
}
}
// Handle XMLSocket Policy File
if (Helper.ByteArrayStartsWith(buffer, Encoding.UTF8.GetBytes("<policy-file-request/>")))
{
this.Send(CrossDomainPolicy.GetPolicyFile());
}
}
public override string Name
{
get
{
return "XmlSocket";
}
}
}
}