diff --git a/protocol.md b/protocol.md index 5963cde..7d36327 100644 --- a/protocol.md +++ b/protocol.md @@ -211,22 +211,17 @@ Communication between the master server and clients will be done over a WebSocke Iterated over n (0 ≤ in - 1) - 2 + 4i + 2 + 3i Server Id Packed Unsigned Short - 3 + 4i + 3 + 3i User Count Packed Unsigned Short - 4 + 4i - IPv4 Address - Bytes (4) - - - 5 + 4i + 4 + 3i Port Packed Unsigned Short @@ -313,6 +308,30 @@ Communication between the master server and clients will be done over a WebSocke + + + + + + + + + + + + + + + + + + + +
+ ID 4: Server List Request
+ [Encrypted] Responder +
#RegionType
1SucceededBoolean
2MessageString
+ #### Client to Master @@ -387,6 +406,16 @@ Communication between the master server and clients will be done over a WebSocke
+ + + + +
+ ID 4: Server List Request
+ [Encrypted] Requester
+ Bodyless Packet +
+ ## Sockstamps Because epoch time is not standardized across systems, an intermediate layer of date/time transmission must be used between the client and server so as to handle time dependent interactions. Therefore, a "sockstamp" will be used in place of the context-dependent implementations of epoch time. diff --git a/server/Entrypoint.cs b/server/Entrypoint.cs index b0ea486..559861b 100644 --- a/server/Entrypoint.cs +++ b/server/Entrypoint.cs @@ -22,8 +22,6 @@ namespace SockScape { class Entrypoint { static void Main(string[] args) { - var db = new DAL.ScapeDb(); - foreach(var server in Configuration.Servers) { var pool = new Pool { InitialCount = 3, diff --git a/server/Libraries/Glove/INI/SettingsFile.cs b/server/Libraries/Glove/INI/SettingsFile.cs index 883c75f..a8e90e6 100644 --- a/server/Libraries/Glove/INI/SettingsFile.cs +++ b/server/Libraries/Glove/INI/SettingsFile.cs @@ -29,7 +29,7 @@ namespace Glove.INI { if(currentInstance != null) currentInstance.Push(line); else - throw new FormatException("Non-section line before any define sections in '"+ path +"'"); + throw new FormatException($"Non-section line before any define sections in '{path}'"); } } } @@ -41,18 +41,18 @@ namespace Glove.INI { if(ContainsSection(name)) { var section = Sections[name]; if(!rule.AllowMultiple && section.Count > 1) - throw new FormatException("Section '"+ name +"' is not allowed to have multiple declarations in '"+ path +"'"); + throw new FormatException($"Section '{name}' is not allowed to have multiple declarations in '{path}'"); if(rule.RequiredFields.Length > 0) { foreach(var instance in section) { foreach(var field in rule.RequiredFields) { if(!instance.ContainsKey(field)) - throw new FormatException("Expected field '"+ field +"' in section '" + name + "' was not found in '" + path + "'"); + throw new FormatException($"Expected field '{field}' in section '{name}' was not found in '{path}'"); } } } } else if(rule.Required) - throw new FormatException("Expected section '"+ name +"' was not found in '"+ path +"'"); + throw new FormatException($"Expected section '{name}' was not found in '{path}'"); } } diff --git a/server/Libraries/Kneesocks/Frame.cs b/server/Libraries/Kneesocks/Frame.cs index 506e7a7..3cbfab8 100644 --- a/server/Libraries/Kneesocks/Frame.cs +++ b/server/Libraries/Kneesocks/Frame.cs @@ -118,7 +118,7 @@ namespace Kneesocks { var rawOpcode = raw[0] & 0x0F; if(!Enum.IsDefined(typeof(kOpcode), rawOpcode)) - throw new ArgumentException("Opcode '0x" + rawOpcode.ToString("X") + "' not understood"); + throw new ArgumentException($"Opcode '0x{rawOpcode.ToString("X")}' not understood"); var returnFrame = new Frame { IsFinal = (raw[0] & 0x80) != 0, @@ -156,9 +156,9 @@ namespace Kneesocks { uint expectedFrameLength = (uint)returnFrame.BodyLength + (uint)returnFrame.HeaderLength; if((uint)raw.Length < expectedFrameLength) - throw new FormatException("Raw frame length ("+ (uint)raw.Length + ") is less than described size ("+ expectedFrameLength + ")"); + throw new FormatException($"Raw frame length ({(uint)raw.Length}) is less than described size ({expectedFrameLength})"); - returnFrame.Content = new byte[returnFrame.BodyLength]; + returnFrame.Content = new byte[returnFrame.BodyLength]; Array.Copy(raw, returnFrame.HeaderLength, returnFrame.Content, 0L, returnFrame.BodyLength); if(returnFrame.IsMasked) returnFrame.Content = returnFrame.MaskedContent; diff --git a/server/Libraries/Kneesocks/Handshake.cs b/server/Libraries/Kneesocks/Handshake.cs index 410c11c..8e634a2 100644 --- a/server/Libraries/Kneesocks/Handshake.cs +++ b/server/Libraries/Kneesocks/Handshake.cs @@ -30,9 +30,8 @@ namespace Kneesocks { Gateway_Timeout = 504 } public kStatusCode? StatusCode { get; private set; } = null; - protected string StatusCodeText { - get => Enum.GetName(typeof(kStatusCode), StatusCode).Replace('_', ' '); - } + protected string StatusCodeText + => Enum.GetName(typeof(kStatusCode), StatusCode).Replace('_', ' '); private readonly Dictionary Headers = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -103,9 +102,9 @@ namespace Kneesocks { SetHeader("Content-Type", "text/plain"); } - var raw = "HTTP/"+ HttpVersion +" "+ (int)StatusCode + " "+ StatusCodeText +"\r\n"; + var raw = $"HTTP/{HttpVersion} {(int)StatusCode} {StatusCodeText}\r\n"; foreach(var header in Headers) - raw += header.Key.Trim() + ": " + header.Value.Trim() + "\r\n"; + raw += $"{header.Key.Trim()}: {header.Value.Trim()}\r\n"; return raw + "\r\n"; } diff --git a/server/Libraries/Kneesocks/Pool.cs b/server/Libraries/Kneesocks/Pool.cs index 6bcfc86..f54f098 100644 --- a/server/Libraries/Kneesocks/Pool.cs +++ b/server/Libraries/Kneesocks/Pool.cs @@ -45,6 +45,9 @@ namespace Kneesocks { CreateThread(runWithNoClients: true); } + public int ConnectionCount + => Connections.Count; + public T this[UInt64 id] { get { lock (Connections) { @@ -86,7 +89,7 @@ namespace Kneesocks { if(Disposed) return false; - if(MaxTotal != 0 && Connections.Count >= MaxTotal) + if(MaxTotal != 0 && ConnectionCount >= MaxTotal) return false; lock(Threads) { diff --git a/server/Libraries/Kneesocks/Stack.cs b/server/Libraries/Kneesocks/Stack.cs index 0f3fc0f..84d95bd 100644 --- a/server/Libraries/Kneesocks/Stack.cs +++ b/server/Libraries/Kneesocks/Stack.cs @@ -32,9 +32,7 @@ namespace Kneesocks { } } - public int Count { - get => Clients.Count; - } + public int Count => Clients.Count; internal void StopThread() => Running = false; diff --git a/server/ServerList.cs b/server/ServerList.cs index 13e7b5b..0665bb1 100644 --- a/server/ServerList.cs +++ b/server/ServerList.cs @@ -6,26 +6,51 @@ using System.Text; using System.Threading.Tasks; namespace SockScape { - class ServerList { - public static Dictionary Servers { get; } - = new Dictionary(); + static class ServerList { + private static ServerDictionary Servers { get; } + = new ServerDictionary(); - public Server this[int i] { - get => Servers.ContainsKey(i) ? Servers[i] : null; - set => Servers[i] = value; + private static Dictionary List + => Servers.List; + + public static void Write(Server server) { + lock(Servers) { + if(HasId(server.Id) && !List[server.Id].Address.Equals(server.Address)) + Console.WriteLine($"{DateTime.Now.ToShortTimeString()} - Server {server.Id} has changed IP addresses."); + + List[server.Id] = server; + } } - public bool HasId(int id) - => Servers.ContainsKey(id); + public static Packet ReportPacket { + get { + var packet = new Packet(kInterMasterId.); - public void Clear() - => Servers.Clear(); + return packet; + } + } + + public static bool HasId(UInt16 id) + => List.ContainsKey(id); + + public static void Clear() + => List.Clear(); + + public class ServerDictionary { + public Dictionary List { get; } + = new Dictionary(); + + public Server this[UInt16 i] { + get => List.ContainsKey(i) ? List[i] : null; + set => List[i] = value; + } + } } class Server { public ushort Id { get; set; } public ushort UserCount { get; set; } - public IPEndPoint Address { get; set; } - public IPEndPoint Owner { get; set; } + public IPAddress Address { get; set; } + public ushort Port { get; set; } } } diff --git a/server/Socks/MasterConnection.cs b/server/Socks/MasterConnection.cs index edf6947..fb1d9bc 100644 --- a/server/Socks/MasterConnection.cs +++ b/server/Socks/MasterConnection.cs @@ -11,7 +11,6 @@ namespace SockScape { class MasterConnection : Connection { private Key Key; public Cipher Encryptor { get; private set; } - protected override void OnOpen() { Key = new Key(); @@ -53,7 +52,7 @@ namespace SockScape { break; } - Console.WriteLine(Id + " says " + data.GetString()); + Console.WriteLine($"{Id} says {data.GetString()}"); } } } diff --git a/server/Socks/MasterUdpClient.cs b/server/Socks/MasterUdpClient.cs index ebdca27..7ee0e42 100644 --- a/server/Socks/MasterUdpClient.cs +++ b/server/Socks/MasterUdpClient.cs @@ -18,7 +18,7 @@ namespace SockScape { private static Thread ListeningThread; private static bool IsOpen; - private static DateTime LastMessageOut; + private static DateTime LastMessageOut = new DateTime(0); private static TimeSpan DeltaLastOut => DateTime.Now - LastMessageOut; @@ -43,9 +43,6 @@ namespace SockScape { public static void Listener() { while(IsOpen) { - if(LastMessageIn.Ticks == 0 && DeltaLastOut.TotalSeconds > 10) - Send(new Packet(kIntraSlaveId.InitiationAttempt, Configuration.General["Master Secret"])); - var endPoint = new IPEndPoint(0, 0); while(Sock.Available > 0) { var data = Sock.Receive(ref endPoint); @@ -67,11 +64,11 @@ namespace SockScape { break; case kIntraMasterId.PositiveAck: - + Console.WriteLine($"Packet type {packet[0]} accepted by master"); break; case kIntraMasterId.NegativeAck: - Console.WriteLine("Packet type "+ packet[0] +" declined for reason "+ packet[1]); + Console.WriteLine($"Packet type {packet[0]} declined by master for reason {packet[1]}"); break; case kIntraMasterId.EncryptionError: @@ -82,6 +79,12 @@ namespace SockScape { } } + if(LastMessageIn.Ticks == 0 && DeltaLastOut.TotalSeconds > 10) + Send(new Packet(kIntraSlaveId.InitiationAttempt, Configuration.General["Master Secret"])); + else { + + } + Thread.Sleep(1); } } diff --git a/server/Socks/MasterUdpServer.cs b/server/Socks/MasterUdpServer.cs index 9e51355..961a735 100644 --- a/server/Socks/MasterUdpServer.cs +++ b/server/Socks/MasterUdpServer.cs @@ -7,13 +7,13 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Glove; +using SockScape.DAL; using SockScape.Encryption; namespace SockScape { static class MasterUdpServer { private static Dictionary Prospects; private static Dictionary Clients; - private static ServerList Servers; private static UdpClient Sock; private static Thread ListeningThread; @@ -22,8 +22,8 @@ namespace SockScape { public static void Initialize() { if(IsOpen || ListeningThread != null) return; - - Servers = new ServerList(); + + ServerList.Clear(); Prospects = new Dictionary(); Clients = new Dictionary(); @@ -50,7 +50,7 @@ namespace SockScape { : Packet.FromBytes(encryptor.Parse(data)); if(packet == null) - break; + continue; switch((kIntraSlaveId)packet.Id) { case kIntraSlaveId.InitiationAttempt: @@ -86,7 +86,30 @@ namespace SockScape { if(!IsClientConnected(client) || packet.RegionCount < 1) break; - + if(packet.CheckRegions(0, 1)) { + NegativeAck(endPoint, kIntraSlaveId.StatusUpdate, "Server count is malformed."); + break; + } + + byte serverCount = packet[0].Raw[0]; + if(packet.RegionCount != 1 + 3 * serverCount) { + NegativeAck(endPoint, kIntraSlaveId.StatusUpdate, "Region count does not match server count"); + break; + } + + for(byte i = 0; i < serverCount; ++i) { + if(!packet.CheckRegions(2 + 3 * i, 2, 2, 2)) + continue; + + ServerList.Write(new Server { + Id = packet[2 + 3 * i].Raw.UnpackUInt16(), + UserCount = packet[3 + 3 * i].Raw.UnpackUInt16(), + Address = endPoint.Address, + Port = packet[4 + 3 * i].Raw.UnpackUInt16() + }); + } + + PositiveAck(endPoint, kIntraSlaveId.StatusUpdate); break; } } @@ -117,16 +140,16 @@ namespace SockScape { return Clients.ContainsKey(client); } - private static Packet PositiveAck(byte id) { - return new Packet(kIntraMasterId.PositiveAck, id); + private static void PositiveAck(IPEndPoint endPoint, kIntraSlaveId id) { + Send(new Packet(kIntraMasterId.PositiveAck, id), endPoint); } - private static Packet NegativeAck(byte id, string message = "") { - return new Packet(kIntraMasterId.NegativeAck, id, message); + private static void NegativeAck(IPEndPoint endPoint, kIntraSlaveId id, string message = "An error occurred while parsing a packet.") { + Send(new Packet(kIntraMasterId.NegativeAck, id, message), endPoint); } - private static Packet EncryptionError(string message = "A general encryption error has occurred.") { - return new Packet(kIntraMasterId.EncryptionError, message); + private static void EncryptionError(IPEndPoint endPoint, string message = "A general encryption error has occurred. Renegotiation required.") { + Send(new Packet(kIntraMasterId.EncryptionError, message), endPoint); } class Client { diff --git a/server/Socks/Protocols/InterMasterIds.cs b/server/Socks/Protocols/InterMasterIds.cs index f51e7fc..a33b7a7 100644 --- a/server/Socks/Protocols/InterMasterIds.cs +++ b/server/Socks/Protocols/InterMasterIds.cs @@ -8,6 +8,7 @@ namespace SockScape { public enum kInterMasterId { KeyExchange = 1, LoginAttempt, - RegistrationAttempt + RegistrationAttempt, + ServerListing } } diff --git a/server/Socks/Protocols/Packet.cs b/server/Socks/Protocols/Packet.cs index 5610c24..a820d44 100644 --- a/server/Socks/Protocols/Packet.cs +++ b/server/Socks/Protocols/Packet.cs @@ -92,6 +92,18 @@ namespace SockScape { return this; } + public bool CheckRegions(int startIndex, params int[] lengths) { + if(startIndex + lengths.Length > RegionCount) + return false; + + for(int i = 0; i < lengths.Length; ++i) { + if(this[startIndex + i].Raw.Length == lengths[i]) + return false; + } + + return true; + } + public byte[] GetBytes() { var header = new List(); header.AddRange(MagicNumber); @@ -117,14 +129,14 @@ namespace SockScape { } public class Region { - public byte[] Data { get; } + private byte[] Data { get; } public Region(byte[] data) { Data = data; } public static implicit operator byte[] (Region region) => region.Data; - public string Bytes + public byte[] Raw => this; public static implicit operator string(Region region) {