From 132e272580f7eaa5c80d6feb4c353ae942ac8b1f Mon Sep 17 00:00:00 2001 From: Malloc of Kuzkycyziklistan Date: Fri, 12 May 2017 16:05:18 -0500 Subject: [PATCH] SOCKS FINALLY DONE HOLY SHIT --- server/Entrypoint.cs | 3 + server/Libraries/Kneesocks/Connection.cs | 112 ++++++++++++++++----- server/Libraries/Kneesocks/Pool.cs | 73 +++++++++++--- server/Libraries/Kneesocks/Server.cs | 11 +- server/Libraries/Kneesocks/Stack.cs | 23 ++--- server/Libraries/Square/ArrayExtensions.cs | 2 +- server/Socks/ActiveConnection.cs | 3 +- server/Socks/PendingConnection.cs | 21 +++- server/Socks/PoolManager.cs | 5 + 9 files changed, 194 insertions(+), 59 deletions(-) diff --git a/server/Entrypoint.cs b/server/Entrypoint.cs index eeeeaf1..0f44180 100644 --- a/server/Entrypoint.cs +++ b/server/Entrypoint.cs @@ -18,6 +18,9 @@ namespace CircleScape { // logic processing loop } + + server.Stop(); + PoolManager.Dispose(); } } } diff --git a/server/Libraries/Kneesocks/Connection.cs b/server/Libraries/Kneesocks/Connection.cs index bf43b2e..92c3e3c 100644 --- a/server/Libraries/Kneesocks/Connection.cs +++ b/server/Libraries/Kneesocks/Connection.cs @@ -9,7 +9,9 @@ using System.IO; namespace Kneesocks { public class Connection { - public UInt64? _Id = null; + private bool Initialized = false; + + private UInt64? _Id = null; public UInt64 Id { get { if(_Id == null) @@ -22,9 +24,14 @@ namespace Kneesocks { _Id = value; } } + internal bool IsIdNull { + get { + return _Id == null; + } + } - private TcpClient Socket; - private NetworkStream Stream; + private TcpClient Socket = null; + private NetworkStream Stream = null; ReadBuffer Buffer; private byte[] FirstTwoBytes = null; @@ -32,27 +39,54 @@ namespace Kneesocks { private byte[] FrameHeader = null; private List ReceiveFrameBuffer = new List(); private List SendFrameBuffer = new List(); + private const int MaximumSendFrameSize = 0xFFFFF; + private Random Random = new Random(); + + protected const int PingInterval = 30; + protected const int TimeoutInterval = 120; + private byte[] PingData = Encoding.ASCII.GetBytes("woomy!"); private DateTime LastPing; + private bool AwaitingPingResponse = false; + private TimeSpan TimeSinceLastPing { + get { + return DateTime.UtcNow - LastPing; + } + } + internal bool OutsidePool = false; public bool Disconnected { get; private set; } = false; public string DisconnectReason { get; private set; } = null; public bool Handshaked { get; private set; } = false; public Handshake ClientHandshake { get; private set; } = null; - public Connection(TcpClient sock) { + public void Initialize(TcpClient sock) { + if(Initialized) + return; + Socket = sock; Socket.ReceiveTimeout = 1; Stream = sock.GetStream(); Buffer = new ReadBuffer(Stream); + + Initialized = true; } - public Connection(UInt64 id, TcpClient sock) : this(sock) { + public void Initialize(UInt64 id, TcpClient sock) { + if(Initialized) + return; + + Initialize(sock); Id = id; + + Initialized = true; } - public Connection(Connection conn, bool preserveId = true) { + public void Initialize(Connection conn, bool preserveId = false) { + if(Initialized) + return; + if(preserveId) _Id = conn._Id; @@ -65,22 +99,25 @@ namespace Kneesocks { FrameHeader = conn.FrameHeader; ReceiveFrameBuffer = conn.ReceiveFrameBuffer; + LastPing = conn.LastPing; + Disconnected = conn.Disconnected; DisconnectReason = conn.DisconnectReason; Handshaked = conn.Handshaked; ClientHandshake = conn.ClientHandshake; + + Initialized = true; } - private const int MaximumFrameSize = 0xFFFFF; private void _Send(byte[] message, bool isFinal = true, bool singleFrame = false, bool first = false) { - int frameCount = singleFrame ? 0 : (message.Length / MaximumFrameSize); + int frameCount = singleFrame ? 0 : (message.Length / MaximumSendFrameSize); for(var i = 0; i <= frameCount; ++i) { SendFrameBuffer.Add(new Frame { IsFinal = (i == frameCount && isFinal) ? true : false, IsMasked = false, Opcode = (i == 0 || (singleFrame && first)) ? Frame.kOpcode.BinaryFrame : Frame.kOpcode.Continuation, - Content = message.Subset(i * (MaximumFrameSize + 1), MaximumFrameSize) + Content = message.Subset(i * (MaximumSendFrameSize + 1), MaximumSendFrameSize) }); } } @@ -96,15 +133,15 @@ namespace Kneesocks { return; bool firstRead = true; - byte[] byteBuffer = new byte[MaximumFrameSize]; + byte[] byteBuffer = new byte[MaximumSendFrameSize]; while(true) { - var bytesRead = stream.Read(byteBuffer, 0, MaximumFrameSize); + var bytesRead = stream.Read(byteBuffer, 0, MaximumSendFrameSize); if(stream.Position == stream.Length) { - _Send(bytesRead == MaximumFrameSize ? byteBuffer : byteBuffer.Take(bytesRead).ToArray(), true, true, firstRead); + _Send(bytesRead == MaximumSendFrameSize ? byteBuffer : byteBuffer.Take(bytesRead).ToArray(), true, true, firstRead); return; } else { - _Send(bytesRead == MaximumFrameSize ? byteBuffer : byteBuffer.Take(bytesRead).ToArray(), false, true, firstRead); + _Send(bytesRead == MaximumSendFrameSize ? byteBuffer : byteBuffer.Take(bytesRead).ToArray(), false, true, firstRead); } firstRead = false; @@ -133,9 +170,25 @@ namespace Kneesocks { buffer = buffer == null ? Buffer.AttemptRead(terminator) : buffer; } - - public void Parse() { + + internal void Parse() { if(Handshaked) { + if(!Buffer.IsReading) { + if(TimeSinceLastPing.Seconds > TimeoutInterval) { + Disconnect(Frame.kClosingReason.Normal, "Ping response timed out."); + } else if(TimeSinceLastPing.Seconds > PingInterval && !AwaitingPingResponse) { + var frameBytes = new Frame { + IsFinal = true, + IsMasked = false, + Opcode = Frame.kOpcode.Ping, + Content = PingData + }.GetBytes(); + + Stream.Write(frameBytes, 0, frameBytes.Length); + AwaitingPingResponse = true; + } + } + lock(SendFrameBuffer) { if(SendFrameBuffer.Count > 0) { foreach(var frame in SendFrameBuffer) { @@ -172,6 +225,7 @@ namespace Kneesocks { Stream.Write(response, 0, response.Length); ClientHandshake = request; Handshaked = true; + LastPing = DateTime.UtcNow; } catch(Exception e) { Disconnect(Frame.kClosingReason.ProtocolError, e.Message); @@ -238,7 +292,8 @@ namespace Kneesocks { if(tempFrame.IsFinal) { switch(tempFrame.Opcode) { case Frame.kOpcode.Ping: - LastPing = DateTime.Now; + LastPing = DateTime.UtcNow; + AwaitingPingResponse = false; tempFrame.Opcode = Frame.kOpcode.Pong; var pingBuffer = tempFrame.GetBytes(); @@ -246,24 +301,33 @@ namespace Kneesocks { break; case Frame.kOpcode.Pong: - LastPing = DateTime.Now; + LastPing = DateTime.UtcNow; + AwaitingPingResponse = false; break; case Frame.kOpcode.Close: Disconnect(Frame.kClosingReason.Normal, "Connection closed."); break; + + case Frame.kOpcode.BinaryFrame: + case Frame.kOpcode.TextFrame: + case Frame.kOpcode.Continuation: + byte[] byteBuffer = new byte[0]; + foreach(var frame in ReceiveFrameBuffer) + byteBuffer = byteBuffer.Concat(frame.Content).ToArray(); + + ReceiveFrameBuffer = new List(); + OnReceive(byteBuffer); + break; } - - byte[] byteBuffer = new byte[0]; - foreach(var frame in ReceiveFrameBuffer) - byteBuffer = byteBuffer.Concat(frame.Content).ToArray(); - - ReceiveFrameBuffer = new List(); - OnReceive(byteBuffer); } } } + public void RemoveFromPool() { + OutsidePool = true; + } + public void Disconnect(string reason = null) { Disconnect(Frame.kClosingReason.Normal, reason); } diff --git a/server/Libraries/Kneesocks/Pool.cs b/server/Libraries/Kneesocks/Pool.cs index a400715..9f0d9ca 100644 --- a/server/Libraries/Kneesocks/Pool.cs +++ b/server/Libraries/Kneesocks/Pool.cs @@ -26,6 +26,7 @@ namespace Kneesocks { // 0 means never redistribute public int Tolerance { get; set; } = 0; + private bool Disposed = false; private int _fullThreadCount; private volatile bool updateFullThreadCount = true; @@ -41,42 +42,59 @@ namespace Kneesocks { CreateThread(runWithNoClients: true); } - private void IndexConnection(UInt64 id, T connection) { - lock(Connections) { - if(id == 0) - id = (ulong)Interlocked.Increment(ref InternalCounter); - - connection.Id = id; - Connections.Add(id, connection); + public T this[UInt64 id] { + get { + if(HasConnection(id)) + return Connections[id]; + else return null; } } - public void InvalidateConnection(UInt64 id) { + public bool HasConnection(UInt64 id) { + return Connections.ContainsKey(id); + } + + private void IndexConnection(T connection) { + lock(Connections) { + if(connection.IsIdNull) + connection.Id = (ulong)Interlocked.Increment(ref InternalCounter); + + Connections.Add(connection.Id, connection); + } + } + + internal void InvalidateConnection(UInt64 id) { lock(Connections) { Connections.Remove(id); } } public void Broadcast(byte[] message) { + if(Disposed) + return; + lock(Connections) { foreach(var conn in Connections) conn.Value.Send(message); } } - public bool AddConnection(T connection, UInt64 id = 0) { + public bool AddConnection(T connection) { + if(Disposed) + return false; + lock(Threads) { foreach(var thread in Threads) { if(thread.Stack.Count < FullThreadSize) { thread.Stack.AddClient(connection); - IndexConnection(id, connection); + IndexConnection(connection); return true; } } if(MaxCount == 0 || Threads.Count < MaxCount) { CreateThread(connection); - IndexConnection(id, connection); + IndexConnection(connection); return true; } } @@ -84,7 +102,7 @@ namespace Kneesocks { return false; } - public void InvalidateThread(Stack stackRef) { + internal void InvalidateThread(Stack stackRef) { lock(Threads) { var ctx = Threads.FirstOrDefault(x => Object.ReferenceEquals(x.Stack, stackRef)); if(ctx != null) { @@ -120,6 +138,37 @@ namespace Kneesocks { } } + public void Dispose() { + if(Disposed) + return; + + Disposed = true; + + lock(Threads) { + foreach(var thread in Threads) + thread.Stack.StopThread(); + } + + while(true) { + Thread.Sleep(100); + + lock(Threads) { + if(Threads.Count == 0) + break; + } + } + + lock(Connections) { + foreach(var conn in Connections) { + conn.Value.Disconnect(Frame.kClosingReason.Normal, "Server shutting down."); + } + } + } + + ~Pool() { + Dispose(); + } + class ThreadContext { public Thread Thread { get; set; } public Stack Stack { get; set; } diff --git a/server/Libraries/Kneesocks/Server.cs b/server/Libraries/Kneesocks/Server.cs index b7ebc10..0c929c6 100644 --- a/server/Libraries/Kneesocks/Server.cs +++ b/server/Libraries/Kneesocks/Server.cs @@ -7,11 +7,11 @@ using System.Net.Sockets; using System.Net; namespace Kneesocks { - public class Server where T : Connection { + public class Server where T : Connection, new() { private TcpListener Socket; private Thread Listener = null; private Pool ConnectionPool = null; - private bool Started = false; + public bool Started { get; private set; } = false; public UInt16 Port { get; private set; } public Server(UInt16 port, Pool pool) { @@ -39,8 +39,11 @@ namespace Kneesocks { Socket.Start(); while(Started) { - if(Socket.Pending()) - ConnectionPool.AddConnection((T)new Connection(Socket.AcceptTcpClient())); + if(Socket.Pending()) { + var templatedConnection = new T(); + templatedConnection.Initialize(Socket.AcceptTcpClient()); + ConnectionPool.AddConnection(templatedConnection); + } Thread.Sleep(100); } diff --git a/server/Libraries/Kneesocks/Stack.cs b/server/Libraries/Kneesocks/Stack.cs index e01e47f..7575a38 100644 --- a/server/Libraries/Kneesocks/Stack.cs +++ b/server/Libraries/Kneesocks/Stack.cs @@ -6,12 +6,14 @@ using System.Text; using System.Threading; namespace Kneesocks { - public class Stack where T : Connection { + internal class Stack where T : Connection { private Pool PoolRef = null; private List Clients = new List(); private bool RunWithNoClients = false; private bool Running = true; + public bool Finished { get; private set; } = false; + public Stack(Pool poolRef, T initialConnection = null) { PoolRef = poolRef; if(initialConnection != null) @@ -36,31 +38,26 @@ namespace Kneesocks { } } - public void StopThread() { + internal void StopThread() { Running = false; } - public bool Finished { get; private set; } - - public bool UnlistConnection(Connection connection) { - lock(Clients) { - foreach(var conn in Clients) { - - } - } + private bool CheckIfConnected(T client) { + return !client.Disconnected && !client.OutsidePool; } - - // USED FOR THREADING -- DO NOT CALL + public void ManageStack() { while(Running && (Count > 0 || RunWithNoClients)) { lock(Clients) { for(var i = Count - 1; i >= 0 && Running; --i) { var client = Clients[i]; - var connected = !client.Disconnected; + var connected = CheckIfConnected(client); if(connected) { try { client.Parse(); + if(CheckIfConnected(client)) + connected = false; } catch { connected = false; } diff --git a/server/Libraries/Square/ArrayExtensions.cs b/server/Libraries/Square/ArrayExtensions.cs index 481a49e..0a33d78 100644 --- a/server/Libraries/Square/ArrayExtensions.cs +++ b/server/Libraries/Square/ArrayExtensions.cs @@ -31,7 +31,7 @@ namespace Square { return BitConverter.ToString(bytes).Replace("-", ""); } - public static string ToString(this byte[] bytes, bool isUtf8 = true) { + public static string GetString(this byte[] bytes, bool isUtf8 = true) { return isUtf8 ? Encoding.UTF8.GetString(bytes) : Encoding.ASCII.GetString(bytes); } diff --git a/server/Socks/ActiveConnection.cs b/server/Socks/ActiveConnection.cs index 6c56e91..c3b475a 100644 --- a/server/Socks/ActiveConnection.cs +++ b/server/Socks/ActiveConnection.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; namespace CircleScape { class ActiveConnection : Kneesocks.Connection { - public ActiveConnection(UInt32 id, TcpClient sock) : base(id, sock) { } - public ActiveConnection(PendingConnection conn) : base(conn) { } + } } diff --git a/server/Socks/PendingConnection.cs b/server/Socks/PendingConnection.cs index 3a6375a..fa929da 100644 --- a/server/Socks/PendingConnection.cs +++ b/server/Socks/PendingConnection.cs @@ -4,10 +4,25 @@ using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; +using Square; +using Kneesocks; namespace CircleScape { - class PendingConnection : Kneesocks.Connection { - public PendingConnection(UInt32 id, TcpClient sock) : base(id, sock) { } - + class PendingConnection : Connection { + private DateTime ConnectionOpened; + + protected override void OnOpen() { + ConnectionOpened = DateTime.UtcNow; + } + + protected override void OnParse() { + if((DateTime.UtcNow - ConnectionOpened).Seconds > 60) { + Disconnect(Frame.kClosingReason.ProtocolError, "Logon request timed out."); + } + } + + protected override void OnReceive(byte[] data) { + Console.WriteLine(Id + " says " + data.GetString()); + } } } diff --git a/server/Socks/PoolManager.cs b/server/Socks/PoolManager.cs index fa406da..ebc6dc7 100644 --- a/server/Socks/PoolManager.cs +++ b/server/Socks/PoolManager.cs @@ -32,5 +32,10 @@ namespace CircleScape { ActiveConnectionsPool = new Pool(); } + + public static void Dispose() { + PendingConnectionsPool.Dispose(); + ActiveConnectionsPool.Dispose(); + } } }