v0.10.5.1

This commit is contained in:
Juan Pablo Arce
2020-09-22 11:31:56 -03:00
parent 44032d0ae0
commit 0002ad2c50
343 changed files with 12276 additions and 5023 deletions
@@ -8,41 +8,9 @@ namespace Barotrauma.Networking
{
class LidgrenServerPeer : ServerPeer
{
private readonly ServerSettings serverSettings;
private NetPeerConfiguration netPeerConfiguration;
private NetServer netServer;
private class PendingClient
{
public string Name;
public int OwnerKey;
public NetConnection Connection;
public ConnectionInitialization InitializationStep;
public double UpdateTime;
public double TimeOut;
public int Retries;
public UInt64? SteamID;
public Int32? PasswordSalt;
public bool AuthSessionStarted;
public PendingClient(NetConnection conn)
{
OwnerKey = 0;
Connection = conn;
InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
Retries = 0;
SteamID = null;
PasswordSalt = null;
UpdateTime = Timing.TotalTime + Timing.Step * 3.0;
TimeOut = NetworkConnection.TimeoutThreshold;
AuthSessionStarted = false;
}
}
private readonly List<LidgrenConnection> connectedClients;
private readonly List<PendingClient> pendingClients;
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
public LidgrenServerPeer(int? ownKey, ServerSettings settings)
@@ -51,7 +19,7 @@ namespace Barotrauma.Networking
netServer = null;
connectedClients = new List<LidgrenConnection>();
connectedClients = new List<NetworkConnection>();
pendingClients = new List<PendingClient>();
incomingLidgrenMessages = new List<NetIncomingMessage>();
@@ -168,7 +136,16 @@ namespace Barotrauma.Networking
for (int i = 0; i < pendingClients.Count; i++)
{
PendingClient pendingClient = pendingClients[i];
UpdatePendingClient(pendingClient, deltaTime);
var connection = pendingClient.Connection as LidgrenConnection;
if (connection.NetConnection.Status == NetConnectionStatus.InitiatedConnect ||
connection.NetConnection.Status == NetConnectionStatus.ReceivedInitiation ||
connection.NetConnection.Status == NetConnectionStatus.RespondedAwaitingApproval ||
connection.NetConnection.Status == NetConnectionStatus.RespondedConnect)
{
continue;
}
UpdatePendingClient(pendingClient);
if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
}
@@ -214,11 +191,11 @@ namespace Barotrauma.Networking
return;
}
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
PendingClient pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection);
if (pendingClient == null)
{
pendingClient = new PendingClient(inc.SenderConnection);
pendingClient = new PendingClient(new LidgrenConnection("PENDING", inc.SenderConnection, 0));
pendingClients.Add(pendingClient);
}
@@ -229,7 +206,7 @@ namespace Barotrauma.Networking
{
if (netServer == null) { return; }
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
PendingClient pendingClient = pendingClients.Find(c => (c.Connection is LidgrenConnection l) && l.NetConnection == inc.SenderConnection);
byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
@@ -237,11 +214,11 @@ namespace Barotrauma.Networking
if (isConnectionInitializationStep && pendingClient != null)
{
ReadConnectionInitializationStep(pendingClient, inc);
ReadConnectionInitializationStep(pendingClient, new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false));
}
else if (!isConnectionInitializationStep)
{
LidgrenConnection conn = connectedClients.Find(c => c.NetConnection == inc.SenderConnection);
LidgrenConnection conn = connectedClients.Find(c => (c is LidgrenConnection l) && l.NetConnection == inc.SenderConnection) as LidgrenConnection;
if (conn == null)
{
if (pendingClient != null)
@@ -278,7 +255,7 @@ namespace Barotrauma.Networking
{
case NetConnectionStatus.Disconnected:
string disconnectMsg;
LidgrenConnection conn = connectedClients.Find(c => c.NetConnection == inc.SenderConnection);
LidgrenConnection conn = connectedClients.Select(c => c as LidgrenConnection).FirstOrDefault(c => c.NetConnection == inc.SenderConnection);
if (conn != null)
{
if (conn == OwnerConnection)
@@ -295,7 +272,7 @@ namespace Barotrauma.Networking
}
else
{
PendingClient pendingClient = pendingClients.Find(c => c.Connection == inc.SenderConnection);
PendingClient pendingClient = pendingClients.Find(c => (c.Connection is LidgrenConnection l) && l.NetConnection == inc.SenderConnection);
if (pendingClient != null)
{
RemovePendingClient(pendingClient, DisconnectReason.Unknown, $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}");
@@ -305,303 +282,6 @@ namespace Barotrauma.Networking
}
}
private void ReadConnectionInitializationStep(PendingClient pendingClient, NetIncomingMessage inc)
{
if (netServer == null) { return; }
pendingClient.TimeOut = NetworkConnection.TimeoutThreshold;
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
//DebugConsole.NewMessage(initializationStep+" "+pendingClient.InitializationStep);
if (pendingClient.InitializationStep != initializationStep) return;
pendingClient.UpdateTime = Timing.TotalTime + Timing.Step;
switch (initializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
string name = Client.SanitizeName(inc.ReadString());
int ownKey = inc.ReadInt32();
UInt64 steamId = inc.ReadUInt64();
UInt16 ticketLength = inc.ReadUInt16();
byte[] ticket = inc.ReadBytes(ticketLength);
if (!Client.IsValidName(name, serverSettings))
{
if (OwnerConnection != null ||
!IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4NoThrow()) &&
ownerKey == null || ownKey == 0 && ownKey != ownerKey)
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidName, "The name \"" + name + "\" is invalid");
return;
}
}
string version = inc.ReadString();
bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false;
if (!isCompatibleVersion)
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidVersion,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version}~[clientversion]={version}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(name + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
return;
}
Client nameTaken = GameMain.Server.ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), name.ToLower()));
if (nameTaken != null)
{
RemovePendingClient(pendingClient, DisconnectReason.NameTaken, "");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (name too similar to the name of the client \"" + nameTaken.Name + "\").", ServerLog.MessageType.Error);
return;
}
int contentPackageCount = inc.ReadVariableInt32();
List<ClientContentPackage> clientContentPackages = new List<ClientContentPackage>();
for (int i = 0; i < contentPackageCount; i++)
{
string packageName = inc.ReadString();
string packageHash = inc.ReadString();
clientContentPackages.Add(new ClientContentPackage(packageName, packageHash));
}
//check if the client is missing any of our packages
List<ContentPackage> missingPackages = new List<ContentPackage>();
foreach (ContentPackage serverContentPackage in GameMain.SelectedPackages)
{
if (!serverContentPackage.HasMultiplayerIncompatibleContent) continue;
bool packageFound = clientContentPackages.Any(cp => cp.Name == serverContentPackage.Name && cp.Hash == serverContentPackage.MD5hash.Hash);
if (!packageFound) { missingPackages.Add(serverContentPackage); }
}
//check if the client is using packages we don't have
List<ClientContentPackage> redundantPackages = new List<ClientContentPackage>();
foreach (ClientContentPackage clientContentPackage in clientContentPackages)
{
bool packageFound = GameMain.SelectedPackages.Any(cp => cp.Name == clientContentPackage.Name && cp.MD5hash.Hash == clientContentPackage.Hash);
if (!packageFound) { redundantPackages.Add(clientContentPackage); }
}
if (missingPackages.Count == 1)
{
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (missingPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
if (redundantPackages.Count == 1)
{
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr(redundantPackages[0])}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (using an incompatible content package " + GetPackageStr(redundantPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
if (redundantPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
redundantPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}");
GameServer.Log(name + " (" + inc.SenderConnection.RemoteEndPoint.Address + ") couldn't join the server (using incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
if (pendingClient.SteamID == null)
{
bool requireSteamAuth = GameMain.Config.RequireSteamAuthentication;
#if DEBUG
requireSteamAuth = false;
#endif
//steam auth cannot be done (SteamManager not initialized or no ticket given),
//but it's not required either -> let the client join without auth
if ((!Steam.SteamManager.IsInitialized || (ticket?.Length ?? 0) == 0) &&
!requireSteamAuth)
{
pendingClient.Name = name;
pendingClient.OwnerKey = ownKey;
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
}
else
{
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "Steam auth session failed to start: " + authSessionStartState.ToString());
return;
}
pendingClient.SteamID = steamId;
pendingClient.Name = name;
pendingClient.OwnerKey = ownKey;
pendingClient.AuthSessionStarted = true;
}
}
else //TODO: could remove since this seems impossible
{
if (pendingClient.SteamID != steamId)
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "SteamID mismatch");
return;
}
}
break;
case ConnectionInitialization.Password:
int pwLength = inc.ReadByte();
byte[] incPassword = new byte[pwLength];
inc.ReadBytes(incPassword, 0, pwLength);
if (pendingClient.PasswordSalt == null)
{
DebugConsole.ThrowError("Received password message from client without salt");
return;
}
if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value))
{
pendingClient.InitializationStep = ConnectionInitialization.ContentPackageOrder;
}
else
{
pendingClient.Retries++;
if (serverSettings.BanAfterWrongPassword && pendingClient.Retries > serverSettings.MaxPasswordRetriesBeforeBan)
{
string banMsg = "Failed to enter correct password too many times";
if (pendingClient.SteamID != null)
{
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.SteamID.Value, banMsg, null);
}
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.Connection.RemoteEndPoint.Address, banMsg, null);
RemovePendingClient(pendingClient, DisconnectReason.Banned, banMsg);
return;
}
}
pendingClient.UpdateTime = Timing.TotalTime;
break;
case ConnectionInitialization.ContentPackageOrder:
pendingClient.InitializationStep = ConnectionInitialization.Success;
pendingClient.UpdateTime = Timing.TotalTime;
break;
}
}
private void UpdatePendingClient(PendingClient pendingClient, float deltaTime)
{
if (netServer == null) { return; }
if (serverSettings.BanList.IsBanned(pendingClient.Connection.RemoteEndPoint.Address, pendingClient.SteamID ?? 0, out string banReason))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
return;
}
//DebugConsole.NewMessage("pending client status: " + pendingClient.InitializationStep);
if (connectedClients.Count >= serverSettings.MaxPlayers)
{
RemovePendingClient(pendingClient, DisconnectReason.ServerFull, "");
}
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
{
LidgrenConnection newConnection = new LidgrenConnection(pendingClient.Name, pendingClient.Connection, pendingClient.SteamID ?? 0)
{
Status = NetworkConnectionStatus.Connected
};
connectedClients.Add(newConnection);
pendingClients.Remove(pendingClient);
if (OwnerConnection == null &&
IPAddress.IsLoopback(pendingClient.Connection.RemoteEndPoint.Address.MapToIPv4NoThrow()) &&
ownerKey != null && pendingClient.OwnerKey != 0 && pendingClient.OwnerKey == ownerKey)
{
ownerKey = null;
OwnerConnection = newConnection;
}
OnInitializationComplete?.Invoke(newConnection);
return;
}
pendingClient.TimeOut -= deltaTime;
if (pendingClient.TimeOut < 0.0)
{
RemovePendingClient(pendingClient, DisconnectReason.Unknown, Lidgren.Network.NetConnection.NoResponseMessage);
}
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
pendingClient.UpdateTime = Timing.TotalTime + 1.0;
NetOutgoingMessage outMsg = netServer.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)pendingClient.InitializationStep);
switch (pendingClient.InitializationStep)
{
case ConnectionInitialization.ContentPackageOrder:
var mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).ToList();
outMsg.WriteVariableInt32(mpContentPackages.Count);
for (int i = 0; i < mpContentPackages.Count; i++)
{
outMsg.Write(mpContentPackages[i].MD5hash.Hash);
}
break;
case ConnectionInitialization.Password:
outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits();
if (pendingClient.PasswordSalt == null)
{
pendingClient.PasswordSalt = CryptoRandom.Instance.Next();
outMsg.Write(pendingClient.PasswordSalt.Value);
}
else
{
outMsg.Write(pendingClient.Retries);
}
break;
}
#if DEBUG
netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Server.SimulatedDuplicatesChance;
netPeerConfiguration.SimulatedMinimumLatency = GameMain.Server.SimulatedMinimumLatency;
netPeerConfiguration.SimulatedRandomLatency = GameMain.Server.SimulatedRandomLatency;
netPeerConfiguration.SimulatedLoss = GameMain.Server.SimulatedLoss;
#endif
NetSendResult result = netServer.SendMessage(outMsg, pendingClient.Connection, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Sent && result != NetSendResult.Queued)
{
DebugConsole.NewMessage("Failed to send initialization step " + pendingClient.InitializationStep.ToString() + " to pending client: " + result.ToString(), Microsoft.Xna.Framework.Color.Yellow);
}
//DebugConsole.NewMessage("sent update to pending client: " + pendingClient.InitializationStep);
}
private void RemovePendingClient(PendingClient pendingClient, DisconnectReason reason, string msg)
{
if (netServer == null) { return; }
if (pendingClients.Contains(pendingClient))
{
pendingClients.Remove(pendingClient);
if (pendingClient.AuthSessionStarted)
{
Steam.SteamManager.StopAuthSession(pendingClient.SteamID.Value);
pendingClient.SteamID = null;
pendingClient.AuthSessionStarted = false;
}
pendingClient.Connection.Disconnect(reason + "/" + msg);
}
}
public override void InitializeSteamServerCallbacks()
{
Steamworks.SteamServer.OnValidateAuthTicketResponse += OnAuthChange;
@@ -618,7 +298,7 @@ namespace Barotrauma.Networking
{
if (status != Steamworks.AuthResponse.OK)
{
LidgrenConnection connection = connectedClients.Find(c => c.SteamID == steamID);
LidgrenConnection connection = connectedClients.Find(c => c.SteamID == steamID) as LidgrenConnection;
if (connection != null)
{
Disconnect(connection, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString());
@@ -627,7 +307,8 @@ namespace Barotrauma.Networking
return;
}
if (serverSettings.BanList.IsBanned(pendingClient.Connection.RemoteEndPoint.Address, steamID, out string banReason))
LidgrenConnection pendingConnection = pendingClient.Connection as LidgrenConnection;
if (serverSettings.BanList.IsBanned(pendingConnection.NetConnection.RemoteEndPoint.Address, steamID, out string banReason))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
return;
@@ -698,5 +379,87 @@ namespace Barotrauma.Networking
}
lidgrenConn.NetConnection.Disconnect(msg ?? "Disconnected");
}
protected override void SendMsgInternal(NetworkConnection conn, DeliveryMethod deliveryMethod, IWriteMessage msg)
{
LidgrenConnection lidgrenConn = conn as LidgrenConnection;
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
switch (deliveryMethod)
{
case DeliveryMethod.Unreliable:
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
break;
case DeliveryMethod.Reliable:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
break;
case DeliveryMethod.ReliableOrdered:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
break;
}
NetOutgoingMessage lidgrenMsg = netServer.CreateMessage();
lidgrenMsg.Write(msg.Buffer, 0, msg.LengthBytes);
NetSendResult result = netServer.SendMessage(lidgrenMsg, lidgrenConn.NetConnection, lidgrenDeliveryMethod);
if (result != NetSendResult.Sent && result != NetSendResult.Queued)
{
DebugConsole.NewMessage("Failed to send message to " + conn.Name + ": " + result.ToString(), Microsoft.Xna.Framework.Color.Yellow);
}
}
protected override void CheckOwnership(PendingClient pendingClient)
{
LidgrenConnection l = pendingClient.Connection as LidgrenConnection;
if (OwnerConnection == null &&
IPAddress.IsLoopback(l.NetConnection.RemoteEndPoint.Address.MapToIPv4NoThrow()) &&
ownerKey != null && pendingClient.OwnerKey != 0 && pendingClient.OwnerKey == ownerKey)
{
ownerKey = null;
OwnerConnection = pendingClient.Connection;
}
}
protected override void ProcessAuthTicket(string name, int ownKey, ulong steamId, PendingClient pendingClient, byte[] ticket)
{
if (pendingClient.SteamID == null)
{
bool requireSteamAuth = GameMain.Config.RequireSteamAuthentication;
#if DEBUG
requireSteamAuth = false;
#endif
//steam auth cannot be done (SteamManager not initialized or no ticket given),
//but it's not required either -> let the client join without auth
if ((!Steam.SteamManager.IsInitialized || (ticket?.Length ?? 0) == 0) &&
!requireSteamAuth)
{
pendingClient.Connection.Name = name;
pendingClient.Name = name;
pendingClient.OwnerKey = ownKey;
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
}
else
{
Steamworks.BeginAuthResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "Steam auth session failed to start: " + authSessionStartState.ToString());
return;
}
pendingClient.SteamID = steamId;
pendingClient.Connection.Name = name;
pendingClient.Name = name;
pendingClient.OwnerKey = ownKey;
pendingClient.AuthSessionStarted = true;
}
}
else //TODO: could remove since this seems impossible
{
if (pendingClient.SteamID != steamId)
{
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "SteamID mismatch");
return;
}
}
}
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
@@ -7,26 +8,6 @@ namespace Barotrauma.Networking
{
abstract class ServerPeer
{
protected struct ClientContentPackage
{
public string Name;
public string Hash;
public ClientContentPackage(string name, string hash)
{
Name = name; Hash = hash;
}
}
protected string GetPackageStr(ContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
}
protected string GetPackageStr(ClientContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + Md5Hash.GetShortHash(contentPackage.Hash) + ")";
}
public delegate void MessageCallback(NetworkConnection connection, IReadMessage message);
public delegate void DisconnectCallback(NetworkConnection connection, string reason);
public delegate void InitializationCompleteCallback(NetworkConnection connection);
@@ -48,7 +29,245 @@ namespace Barotrauma.Networking
public abstract void Start();
public abstract void Close(string msg = null);
public abstract void Update(float deltaTime);
protected class PendingClient
{
public string Name;
public int OwnerKey;
public NetworkConnection Connection;
public ConnectionInitialization InitializationStep;
public double UpdateTime;
public double TimeOut;
public int Retries;
public UInt64? SteamID;
public Int32? PasswordSalt;
public bool AuthSessionStarted;
public PendingClient(NetworkConnection conn)
{
OwnerKey = 0;
Connection = conn;
InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
Retries = 0;
SteamID = null;
PasswordSalt = null;
UpdateTime = Timing.TotalTime + Timing.Step * 3.0;
TimeOut = NetworkConnection.TimeoutThreshold;
AuthSessionStarted = false;
}
public void Heartbeat()
{
TimeOut = NetworkConnection.TimeoutThreshold;
}
}
protected List<NetworkConnection> connectedClients;
protected List<PendingClient> pendingClients;
protected ServerSettings serverSettings;
protected void ReadConnectionInitializationStep(PendingClient pendingClient, IReadMessage inc)
{
pendingClient.TimeOut = NetworkConnection.TimeoutThreshold;
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
if (pendingClient.InitializationStep != initializationStep) return;
pendingClient.UpdateTime = Timing.TotalTime + Timing.Step;
switch (initializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
string name = Client.SanitizeName(inc.ReadString());
int ownerKey = inc.ReadInt32();
UInt64 steamId = inc.ReadUInt64();
UInt16 ticketLength = inc.ReadUInt16();
byte[] ticketBytes = inc.ReadBytes(ticketLength);
if (!Client.IsValidName(name, serverSettings))
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidName, "The name \"" + name + "\" is invalid");
return;
}
string version = inc.ReadString();
bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false;
if (!isCompatibleVersion)
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidVersion,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version}~[clientversion]={version}");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
return;
}
string language = inc.ReadString();
pendingClient.Connection.Language = language;
Client nameTaken = GameMain.Server.ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), name.ToLower()));
if (nameTaken != null)
{
RemovePendingClient(pendingClient, DisconnectReason.NameTaken, "");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (name too similar to the name of the client \"" + nameTaken.Name + "\").", ServerLog.MessageType.Error);
return;
}
if (!pendingClient.AuthSessionStarted)
{
ProcessAuthTicket(name, ownerKey, steamId, pendingClient, ticketBytes);
}
break;
case ConnectionInitialization.Password:
int pwLength = inc.ReadByte();
byte[] incPassword = inc.ReadBytes(pwLength);
if (pendingClient.PasswordSalt == null)
{
DebugConsole.ThrowError("Received password message from client without salt");
return;
}
if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value))
{
pendingClient.InitializationStep = ConnectionInitialization.ContentPackageOrder;
}
else
{
pendingClient.Retries++;
if (serverSettings.BanAfterWrongPassword && pendingClient.Retries > serverSettings.MaxPasswordRetriesBeforeBan)
{
string banMsg = "Failed to enter correct password too many times";
BanPendingClient(pendingClient, banMsg, null);
RemovePendingClient(pendingClient, DisconnectReason.Banned, banMsg);
return;
}
}
pendingClient.UpdateTime = Timing.TotalTime;
break;
case ConnectionInitialization.ContentPackageOrder:
pendingClient.InitializationStep = ConnectionInitialization.Success;
pendingClient.UpdateTime = Timing.TotalTime;
break;
}
}
protected abstract void ProcessAuthTicket(string name, int ownKey, ulong steamId, PendingClient pendingClient, byte[] ticket);
protected void BanPendingClient(PendingClient pendingClient, string banReason, TimeSpan? duration)
{
if (pendingClient.Connection is LidgrenConnection l)
{
serverSettings.BanList.BanPlayer(pendingClient.Name, l.NetConnection.RemoteEndPoint.Address, banReason, duration);
}
else if (pendingClient.Connection is SteamP2PConnection s)
{
serverSettings.BanList.BanPlayer(pendingClient.Name, s.SteamID, banReason, duration);
}
}
protected bool IsPendingClientBanned(PendingClient pendingClient, out string banReason)
{
if (pendingClient.Connection is LidgrenConnection l)
{
return serverSettings.BanList.IsBanned(l.NetConnection.RemoteEndPoint.Address, out banReason);
}
else if (pendingClient.Connection is SteamP2PConnection s)
{
return serverSettings.BanList.IsBanned(s.SteamID, out banReason);
}
banReason = null;
return false;
}
protected abstract void SendMsgInternal(NetworkConnection conn, DeliveryMethod deliveryMethod, IWriteMessage msg);
protected void UpdatePendingClient(PendingClient pendingClient)
{
if (IsPendingClientBanned(pendingClient, out string banReason))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
return;
}
if (connectedClients.Count >= serverSettings.MaxPlayers - 1)
{
RemovePendingClient(pendingClient, DisconnectReason.ServerFull, "");
}
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
{
NetworkConnection newConnection = pendingClient.Connection;
connectedClients.Add(newConnection);
pendingClients.Remove(pendingClient);
CheckOwnership(pendingClient);
OnInitializationComplete?.Invoke(newConnection);
}
pendingClient.TimeOut -= Timing.Step;
if (pendingClient.TimeOut < 0.0)
{
RemovePendingClient(pendingClient, DisconnectReason.Unknown, Lidgren.Network.NetConnection.NoResponseMessage);
}
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
pendingClient.UpdateTime = Timing.TotalTime + 1.0;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep |
PacketHeader.IsServerMessage));
outMsg.Write((byte)pendingClient.InitializationStep);
switch (pendingClient.InitializationStep)
{
case ConnectionInitialization.ContentPackageOrder:
outMsg.Write(GameMain.Server.ServerName);
var mpContentPackages = GameMain.Config.AllEnabledPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).ToList();
outMsg.WriteVariableUInt32((UInt32)mpContentPackages.Count);
for (int i = 0; i < mpContentPackages.Count; i++)
{
outMsg.Write(mpContentPackages[i].Name);
outMsg.Write(mpContentPackages[i].MD5hash.Hash);
outMsg.Write(mpContentPackages[i].SteamWorkshopId);
}
break;
case ConnectionInitialization.Password:
outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits();
if (pendingClient.PasswordSalt == null)
{
pendingClient.PasswordSalt = Lidgren.Network.CryptoRandom.Instance.Next();
outMsg.Write(pendingClient.PasswordSalt.Value);
}
else
{
outMsg.Write(pendingClient.Retries);
}
break;
}
SendMsgInternal(pendingClient.Connection, DeliveryMethod.Reliable, outMsg);
}
protected virtual void CheckOwnership(PendingClient pendingClient) { }
protected void RemovePendingClient(PendingClient pendingClient, DisconnectReason reason, string msg)
{
if (pendingClients.Contains(pendingClient))
{
Disconnect(pendingClient.Connection, reason + "/" + msg);
pendingClients.Remove(pendingClient);
if (pendingClient.AuthSessionStarted)
{
Steam.SteamManager.StopAuthSession(pendingClient.SteamID.Value);
pendingClient.SteamID = null;
pendingClient.AuthSessionStarted = false;
}
}
}
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod);
public abstract void Disconnect(NetworkConnection conn, string msg = null);
@@ -10,50 +10,17 @@ namespace Barotrauma.Networking
{
private bool started;
private ServerSettings serverSettings;
public UInt64 OwnerSteamID
{
get;
private set;
}
private class PendingClient
{
public string Name;
public ConnectionInitialization InitializationStep;
public double UpdateTime;
public double TimeOut;
public int Retries;
public UInt64 SteamID;
public Int32? PasswordSalt;
public bool AuthSessionStarted;
public PendingClient(UInt64 steamId)
{
InitializationStep = ConnectionInitialization.SteamTicketAndVersion;
Retries = 0;
SteamID = steamId;
PasswordSalt = null;
UpdateTime = Timing.TotalTime+Timing.Step*3.0;
TimeOut = NetworkConnection.TimeoutThreshold;
AuthSessionStarted = false;
}
public void Heartbeat()
{
TimeOut = NetworkConnection.TimeoutThreshold;
}
}
private List<SteamP2PConnection> connectedClients;
private List<PendingClient> pendingClients;
public SteamP2PServerPeer(UInt64 steamId, ServerSettings settings)
{
serverSettings = settings;
connectedClients = new List<SteamP2PConnection>();
connectedClients = new List<NetworkConnection>();
pendingClients = new List<PendingClient>();
ownerKey = null;
@@ -114,10 +81,11 @@ namespace Barotrauma.Networking
//backwards for loop so we can remove elements while iterating
for (int i = connectedClients.Count - 1; i >= 0; i--)
{
connectedClients[i].Decay(deltaTime);
if (connectedClients[i].Timeout < 0.0)
SteamP2PConnection conn = connectedClients[i] as SteamP2PConnection;
conn.Decay(deltaTime);
if (conn.Timeout < 0.0)
{
Disconnect(connectedClients[i], "Timed out");
Disconnect(conn, "Timed out");
}
}
@@ -172,7 +140,7 @@ namespace Barotrauma.Networking
if (senderSteamId != OwnerSteamID) //sender is remote, handle disconnects and heartbeats
{
PendingClient pendingClient = pendingClients.Find(c => c.SteamID == senderSteamId);
SteamP2PConnection connectedClient = connectedClients.Find(c => c.SteamID == senderSteamId);
SteamP2PConnection connectedClient = connectedClients.Find(c => c.SteamID == senderSteamId) as SteamP2PConnection;
pendingClient?.Heartbeat();
connectedClient?.Heartbeat();
@@ -210,6 +178,7 @@ namespace Barotrauma.Networking
}
else if (isConnectionInitializationStep)
{
if (pendingClient != null)
{
ReadConnectionInitializationStep(pendingClient, new ReadOnlyMessage(inc.Buffer, false, inc.BytePosition, inc.LengthBytes - inc.BytePosition, null));
@@ -219,7 +188,7 @@ namespace Barotrauma.Networking
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
if (initializationStep == ConnectionInitialization.ConnectionStarted)
{
pendingClients.Add(new PendingClient(senderSteamId));
pendingClients.Add(new PendingClient(new SteamP2PConnection("PENDING", senderSteamId)) { SteamID = senderSteamId });
}
}
}
@@ -252,7 +221,7 @@ namespace Barotrauma.Networking
string ownerName = inc.ReadString();
OwnerConnection = new SteamP2PConnection(ownerName, OwnerSteamID)
{
Status = NetworkConnectionStatus.Connected
Language = GameMain.Config.Language
};
OnInitializationComplete?.Invoke(OwnerConnection);
@@ -273,246 +242,6 @@ namespace Barotrauma.Networking
}
}
private void ReadConnectionInitializationStep(PendingClient pendingClient, IReadMessage inc)
{
if (!started) { return; }
pendingClient.TimeOut = NetworkConnection.TimeoutThreshold;
ConnectionInitialization initializationStep = (ConnectionInitialization)inc.ReadByte();
//DebugConsole.NewMessage(initializationStep+" "+pendingClient.InitializationStep);
if (pendingClient.InitializationStep != initializationStep) return;
pendingClient.UpdateTime = Timing.TotalTime+Timing.Step;
switch (initializationStep)
{
case ConnectionInitialization.SteamTicketAndVersion:
string name = Client.SanitizeName(inc.ReadString());
UInt64 steamId = inc.ReadUInt64();
UInt16 ticketLength = inc.ReadUInt16();
inc.BitPosition += ticketLength * 8; //skip ticket, owner handles steam authentication
if (!Client.IsValidName(name, serverSettings))
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidName, "The name \"" + name + "\" is invalid");
return;
}
string version = inc.ReadString();
bool isCompatibleVersion = NetworkMember.IsCompatible(version, GameMain.Version.ToString()) ?? false;
if (!isCompatibleVersion)
{
RemovePendingClient(pendingClient, DisconnectReason.InvalidVersion,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version}~[clientversion]={version}");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
return;
}
Client nameTaken = GameMain.Server.ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), name.ToLower()));
if (nameTaken != null)
{
RemovePendingClient(pendingClient, DisconnectReason.NameTaken, "");
GameServer.Log(name + " (" + pendingClient.SteamID.ToString() + ") couldn't join the server (name too similar to the name of the client \"" + nameTaken.Name + "\").", ServerLog.MessageType.Error);
return;
}
int contentPackageCount = (int)inc.ReadVariableUInt32();
List<ClientContentPackage> clientContentPackages = new List<ClientContentPackage>();
for (int i = 0; i < contentPackageCount; i++)
{
string packageName = inc.ReadString();
string packageHash = inc.ReadString();
clientContentPackages.Add(new ClientContentPackage(packageName, packageHash));
}
//check if the client is missing any of our packages
List<ContentPackage> missingPackages = new List<ContentPackage>();
foreach (ContentPackage serverContentPackage in GameMain.SelectedPackages)
{
if (!serverContentPackage.HasMultiplayerIncompatibleContent) continue;
bool packageFound = clientContentPackages.Any(cp => cp.Name == serverContentPackage.Name && cp.Hash == serverContentPackage.MD5hash.Hash);
if (!packageFound) { missingPackages.Add(serverContentPackage); }
}
//check if the client is using packages we don't have
List<ClientContentPackage> redundantPackages = new List<ClientContentPackage>();
foreach (ClientContentPackage clientContentPackage in clientContentPackages)
{
bool packageFound = GameMain.SelectedPackages.Any(cp => cp.Name == clientContentPackage.Name && cp.MD5hash.Hash == clientContentPackage.Hash);
if (!packageFound) { redundantPackages.Add(clientContentPackage); }
}
if (missingPackages.Count == 1)
{
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
$"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
else if (missingPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
RemovePendingClient(pendingClient, DisconnectReason.MissingContentPackage,
$"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}");
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
if (redundantPackages.Count == 1)
{
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr(redundantPackages[0])}");
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (using an incompatible content package " + GetPackageStr(redundantPackages[0]) + ")", ServerLog.MessageType.Error);
return;
}
if (redundantPackages.Count > 1)
{
List<string> packageStrs = new List<string>();
redundantPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
RemovePendingClient(pendingClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}");
GameServer.Log(name + " (" + pendingClient.SteamID + ") couldn't join the server (using incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return;
}
if (!pendingClient.AuthSessionStarted)
{
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
pendingClient.Name = name;
pendingClient.AuthSessionStarted = true;
}
break;
case ConnectionInitialization.Password:
int pwLength = inc.ReadByte();
byte[] incPassword = inc.ReadBytes(pwLength);
if (pendingClient.PasswordSalt == null)
{
DebugConsole.ThrowError("Received password message from client without salt");
return;
}
if (serverSettings.IsPasswordCorrect(incPassword, pendingClient.PasswordSalt.Value))
{
pendingClient.InitializationStep = ConnectionInitialization.ContentPackageOrder;
}
else
{
pendingClient.Retries++;
if (serverSettings.BanAfterWrongPassword && pendingClient.Retries > serverSettings.MaxPasswordRetriesBeforeBan)
{
string banMsg = "Failed to enter correct password too many times";
serverSettings.BanList.BanPlayer(pendingClient.Name, pendingClient.SteamID, banMsg, null);
RemovePendingClient(pendingClient, DisconnectReason.Banned, banMsg);
return;
}
}
pendingClient.UpdateTime = Timing.TotalTime;
break;
case ConnectionInitialization.ContentPackageOrder:
pendingClient.InitializationStep = ConnectionInitialization.Success;
pendingClient.UpdateTime = Timing.TotalTime;
break;
}
}
private void UpdatePendingClient(PendingClient pendingClient)
{
if (!started) { return; }
if (serverSettings.BanList.IsBanned(pendingClient.SteamID, out string banReason))
{
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
return;
}
//DebugConsole.NewMessage("pending client status: " + pendingClient.InitializationStep);
if (connectedClients.Count >= serverSettings.MaxPlayers - 1)
{
RemovePendingClient(pendingClient, DisconnectReason.ServerFull, "");
}
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
{
SteamP2PConnection newConnection = new SteamP2PConnection(pendingClient.Name, pendingClient.SteamID)
{
Status = NetworkConnectionStatus.Connected
};
connectedClients.Add(newConnection);
pendingClients.Remove(pendingClient);
OnInitializationComplete?.Invoke(newConnection);
}
pendingClient.TimeOut -= Timing.Step;
if (pendingClient.TimeOut < 0.0)
{
RemovePendingClient(pendingClient, DisconnectReason.Unknown, Lidgren.Network.NetConnection.NoResponseMessage);
}
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
pendingClient.UpdateTime = Timing.TotalTime + 1.0;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(pendingClient.SteamID);
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep |
PacketHeader.IsServerMessage));
outMsg.Write((byte)pendingClient.InitializationStep);
switch (pendingClient.InitializationStep)
{
case ConnectionInitialization.ContentPackageOrder:
var mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent).ToList();
outMsg.WriteVariableUInt32((UInt32)mpContentPackages.Count);
for (int i = 0; i < mpContentPackages.Count; i++)
{
outMsg.Write(mpContentPackages[i].MD5hash.Hash);
}
break;
case ConnectionInitialization.Password:
outMsg.Write(pendingClient.PasswordSalt == null); outMsg.WritePadBits();
if (pendingClient.PasswordSalt == null)
{
pendingClient.PasswordSalt = Lidgren.Network.CryptoRandom.Instance.Next();
outMsg.Write(pendingClient.PasswordSalt.Value);
}
else
{
outMsg.Write(pendingClient.Retries);
}
break;
}
byte[] msgToSend = (byte[])outMsg.Buffer.Clone();
Array.Resize(ref msgToSend, outMsg.LengthBytes);
ChildServerRelay.Write(msgToSend);
}
private void RemovePendingClient(PendingClient pendingClient, DisconnectReason reason, string msg)
{
if (!started) { return; }
if (pendingClients.Contains(pendingClient))
{
SendDisconnectMessage(pendingClient.SteamID, reason + "/" + msg);
pendingClients.Remove(pendingClient);
if (pendingClient.AuthSessionStarted)
{
Steam.SteamManager.StopAuthSession(pendingClient.SteamID);
pendingClient.SteamID = 0;
pendingClient.AuthSessionStarted = false;
}
}
}
public override void InitializeSteamServerCallbacks()
{
throw new InvalidOperationException("Called InitializeSteamServerCallbacks on SteamP2PServerPeer!");
@@ -582,5 +311,25 @@ namespace Barotrauma.Networking
{
Disconnect(conn, msg, true);
}
protected override void SendMsgInternal(NetworkConnection conn, DeliveryMethod deliveryMethod, IWriteMessage msg)
{
IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.Write(conn.SteamID);
msgToSend.Write((byte)deliveryMethod);
msgToSend.Write(msg.Buffer, 0, msg.LengthBytes);
byte[] bufToSend = (byte[])msgToSend.Buffer.Clone();
Array.Resize(ref bufToSend, msgToSend.LengthBytes);
ChildServerRelay.Write(bufToSend);
}
protected override void ProcessAuthTicket(string name, int ownKey, ulong steamId, PendingClient pendingClient, byte[] ticket)
{
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
pendingClient.Connection.Name = name;
pendingClient.Name = name;
pendingClient.AuthSessionStarted = true;
}
}
}