From 5c343caeafcdf72c771b0f92e3f4b71886a0f4f0 Mon Sep 17 00:00:00 2001 From: alzeih Date: Sat, 8 Jan 2011 00:25:30 +1300 Subject: [PATCH] Mod version validation - Game.CurrentMods property to query the current mods when no orderManager accessible - Server sends mod versions to master server on ping - Client sends mod versions on handshake response - Validate match on server side of handshake, not client side --- OpenRA.Game/Game.cs | 5 +++ OpenRA.Game/Network/UnitOrders.cs | 32 +------------------ OpenRA.Game/Server/Server.cs | 20 ++++++++++-- .../ServerTraits/MasterServerPinger.cs | 31 +++++++++--------- .../Delegates/ServerBrowserDelegate.cs | 19 +++++++++-- 5 files changed, 57 insertions(+), 50 deletions(-) diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 947ed35e19..bf4fdf8ef9 100755 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -194,6 +194,11 @@ namespace OpenRA { get { return orderManager.Connection.LocalClientId == 0; } } + + public static Dictionary CurrentMods + { + get { return Mod.AllMods.Where( k => orderManager.LobbyInfo.GlobalSettings.Mods.Contains( k.Key )).ToDictionary( k => k.Key, k => k.Value ); } + } static Modifiers modifiers; public static Modifiers GetModifierKeys() { return modifiers; } diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index ba92e895c5..6983857d43 100755 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -103,37 +103,6 @@ namespace OpenRA.Network { var request = HandshakeRequest.Deserialize(order.TargetString); - // Check valid mods/versions - var serverMods = request.Mods; - var localMods = orderManager.LobbyInfo.GlobalSettings.Mods; - - bool valid = true; - if (localMods.Length != serverMods.Length) - valid = false; - else - foreach (var m in serverMods) - { - var parts = m.Split('@'); - if (!localMods.Contains(parts[0])) - { - valid = false; - break; - } - if (parts[1] == "{DEV_VERSION}" || Mod.AllMods[parts[0]].Version == "{DEV_VERSION}") - continue; - - if (parts[1] != Mod.AllMods[parts[0]].Version) - { - valid = false; - break; - } - } - - if (!valid) - throw new InvalidOperationException("Mod/Version mismatch. Client: `{0}`, Server: `{1}`".F( - string.Join(",",localMods.Select(m => "{0}@{1}".F(m, Mod.AllMods[m].Version)).ToArray()), - string.Join(",",serverMods))); - // Check that the map exists on the client if (!Game.modData.AvailableMaps.ContainsKey(request.Map)) throw new InvalidOperationException("Missing map {0}".F(request.Map)); @@ -149,6 +118,7 @@ namespace OpenRA.Network State = Session.ClientState.NotReady }; + var localMods = orderManager.LobbyInfo.GlobalSettings.Mods.Select(m => "{0}@{1}".F(m,Mod.AllMods[m].Version)).ToArray(); var response = new HandshakeResponse() { Client = info, diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index 0a5ebeca2a..4b8abe8504 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -188,8 +188,24 @@ namespace OpenRA.Server DropClient(newConn); return; } - - var client = HandshakeResponse.Deserialize(data).Client; + + var handshake = HandshakeResponse.Deserialize(data); + var client = handshake.Client; + var mods = handshake.Mods; + + // Check that the client has compatable mods + + var valid = mods.All( m => m.Contains('@')) && //valid format + mods.Count() == Game.CurrentMods.Count() && //same number + mods.Select( m => Pair.New(m.Split('@')[0], m.Split('@')[1])).All(kv => Game.CurrentMods.ContainsKey(kv.First) && + (kv.Second == "{DEV_VERSION}" || Game.CurrentMods[kv.First].Version == "{DEV_VERSION}" || kv.Second == Game.CurrentMods[kv.First].Version)); + if (!valid) + { + Log.Write("server", "Rejected connection from {0}; mods do not match.", + newConn.socket.RemoteEndPoint); + DropClient(newConn); + return; + } // Promote connection to a valid client preConns.Remove(newConn); diff --git a/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs b/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs index 1c0bfcf8f3..db2d515926 100644 --- a/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs +++ b/OpenRA.Mods.RA/ServerTraits/MasterServerPinger.cs @@ -13,13 +13,14 @@ using System.Collections.Generic; using System.Net; using OpenRA.Server; using S = OpenRA.Server.Server; +using System.Linq; namespace OpenRA.Mods.RA.Server { public class MasterServerPinger : ServerTrait, ITick, INotifySyncLobbyInfo, IStartGame { const int MasterPingInterval = 60 * 3; // 3 minutes. server has a 5 minute TTL for games, so give ourselves a bit - // of leeway. + // of leeway. public int TickTimeout { get { return MasterPingInterval * 10000; } } public void Tick(S server) { @@ -29,10 +30,10 @@ namespace OpenRA.Mods.RA.Server lock (masterServerMessages) while (masterServerMessages.Count > 0) server.SendChat(null, masterServerMessages.Dequeue()); - + } - - + + public void LobbyInfoSynced(S server) { PingMasterServer(server); } public void GameStarted(S server) { PingMasterServer(server); } @@ -42,7 +43,7 @@ namespace OpenRA.Mods.RA.Server static string masterServerUrl = Game.Settings.Server.MasterServer; static int externalPort = Game.Settings.Server.ExternalPort; static bool isInitialPing = true; - + static volatile bool isBusy; static Queue masterServerMessages = new Queue(); public static void PingMasterServer(S server) @@ -62,13 +63,13 @@ namespace OpenRA.Mods.RA.Server using (var wc = new WebClient()) { wc.Proxy = null; - wc.DownloadData( - masterServerUrl + url.F( - externalPort, Uri.EscapeUriString(server.Name), - server.GameStarted ? 2 : 1, // todo: post-game states, etc. - server.lobbyInfo.Clients.Count, - string.Join(",", server.lobbyInfo.GlobalSettings.Mods), - server.lobbyInfo.GlobalSettings.Map)); + wc.DownloadData( + masterServerUrl + url.F( + externalPort, Uri.EscapeUriString(server.Name), + server.GameStarted ? 2 : 1, // todo: post-game states, etc. + server.lobbyInfo.Clients.Count, + string.Join(",", Game.CurrentMods.Select(f => "{0}@{1}".F(f.Key, f.Value.Version)).ToArray()), + server.lobbyInfo.GlobalSettings.Map)); if (isInitialPing) { @@ -78,11 +79,11 @@ namespace OpenRA.Mods.RA.Server } } } - catch (Exception ex) + catch(Exception ex) { Log.Write("server", ex.ToString()); - lock (masterServerMessages) - masterServerMessages.Enqueue("Master server communication failed."); + lock( masterServerMessages ) + masterServerMessages.Enqueue( "Master server communication failed." ); } isBusy = false; diff --git a/OpenRA.Mods.RA/Widgets/Delegates/ServerBrowserDelegate.cs b/OpenRA.Mods.RA/Widgets/Delegates/ServerBrowserDelegate.cs index 560a09218b..74244d9895 100644 --- a/OpenRA.Mods.RA/Widgets/Delegates/ServerBrowserDelegate.cs +++ b/OpenRA.Mods.RA/Widgets/Delegates/ServerBrowserDelegate.cs @@ -149,8 +149,7 @@ namespace OpenRA.Mods.RA.Widgets.Delegates return; } - // only "waiting for players" - var gamesWaiting = games.Where(g => g.State == 1); + var gamesWaiting = games.Where(g => CanJoin(g)); if (gamesWaiting.Count() == 0) { @@ -179,6 +178,22 @@ namespace OpenRA.Mods.RA.Widgets.Delegates i++; } } + + bool CanJoin(GameServer game) + { + //"waiting for players" + if (game.State != 1) + return false; + + // Mods won't match if there are a different number + if (Game.CurrentMods.Count != game.Mods.Count()) + return false; + + return game.Mods.All( m => m.Contains('@')) && game.Mods.Select( m => Pair.New(m.Split('@')[0], m.Split('@')[1])) + .All(kv => Game.CurrentMods.ContainsKey(kv.First) && + (kv.Second == "{DEV_VERSION}" || Game.CurrentMods[kv.First].Version == "{DEV_VERSION}" || kv.Second == Game.CurrentMods[kv.First].Version)); + } + } public class DirectConnectDelegate : IWidgetDelegate