public static void Initialize() { if (IsOpen || ListeningThread != null) { return; } MasterServerList.Clear(); Prospects = new Dictionary <string, Client>(); Clients = new Dictionary <string, Client>(); ushort port = (ushort)Configuration.General["Master Port"]; Sock = new UdpClient(new IPEndPoint(IPAddress.Any, port)); // TODO figure out what this has to do with ICMP (in client too) /*uint IOC_IN = 0x80000000, * IOC_VENDOR = 0x18000000, * SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; * Sock.Client.IOControl((int)SIO_UDP_CONNRESET, new byte[] {0}, null);*/ IsOpen = true; ListeningThread = new Thread(Listener); ListeningThread.Start(); }
protected override void OnReceive(byte[] data) { Packet packet = Encryptor == null?Packet.FromBytes(data) : Packet.FromBytes(Encryptor.Parse(data)); if (packet == null) { Disconnect(Frame.kClosingReason.ProtocolError, "Packet received was not legal."); return; } if (packet.Id != (int)kInterMasterId.KeyExchange && Encryptor == null) { Disconnect(Frame.kClosingReason.ProtocolError, "You must exchange keys before performing any other operations."); return; } // TODO rate limiting by ip switch ((kInterMasterId)packet.Id) { case kInterMasterId.KeyExchange: Key.ParseResponsePacket(packet); if (!Key.Succeeded) { Disconnect(Frame.kClosingReason.ProtocolError, "Could not exchange keys."); return; } Encryptor = new StreamCipher(Key.PrivateKey); break; case kInterMasterId.LoginAttempt: if (packet.RegionCount != 3 || !packet.CheckRegionsMaxLength(0, 16, 255) || !packet.CheckRegionLengths(2, 2)) { break; } Session session; using (var db = new ScapeDb()) { User user; if ((user = db.Users.FirstOrDefault(x => x.Username == packet[0])) == null) { SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(false), "User does not exist.")); break; } if (packet[1].Str.Trim() == "" || !packet[1].Str.CheckPassword(user.Password)) { SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(false), "Password is incorrect.")); break; } if (user.Session?.DeltaLastPing.TotalMinutes < 3) { SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(false), "You are already logged in. Log out or try again shortly.")); break; } ushort serverId = packet[2].Raw.UnpackUInt16(); if (!MasterServerList.HasId(serverId)) { SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(false), "The world you have specified is offline.")); break; } if (user.Session?.DeltaLastPing.TotalMinutes >= 3) { db.Entry(user.Session).State = EntityState.Deleted; db.SaveChanges(); } db.Sessions.Add(session = new Session { Id = user.Id, Secret = RNG.NextBytes(16), ServerId = serverId, LastPing = DateTime.UtcNow }); db.SaveChanges(); } var server = MasterServerList.Get((ushort)session.ServerId); SendEncrypted(new Packet(kInterMasterId.LoginAttempt, Convert.ToByte(true), session.Secret, server.Address.ToString(), server.Port.Pack())); break; case kInterMasterId.RegistrationAttempt: if (packet.RegionCount != 3 || !packet.CheckAllMaxLength(0xFF)) { break; } if (!packet[0].Raw.IsAsciiString()) { SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "Your username cannot contain unicode characters.")); break; } string username = packet[0].Str.Trim(), password = packet[1].Str.Trim(), email = packet[2].Str.Trim(); if (username.Length > 16 || !UsernameRegex.IsMatch(username)) { SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "The username is max 16 characters and can only be letters, numbers, and underscores.")); break; } if (!EmailRegex.IsMatch(email)) { SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "The email address is malformed.")); break; } using (var db = new ScapeDb()) { if (db.Users.FirstOrDefault(x => x.Username == username) != null) { SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "This username is already in use.")); break; } if (db.Users.FirstOrDefault(x => x.Email == email) != null) { SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(false), "This email address is already in use.")); break; } // TODO email activation db.Users.Add(new User { Username = username, Password = password.HashPassword(), Email = email, Joined = DateTime.UtcNow }); db.SaveChanges(); } SendEncrypted(new Packet(kInterMasterId.RegistrationAttempt, Convert.ToByte(true), "Registration was successful.")); break; case kInterMasterId.ServerListing: SendEncrypted(MasterServerList.ReportPacket); break; default: Disconnect(Frame.kClosingReason.ProtocolError, "Packet ID could not be understood at this time."); break; } Console.WriteLine($"{Id} says {data.GetString()}"); }
public static void Listener() { while (IsOpen) { IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 0); while (Sock.Available > 0) { var data = Sock.Receive(ref endPoint); var client = endPoint.ToString(); var encryptor = IsClientConnected(client) ? Clients[client].Encryptor : null; if (data.Take(Packet.MagicNumber.Length).SequenceEqual(Packet.MagicNumber)) { encryptor = null; } Packet packet = encryptor == null?Packet.FromBytes(data) : Packet.FromBytes(encryptor.Decrypt(data)); if (packet == null) { if (encryptor != null) { EncryptionError(endPoint); } continue; } if (IsProspectConnected(client) && encryptor == null) { Prospects[client].LastReceive = DateTime.UtcNow; } else if (IsClientConnected(client) && encryptor != null) { Clients[client].LastReceive = DateTime.UtcNow; } switch ((kIntraSlaveId)packet.Id) { case kIntraSlaveId.InitiationAttempt: if (packet.RegionCount != 1 || IsProspectConnected(client)) { break; } if (packet[0] == Configuration.General["Master Secret"]) { var key = new Key(); Prospects[client] = new Client { LastReceive = DateTime.UtcNow, Address = endPoint, Key = key }; Send(key.GenerateRequestPacket(), endPoint); } break; case kIntraSlaveId.KeyExchange: if (!IsProspectConnected(client)) { break; } var privateKey = Prospects[client].Key.ParseResponsePacket(packet); if (privateKey != -1) { Prospects[client].LastReceive = DateTime.UtcNow; Prospects[client].Encryptor = new BlockCipher(privateKey); Clients[client] = Prospects[client]; Prospects.Remove(client); } else { Prospects.Remove(client); } break; case kIntraSlaveId.StatusUpdate: if (!IsClientConnected(client) || packet.RegionCount < 1) { break; } if (!packet.CheckRegionLengths(0, 1)) { NegativeAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate, "Server count is malformed."); break; } byte serverCount = packet[0].Raw[0]; if (packet.RegionCount != 1 + 3 * serverCount) { NegativeAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate, "Region count does not match server count"); break; } for (byte i = 0; i < serverCount; ++i) { if (!packet.CheckRegionLengths(1 + 3 * i, 2, 2, 2)) { continue; } MasterServerList.Write(new Server { Id = packet[1 + 3 * i].Raw.UnpackUInt16(), UserCount = packet[2 + 3 * i].Raw.UnpackUInt16(), Address = endPoint.Address, Port = packet[3 + 3 * i].Raw.UnpackUInt16(), Owner = Clients[client] }); } PositiveAck(endPoint, encryptor, kIntraSlaveId.StatusUpdate); break; } } Prospects = Prospects.Where(x => x.Value.ReceiveDelta.TotalSeconds < 10) .ToDictionary(x => x.Key, x => x.Value); var expiredClients = Clients.Where(x => x.Value.ReceiveDelta.TotalSeconds > 60).Select(x => x.Value).ToList(); if (expiredClients.Count > 0) { MasterServerList.RemoveServersByOwners(expiredClients); } Thread.Sleep(1); } }