From b9a246ca68ecde523cbe7c3b97d2e3956c6c10ec Mon Sep 17 00:00:00 2001 From: juanjp600 Date: Wed, 31 Aug 2016 18:26:17 -0300 Subject: [PATCH] New login process + a little bit of cleanup No reliability required :) Will get to the client soon --- Subsurface/Source/Networking/GameClient.cs | 13 +- Subsurface/Source/Networking/GameServer.cs | 137 ++++++++------- .../Source/Networking/GameServerLogin.cs | 166 +++++++++++++++++- Subsurface/Source/Networking/NetworkMember.cs | 17 ++ 4 files changed, 258 insertions(+), 75 deletions(-) diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 7021d15b2..fe7deda61 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -114,10 +114,7 @@ namespace Barotrauma.Networking client = new NetClient(config); netPeer = client; client.Start(); - - NetOutgoingMessage outmsg = client.CreateMessage(); - - + System.Net.IPEndPoint IPEndPoint = null; try { @@ -133,8 +130,7 @@ namespace Barotrauma.Networking // Connect client, to ip previously requested from user try { - client.Connect(IPEndPoint, outmsg); - + } catch (Exception e) { @@ -446,11 +442,6 @@ namespace Barotrauma.Networking public bool SpectateClicked(GUIButton button, object userData) { - NetOutgoingMessage msg = client.CreateMessage(); - - - client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); - if (button != null) button.Enabled = false; return false; diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index a8c4015cc..ee0e00480 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -28,6 +28,8 @@ namespace Barotrauma.Networking private NetServer server; private NetPeerConfiguration config; + private int MaxPlayers; + private DateTime sparseUpdateTimer; private DateTime refreshMasterTimer; @@ -80,6 +82,7 @@ namespace Barotrauma.Networking } config.MaximumConnections = maxPlayers; + MaxPlayers = maxPlayers; config.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt | @@ -444,47 +447,57 @@ namespace Barotrauma.Networking } sparseUpdateTimer = DateTime.Now + sparseUpdateInterval; - } - - public bool StartGameClicked(GUIButton button, object obj) + } + + private byte GetNewClientID() { - Submarine selectedSub = null; - Submarine selectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle; - - if (Voting.AllowSubVoting) - { - selectedSub = Voting.HighestVoted(VoteType.Sub, connectedClients); - if (selectedSub == null) selectedSub = GameMain.NetLobbyScreen.SelectedSub; + byte userID = 1; + while (connectedClients.Any(c => c.ID == userID)) + { + userID++; } - else - { - selectedSub = GameMain.NetLobbyScreen.SelectedSub; - } - - if (selectedSub == null) - { - GameMain.NetLobbyScreen.SubList.Flash(); - return false; - } - - if (selectedShuttle == null) - { - GameMain.NetLobbyScreen.ShuttleList.Flash(); - return false; - } - - GameModePreset selectedMode = Voting.HighestVoted(VoteType.Mode, connectedClients); - if (selectedMode == null) selectedMode = GameMain.NetLobbyScreen.SelectedMode; - - if (selectedMode == null) - { - GameMain.NetLobbyScreen.ModeList.Flash(); - return false; - } - - //CoroutineManager.StartCoroutine(WaitForPlayersReady(selectedSub, selectedShuttle, selectedMode), "WaitForPlayersReady"); - - return true; + return userID; + } + + public bool StartGameClicked(GUIButton button, object obj) + { + Submarine selectedSub = null; + Submarine selectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle; + + if (Voting.AllowSubVoting) + { + selectedSub = Voting.HighestVoted(VoteType.Sub, connectedClients); + if (selectedSub == null) selectedSub = GameMain.NetLobbyScreen.SelectedSub; + } + else + { + selectedSub = GameMain.NetLobbyScreen.SelectedSub; + } + + if (selectedSub == null) + { + GameMain.NetLobbyScreen.SubList.Flash(); + return false; + } + + if (selectedShuttle == null) + { + GameMain.NetLobbyScreen.ShuttleList.Flash(); + return false; + } + + GameModePreset selectedMode = Voting.HighestVoted(VoteType.Mode, connectedClients); + if (selectedMode == null) selectedMode = GameMain.NetLobbyScreen.SelectedMode; + + if (selectedMode == null) + { + GameMain.NetLobbyScreen.ModeList.Flash(); + return false; + } + + //CoroutineManager.StartCoroutine(WaitForPlayersReady(selectedSub, selectedShuttle, selectedMode), "WaitForPlayersReady"); + + return true; } public void EndGame() @@ -528,29 +541,29 @@ namespace Barotrauma.Networking } } - CoroutineManager.StartCoroutine(EndCinematic()); - } - - public IEnumerable EndCinematic() - { - float endPreviewLength = 10.0f; - - var cinematic = new TransitionCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, endPreviewLength); - - float secondsLeft = endPreviewLength; - - do - { - secondsLeft -= CoroutineManager.UnscaledDeltaTime; - - yield return CoroutineStatus.Running; - } while (secondsLeft > 0.0f); - - Submarine.Unload(); - - GameMain.NetLobbyScreen.Select(); - - yield return CoroutineStatus.Success; + CoroutineManager.StartCoroutine(EndCinematic()); + } + + public IEnumerable EndCinematic() + { + float endPreviewLength = 10.0f; + + var cinematic = new TransitionCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, endPreviewLength); + + float secondsLeft = endPreviewLength; + + do + { + secondsLeft -= CoroutineManager.UnscaledDeltaTime; + + yield return CoroutineStatus.Running; + } while (secondsLeft > 0.0f); + + Submarine.Unload(); + + GameMain.NetLobbyScreen.Select(); + + yield return CoroutineStatus.Success; } private void UpdateCrewFrame() diff --git a/Subsurface/Source/Networking/GameServerLogin.cs b/Subsurface/Source/Networking/GameServerLogin.cs index 4457cee2e..ade2fc44a 100644 --- a/Subsurface/Source/Networking/GameServerLogin.cs +++ b/Subsurface/Source/Networking/GameServerLogin.cs @@ -13,6 +13,8 @@ namespace Barotrauma.Networking public NetConnection Connection; public int Nonce; + public int failedAttempts; + public float AuthTimer; public UnauthenticatedClient(NetConnection connection, int nonce) @@ -21,12 +23,172 @@ namespace Barotrauma.Networking Nonce = nonce; AuthTimer = 5.0f; + + failedAttempts = 0; } } partial class GameServer : NetworkMember, IPropertyObject { - List unauthenticatedClients = new List(); - + List unauthenticatedClients = new List(); + + private void ClientPasswordRequest(NetConnection conn) + { + //client wants to know if server requires password + + if (ConnectedClients.Count > MaxPlayers) + { + //server is full, can't allow new connection + conn.Disconnect("Server full"); + } + + if (ConnectedClients.Find(c => c.Connection == conn) != null) + { + //this client has already been authenticated + return; + } + UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == conn); + if (unauthClient == null) + { + //new client, generate nonce and add to unauth queue + int nonce = CryptoRandom.Instance.Next(); + unauthClient = new UnauthenticatedClient(conn, nonce); + unauthenticatedClients.Add(unauthClient); + } + //if the client is already in the queue, getting another unauth request means that our response was lost; resend + NetOutgoingMessage nonceMsg = server.CreateMessage(); + if (string.IsNullOrEmpty(password)) + { + nonceMsg.Write(false); //false = no password + } + else + { + nonceMsg.Write(true); //true = password + nonceMsg.Write(unauthClient.Nonce); //here's nonce, encrypt with this + } + server.SendMessage(nonceMsg, conn, NetDeliveryMethod.Unreliable); + } + + private void ClientInitialize(NetIncomingMessage inc) + { + if (ConnectedClients.Find(c => c.Connection == inc.SenderConnection) != null) + { + //this client was already authenticated + //another init request means they didn't get any update packets yet + return; + } + + UnauthenticatedClient unauthClient = unauthenticatedClients.Find(uc => uc.Connection == inc.SenderConnection); + if (unauthClient == null) + { + //client did not ask for nonce first, can't authorize + inc.SenderConnection.Disconnect("Client did not properly request authentication."); + return; + } + + if (!string.IsNullOrEmpty(password)) + { + //decrypt message and compare password + string saltedPw = password; + saltedPw = saltedPw + Convert.ToString(unauthClient.Nonce); + saltedPw = Encoding.UTF8.GetString(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(saltedPw))); + NetEncryption algo = new NetXtea(server, saltedPw); + inc.Decrypt(algo); + string clPw = inc.ReadString(); + if (clPw != saltedPw) + { + unauthClient.failedAttempts++; + if (unauthClient.failedAttempts > 3) + { + //disconnect after too many failed attempts + unauthClient.Connection.Disconnect("Too many failed login attempts."); + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + return; + } + else + { + //not disconnecting the player here, because they'll still use the same connection and nonce if they try logging in again + NetOutgoingMessage reject = server.CreateMessage(); + reject.Write((byte)ServerPacketHeader.AUTH_FAILURE); + reject.Write("Wrong password!"); + server.SendMessage(reject, unauthClient.Connection, NetDeliveryMethod.Unreliable); + return; + } + } + } + string clVersion = inc.ReadString(); + string clPackageName = inc.ReadString(); + string clPackageHash = inc.ReadString(); + + if (clVersion != GameMain.Version.ToString()) + { + inc.SenderConnection.Disconnect("Version " + GameMain.Version + " required to connect to the server (Your version: " + clVersion + ")"); + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + DebugConsole.NewMessage(name + " couldn't join the server (wrong game version)", Color.Red); + return; + } + if (clPackageName != GameMain.SelectedPackage.Name) + { + inc.SenderConnection.Disconnect("Your content package (" + clPackageName + ") doesn't match the server's version (" + GameMain.SelectedPackage.Name + ")"); + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + DebugConsole.NewMessage(name + " couldn't join the server (wrong content package name)", Color.Red); + return; + } + if (clPackageHash != GameMain.SelectedPackage.MD5hash.Hash) + { + unauthClient.Connection.Disconnect("Your content package (MD5: " + clPackageHash + ") doesn't match the server's version (MD5: " + GameMain.SelectedPackage.MD5hash.Hash + ")"); + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + DebugConsole.NewMessage(name + " couldn't join the server (wrong content package hash)", Color.Red); + return; + } + + string clName = Client.SanitizeName(inc.ReadString()); + if (string.IsNullOrWhiteSpace(clName)) + { + unauthClient.Connection.Disconnect("You need a name."); + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + return; + } + if (!Client.IsValidName(name)) + { + unauthClient.Connection.Disconnect("Your name contains illegal symbols."); + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + return; + } + Client nameTaken = ConnectedClients.Find(c => c.name.ToLower() == clName.ToLower()); + if (nameTaken != null) + { + if (nameTaken.Connection.RemoteEndPoint.Address.ToString() == inc.SenderEndPoint.Address.ToString()) + { + //both name and IP address match, replace this player's connection + nameTaken.Connection.Disconnect("Your session was taken by a new connection on the same IP address."); + nameTaken.Connection = unauthClient.Connection; + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + return; + } + else + { + //can't authorize this client + unauthClient.Connection.Disconnect("That name is taken."); + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + return; + } + } + + //new client + Client newClient = new Client(clName, GetNewClientID()); + newClient.Connection = unauthClient.Connection; + unauthenticatedClients.Remove(unauthClient); + unauthClient = null; + return; + } } } diff --git a/Subsurface/Source/Networking/NetworkMember.cs b/Subsurface/Source/Networking/NetworkMember.cs index 48103d3b8..15b66212f 100644 --- a/Subsurface/Source/Networking/NetworkMember.cs +++ b/Subsurface/Source/Networking/NetworkMember.cs @@ -9,6 +9,23 @@ using Barotrauma.Items.Components; namespace Barotrauma.Networking { + enum ClientPacketHeader + { + REQUEST_AUTH, //ask the server if a password is needed, if so we'll get nonce for encryption + REQUEST_INIT, //ask the server to give you initialization + UPDATE_LOBBY, //update state in lobby + UPDATE_INGAME_ALIVE, //update state ingame while alive (allow character input) + UPDATE_INGAME_SPECTATING, //update state ingame while spectating/dead + } + enum ServerPacketHeader + { + AUTH_RESPONSE, //tell the player if they require a password to log in + AUTH_FAILURE, //the server won't authorize player yet, however connection is still alive + UPDATE_LOBBY, //update state in lobby (votes and chat messages) + UPDATE_INGAME_ALIVE, //update state ingame while alive (character input and chat messages) + UPDATE_INGAME_SPECTATING, //update state ingame while spectating/dead (chat messages) + } + enum VoteType { Unknown,