]> git.deb.at Git - rhonda/impostor.git/commitdiff
Added server redirection using redis
authorAeonLucid <aeonlucid@gmail.com>
Sat, 26 Sep 2020 03:43:37 +0000 (05:43 +0200)
committerAeonLucid <aeonlucid@gmail.com>
Sat, 26 Sep 2020 03:43:37 +0000 (05:43 +0200)
27 files changed:
.github/workflows/deploy.yml
.github/workflows/main.yml
src/.gitignore
src/Impostor.Server/Data/DisconnectMessages.cs
src/Impostor.Server/Data/ServerConfig.cs
src/Impostor.Server/Data/ServerRedirectorConfig.cs [new file with mode: 0644]
src/Impostor.Server/Data/ServerRedirectorNode.cs [new file with mode: 0644]
src/Impostor.Server/Impostor.Server.csproj
src/Impostor.Server/Net/Client.cs
src/Impostor.Server/Net/Manager/ClientManager.cs
src/Impostor.Server/Net/Manager/GameManager.cs
src/Impostor.Server/Net/Manager/IClientManager.cs [new file with mode: 0644]
src/Impostor.Server/Net/Matchmaker.cs
src/Impostor.Server/Net/MatchmakerService.cs
src/Impostor.Server/Net/Messages/Message13Redirect.cs [new file with mode: 0644]
src/Impostor.Server/Net/Redirector/ClientManagerRedirector.cs [new file with mode: 0644]
src/Impostor.Server/Net/Redirector/ClientRedirector.cs [new file with mode: 0644]
src/Impostor.Server/Net/Redirector/INodeProvider.cs [new file with mode: 0644]
src/Impostor.Server/Net/Redirector/NodeProviderNoOp.cs [new file with mode: 0644]
src/Impostor.Server/Net/Redirector/NodeProviderRedis.cs [new file with mode: 0644]
src/Impostor.Server/Net/State/ClientPlayer.Events.cs
src/Impostor.Server/Net/State/ClientPlayer.cs
src/Impostor.Server/Net/State/Game.Incoming.cs
src/Impostor.Server/Net/State/Game.Outgoing.cs
src/Impostor.Server/Net/State/Game.cs
src/Impostor.Server/Program.cs
src/Impostor.Server/config.json

index e9a690cd705fb8f995136c3c7f203a52dc18e6ad..e4720ac823add45214ecbcb0f2f9c48c192acaeb 100644 (file)
@@ -19,7 +19,7 @@ jobs:
       - name: Setup .NET Core
         uses: actions/setup-dotnet@v1
         with:
-          dotnet-version: 3.1.301
+          dotnet-version: 3.1.402
       - name: Install dependencies
         run: dotnet restore ./src
       - name: Build Impostor.Client
index d51ecae2d7a3116fca1f65e9b1b76fb574ad9d58..8d14e710bc6dce3d747b962940bb3ea24c22dff1 100644 (file)
@@ -26,7 +26,7 @@ jobs:
       - name: Setup .NET Core
         uses: actions/setup-dotnet@v1
         with:
-          dotnet-version: 3.1.301
+          dotnet-version: 3.1.402
       - name: Install dependencies
         run: dotnet restore ./src
       - name: Build Impostor.Client
@@ -35,13 +35,3 @@ jobs:
         run: dotnet build --no-restore -c Release -f netcoreapp3.1 ./src/Impostor.Server/Impostor.Server.csproj /p:PublishTrimmed=false
       - name: Test
         run: dotnet test --no-restore -v normal -f netcoreapp3.1 ./src
-  deploy:
-    name: Deploy
-    runs-on: windows-latest
-    needs: build
-    if: github.event_name == 'release'
-    steps:
-      - name: Publish Impostor.Server (win-x64)
-        run: dotnet publish -c release -o /build/linux-x64 -f netcoreapp3.1 -r win-x64 --self-contained --no-restore ./src/Impostor.Server/Impostor.Server.csproj
-      - name: Publish Impostor.Server (linux-x64)
-        run: dotnet publish -c release -o /build/linux-x64 -f netcoreapp3.1 -r linux-x64 --self-contained --no-restore ./src/Impostor.Server/Impostor.Server.csproj
\ No newline at end of file
index 3c4efe206bd0e7230ad0ae8396a3c883c8207906..e0838bae98cb7d60d4df5a849ac9424cc24dca0e 100644 (file)
@@ -1,3 +1,5 @@
+config.*.json
+
 ## Ignore Visual Studio temporary files, build results, and
 ## files generated by popular Visual Studio add-ons.
 
index 1c1617380125627e5790869822b41866c59adc3b..50dc00f5a47738bb86c1b31f3d71924e3c4a2d7d 100644 (file)
@@ -8,5 +8,8 @@
 
         public const string Destroyed = "The game you tried to join is being destroyed. " +
                                         "Please create a new game.";
+
+        public const string NotImplemented = "Game listing has not been implemented in Impostor yet for servers " +
+                                             "running in server redirection mode.";
     }
 }
\ No newline at end of file
index 007b5edf69a66609c9846370cbcb5c0c53d1a7d4..5bcf68df3b2d523532758ab1ca62fffbb9d2e46b 100644 (file)
@@ -1,7 +1,9 @@
 namespace Impostor.Server.Data
 {
-    public class ServerConfig
+    internal class ServerConfig
     {
+        public const string Section = "Server"; 
+        
         public string PublicIp { get; set; } = "127.0.0.1";
         public ushort PublicPort { get; set; } = 22023;
         public string ListenIp { get; set; } = "127.0.0.1";
diff --git a/src/Impostor.Server/Data/ServerRedirectorConfig.cs b/src/Impostor.Server/Data/ServerRedirectorConfig.cs
new file mode 100644 (file)
index 0000000..cd5b4d7
--- /dev/null
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace Impostor.Server.Data
+{
+    internal class ServerRedirectorConfig
+    {
+        public const string Section = "ServerRedirector";
+        
+        public bool Enabled { get; set; }
+        public bool Master { get; set; }
+        public string Redis { get; set; }
+        public List<ServerRedirectorNode> Nodes { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Data/ServerRedirectorNode.cs b/src/Impostor.Server/Data/ServerRedirectorNode.cs
new file mode 100644 (file)
index 0000000..8da03ad
--- /dev/null
@@ -0,0 +1,8 @@
+namespace Impostor.Server.Data
+{
+    internal class ServerRedirectorNode
+    {
+        public string Ip { get; set; }
+        public ushort Port { get; set; }
+    }
+}
\ No newline at end of file
index 003e6364c4c30277f2f0eb7b503188230be6fb51..0387f844c0e7b3a6e57d317698e579e75b25654a 100644 (file)
@@ -18,6 +18,7 @@
     </ItemGroup>
 
     <ItemGroup>
+      <PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="3.1.8" />
       <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.8" />
       <PackageReference Include="Serilog.Extensions.Hosting" Version="3.1.0" />
       <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
@@ -27,6 +28,9 @@
       <None Update="config.json">
         <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       </None>
+      <None Update="config.*.json">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </None>
     </ItemGroup>
 
 </Project>
index f171a74515fe7accc02e0e1166523f27211c5ccc..f949134ca709198c52ee725d6ea3cbae5cbad053 100644 (file)
@@ -1,4 +1,4 @@
-using System;
+using System;
 using Hazel;
 using Impostor.Server.Data;
 using Impostor.Server.Net.Manager;
@@ -10,7 +10,7 @@ using ILogger = Serilog.ILogger;
 
 namespace Impostor.Server.Net
 {
-    public class Client
+    internal class Client
     {
         private static readonly ILogger Logger = Log.ForContext<Client>();
         
index b4f7684f175227bcb512633a989361ba679f776a..28298c5da634d5f9011699248c95168ba3c359f9 100644 (file)
@@ -1,26 +1,29 @@
 using System.Collections.Concurrent;
+using Hazel;
 using Impostor.Server.Exceptions;
-using Serilog;
+using Microsoft.Extensions.Logging;
 
 namespace Impostor.Server.Net.Manager
 {
-    public class ClientManager
+    internal class ClientManager : IClientManager
     {
-        private static readonly ILogger Logger = Log.ForContext<ClientManager>();
-        
+        private readonly ILogger<ClientManager> _clientManager;
+        private readonly GameManager _gameManager;
         private readonly ConcurrentDictionary<int, Client> _clients;
         private readonly object _idLock;
         private int _idLast;
         
-        public ClientManager()
+        public ClientManager(ILogger<ClientManager> clientManager, GameManager gameManager)
         {
+            _clientManager = clientManager;
+            _gameManager = gameManager;
             _clients = new ConcurrentDictionary<int, Client>();
             _idLock = new object();
             _idLast = 0;
         }
 
         // No idea what a good way for this is.
-        public int NextId()
+        private int NextId()
         {
             lock (_idLock)
             {
@@ -35,7 +38,7 @@ namespace Impostor.Server.Net.Manager
                         _idLast = 0;
                     }
 
-                    if (_clients.ContainsKey(_idLast))
+                    if (_clients.ContainsKey(result))
                     {
                         continue;
                     }
@@ -47,17 +50,17 @@ namespace Impostor.Server.Net.Manager
             }
         }
         
-        public void Add(Client client)
+        public void Create(string name, Connection connection)
         {
-            Logger.Information("Client connected.");
-
-            _clients.TryAdd(client.Id, client);
+            var clientId = NextId();
+            
+            _clientManager.LogInformation("Client connected.");
+            _clients.TryAdd(clientId, new Client(this, _gameManager, clientId, name, connection));
         }
 
         public void Remove(Client client)
         {
-            Logger.Information("Client disconnected.");
-
+            _clientManager.LogInformation("Client disconnected.");
             _clients.TryRemove(client.Id, out _);
         }
     }
index 291b9d2078983edae7d93cc6b7854bb79fac5788..7c0db53d36da1c31115dd37c532e495a644ba349 100644 (file)
@@ -1,8 +1,9 @@
-using System.Collections.Concurrent;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 using System.Net;
 using Impostor.Server.Data;
+using Impostor.Server.Net.Redirector;
 using Impostor.Server.Net.State;
 using Impostor.Shared.Innersloth;
 using Impostor.Shared.Innersloth.Data;
@@ -11,26 +12,33 @@ using Microsoft.Extensions.Options;
 
 namespace Impostor.Server.Net.Manager
 {
-    public class GameManager
+    internal class GameManager
     {
         private readonly ILogger<GameManager> _logger;
+        private readonly INodeProvider _nodeProvider;
         private readonly IPEndPoint _publicIp;
         private readonly ConcurrentDictionary<int, Game> _games;
 
-        public GameManager(ILogger<GameManager> logger, IOptions<ServerConfig> config)
+        public GameManager(ILogger<GameManager> logger, IOptions<ServerConfig> config, INodeProvider nodeProvider)
         {
             _logger = logger;
+            _nodeProvider = nodeProvider;
             _publicIp = new IPEndPoint(IPAddress.Parse(config.Value.PublicIp), config.Value.PublicPort);
             _games = new ConcurrentDictionary<int, Game>();
         }
         
         public Game Create(GameOptionsData options)
         {
+            // TODO: Prevent duplicates when using server redirector using INodeProvider.
+            
             var gameCode = GameCode.GenerateCode(6);
-            var game = new Game(this, _publicIp, gameCode, options);
+            var gameCodeStr = GameCode.IntToGameName(gameCode);
+            var game = new Game(this, _nodeProvider, _publicIp, gameCode, options);
 
-            if (_games.TryAdd(gameCode, game))
+            if (_nodeProvider.Find(gameCodeStr) == null && 
+                _games.TryAdd(gameCode, game))
             {
+                _nodeProvider.Save(gameCodeStr, _publicIp);
                 _logger.LogDebug("Created game with code {0} ({1}).", game.CodeStr, gameCode);        
                 return game;
             }
@@ -53,7 +61,7 @@ namespace Impostor.Server.Net.Manager
             foreach (var (code, game) in _games.Where(x => 
                 x.Value.IsPublic &&
                 x.Value.GameState == GameStates.NotStarted && 
-                x.Value.PlayerCount < 10))
+                x.Value.PlayerCount < 10)) // TODO: Do "< x.Value.Options.MaxPlayers" when GameData packets are done.
             {
                 // Check for options.
                 // TODO: Re-enable map filter when GameData packets are done.
@@ -77,7 +85,8 @@ namespace Impostor.Server.Net.Manager
 
         public void Remove(int gameCode)
         {
-            _logger.LogDebug("Remove game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode);   
+            _logger.LogDebug("Remove game with code {0} ({1}).", GameCode.IntToGameName(gameCode), gameCode);
+            _nodeProvider.Remove(GameCode.IntToGameName(gameCode));
             _games.TryRemove(gameCode, out _);
         }
     }
diff --git a/src/Impostor.Server/Net/Manager/IClientManager.cs b/src/Impostor.Server/Net/Manager/IClientManager.cs
new file mode 100644 (file)
index 0000000..4449e2d
--- /dev/null
@@ -0,0 +1,9 @@
+using Hazel;
+
+namespace Impostor.Server.Net.Manager
+{
+    internal interface IClientManager
+    {
+        void Create(string name, Connection connection);
+    }
+}
\ No newline at end of file
index b7bdb3294518efdbe7488855bbdddadc36630621..46759e50d81b08c1e56e421dbc0b673c3d932ee1 100644 (file)
@@ -10,21 +10,22 @@ using Microsoft.Extensions.Options;
 
 namespace Impostor.Server.Net
 {
-    public class Matchmaker
+    internal class Matchmaker
     {
         private readonly ILogger<Matchmaker> _logger;
-        private readonly ServerConfig _config;
-        private readonly GameManager _gameManager;
-        private readonly ClientManager _clientManager;
+        private readonly ServerConfig _serverConfig;
+        private readonly IClientManager _clientManager;
         private readonly UdpConnectionListener _connection;
         
-        public Matchmaker(ILogger<Matchmaker> logger, IOptions<ServerConfig> configOptions, GameManager gameManager)
+        public Matchmaker(
+            ILogger<Matchmaker> logger, 
+            IOptions<ServerConfig> serverConfig, 
+            IClientManager clientManager)
         {
             _logger = logger;
-            _config = configOptions.Value;
-            _gameManager = gameManager;
-            _clientManager = new ClientManager();
-            _connection = new UdpConnectionListener(new IPEndPoint(IPAddress.Parse(_config.ListenIp), _config.ListenPort), IPMode.IPv4, s =>
+            _serverConfig = serverConfig.Value;
+            _clientManager = clientManager;
+            _connection = new UdpConnectionListener(new IPEndPoint(IPAddress.Parse(_serverConfig.ListenIp), _serverConfig.ListenPort), IPMode.IPv4, s =>
             {
                 _logger.LogWarning("Log from Hazel: {0}", s);
             });
@@ -52,8 +53,8 @@ namespace Impostor.Server.Net
                 return;
             }
             
-            // Register client.
-            _clientManager.Add(new Client(_clientManager, _gameManager, _clientManager.NextId(), clientName, e.Connection));
+            // Create client.
+            _clientManager.Create(clientName, e.Connection);
         }
 
         public void Start()
index 43fe21e985a9c9c183e2659f1143b6b70370ffda..08d0f1e919c65ff7d7b4332f85ef63203128ce72 100644 (file)
@@ -1,27 +1,47 @@
 using System.Threading;
 using System.Threading.Tasks;
+using Impostor.Server.Data;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
 
 namespace Impostor.Server.Net
 {
-    public class MatchmakerService : IHostedService
+    internal class MatchmakerService : IHostedService
     {
         private readonly ILogger<MatchmakerService> _logger;
+        private readonly ServerConfig _serverConfig;
+        private readonly ServerRedirectorConfig _redirectorConfig;
         private readonly Matchmaker _matchmaker;
 
-        public MatchmakerService(ILogger<MatchmakerService> logger, Matchmaker matchmaker)
+        public MatchmakerService(
+            ILogger<MatchmakerService> logger, 
+            IOptions<ServerConfig> serverConfig, 
+            IOptions<ServerRedirectorConfig> redirectorConfig,
+            Matchmaker matchmaker)
         {
             _logger = logger;
+            _serverConfig = serverConfig.Value;
+            _redirectorConfig = redirectorConfig.Value;
             _matchmaker = matchmaker;
         }
         
         public Task StartAsync(CancellationToken cancellationToken)
         {
             _matchmaker.Start();
-            _logger.LogInformation("Matchmaker is running on {0}:{1}.", 
+            
+            _logger.LogInformation("Matchmaker is listening on {0}:{1}, the public server ip is {2}:{3}.", 
                 _matchmaker.EndPoint.Address, 
-                _matchmaker.EndPoint.Port);
+                _matchmaker.EndPoint.Port,
+                _serverConfig.PublicIp, 
+                _serverConfig.PublicPort);
+
+            if (_redirectorConfig.Enabled)
+            {
+                _logger.LogWarning(_redirectorConfig.Master
+                    ? "Server redirection is enabled as master, this instance will redirect clients to other nodes."
+                    : "Server redirection is enabled as node, this instance will accept clients.");
+            }
             
             return Task.CompletedTask;
         }
diff --git a/src/Impostor.Server/Net/Messages/Message13Redirect.cs b/src/Impostor.Server/Net/Messages/Message13Redirect.cs
new file mode 100644 (file)
index 0000000..fe49c2d
--- /dev/null
@@ -0,0 +1,21 @@
+using System.Net;
+using Hazel;
+
+namespace Impostor.Server.Net.Messages
+{
+    internal static class Message13Redirect
+    {
+        public static void Serialize(MessageWriter writer, bool clear, IPEndPoint ipEndPoint)
+        {
+            if (clear)
+            {
+                writer.Clear(SendOption.Reliable);
+            }
+            
+            writer.StartMessage(MessageFlags.Redirect);
+            writer.Write(ipEndPoint.Address.GetAddressBytes());
+            writer.Write((ushort) ipEndPoint.Port);
+            writer.EndMessage();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Redirector/ClientManagerRedirector.cs b/src/Impostor.Server/Net/Redirector/ClientManagerRedirector.cs
new file mode 100644 (file)
index 0000000..f22a624
--- /dev/null
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using Hazel;
+using Impostor.Server.Net.Manager;
+using Microsoft.Extensions.Logging;
+
+namespace Impostor.Server.Net.Redirector
+{
+    internal class ClientManagerRedirector : IClientManager
+    {
+        private readonly ILogger<ClientManagerRedirector> _logger;
+        private readonly INodeProvider _nodeProvider;
+        private readonly HashSet<ClientRedirector> _clients;
+
+        public ClientManagerRedirector(ILogger<ClientManagerRedirector> logger, INodeProvider nodeProvider)
+        {
+            _logger = logger;
+            _nodeProvider = nodeProvider;
+            _clients = new HashSet<ClientRedirector>();
+        }
+        
+        public void Create(string name, Connection connection)
+        {
+            _logger.LogInformation("Client connected.");
+            _clients.Add(new ClientRedirector(name, connection, this, _nodeProvider));
+        }
+
+        public void Remove(ClientRedirector client)
+        {
+            _logger.LogInformation("Client disconnected.");
+            _clients.Remove(client);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Redirector/ClientRedirector.cs b/src/Impostor.Server/Net/Redirector/ClientRedirector.cs
new file mode 100644 (file)
index 0000000..ce85758
--- /dev/null
@@ -0,0 +1,116 @@
+using System;
+using Hazel;
+using Impostor.Server.Data;
+using Impostor.Server.Net.Messages;
+using Impostor.Shared.Innersloth;
+using Impostor.Shared.Innersloth.Data;
+using Serilog;
+using ILogger = Serilog.ILogger;
+
+namespace Impostor.Server.Net.Redirector
+{
+    internal class ClientRedirector
+    {
+        private static readonly ILogger Logger = Log.ForContext<ClientRedirector>();
+
+        private readonly string _name;
+        private readonly Connection _connection;
+        private readonly ClientManagerRedirector _clientManager;
+        private readonly INodeProvider _nodeProvider;
+
+        public ClientRedirector(string name, Connection connection, ClientManagerRedirector clientManager, INodeProvider nodeProvider)
+        {
+            _name = name;
+            _connection = connection;
+            _connection.DataReceived += OnDataReceived;
+            _connection.Disconnected += OnDisconnected;
+            _clientManager = clientManager;
+            _nodeProvider = nodeProvider;
+        }
+
+        private void OnDataReceived(DataReceivedEventArgs e)
+        {
+            try
+            {
+                while (true)
+                {
+                    if (e.Message.Position >= e.Message.Length)
+                    {
+                        break;
+                    }
+
+                    OnMessageReceived(e.Message.ReadMessage());
+                }
+            }
+            catch (Exception ex)
+            {
+                Logger.Error(ex, "Exception caught in client data handler.");
+            }
+        }
+
+        private void OnMessageReceived(MessageReader message)
+        {
+            var flag = message.Tag;
+            
+            Logger.Verbose("Server got {0}.", flag);
+
+            switch (flag)
+            {
+                case MessageFlags.HostGame:
+                {
+                    using (var packet = MessageWriter.Get(SendOption.Reliable))
+                    {
+                        Message13Redirect.Serialize(packet, false, _nodeProvider.Get());
+                        _connection.Send(packet);
+                    }
+                    break;
+                }
+
+                case MessageFlags.JoinGame:
+                {
+                    Message01JoinGame.Deserialize(message, 
+                        out var gameCode, 
+                        out var unknown);
+
+                    using (var packet = MessageWriter.Get(SendOption.Reliable))
+                    {
+                        var endpoint = _nodeProvider.Find(GameCode.IntToGameName(gameCode));
+                        if (endpoint == null)
+                        {
+                            Message01JoinGame.SerializeError(packet, false, DisconnectReason.GameMissing);
+                        }
+                        else
+                        {
+                            Message13Redirect.Serialize(packet, false, endpoint);
+                        }
+                        
+                        _connection.Send(packet);
+                    }
+                    break;
+                }
+
+                case MessageFlags.GetGameListV2:
+                {
+                    // TODO: Implement.
+                    using (var packet = MessageWriter.Get(SendOption.Reliable))
+                    {
+                        Message01JoinGame.SerializeError(packet, false, DisconnectReason.Custom, DisconnectMessages.NotImplemented);
+                        _connection.Send(packet);
+                    }
+                    break;
+                }
+
+                default:
+                {
+                    Logger.Warning("Received unsupported message flag on the redirector ({0}).", flag);
+                    break;
+                }
+            }
+        }
+
+        private void OnDisconnected(object sender, DisconnectedEventArgs e)
+        {
+            _clientManager.Remove(this);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Redirector/INodeProvider.cs b/src/Impostor.Server/Net/Redirector/INodeProvider.cs
new file mode 100644 (file)
index 0000000..37418e6
--- /dev/null
@@ -0,0 +1,12 @@
+using System.Net;
+
+namespace Impostor.Server.Net.Redirector
+{
+    internal interface INodeProvider
+    {
+        IPEndPoint Get();
+        IPEndPoint Find(string gameCode);
+        void Save(string gameCode, IPEndPoint endPoint);
+        void Remove(string gameCode);
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Redirector/NodeProviderNoOp.cs b/src/Impostor.Server/Net/Redirector/NodeProviderNoOp.cs
new file mode 100644 (file)
index 0000000..fa97968
--- /dev/null
@@ -0,0 +1,29 @@
+using System;
+using System.Net;
+
+namespace Impostor.Server.Net.Redirector
+{
+    public class NodeProviderNoOp : INodeProvider
+    {
+        public IPEndPoint Get()
+        {
+            throw new NotImplementedException();
+        }
+
+        public IPEndPoint Find(string gameCode)
+        {
+            // Do nothing.
+            return null;
+        }
+
+        public void Save(string gameCode, IPEndPoint endPoint)
+        {
+            // Do nothing.
+        }
+
+        public void Remove(string gameCode)
+        {
+            // Do nothing.
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Impostor.Server/Net/Redirector/NodeProviderRedis.cs b/src/Impostor.Server/Net/Redirector/NodeProviderRedis.cs
new file mode 100644 (file)
index 0000000..c1ef93c
--- /dev/null
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using Impostor.Server.Data;
+using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Options;
+
+namespace Impostor.Server.Net.Redirector
+{
+    internal class NodeProviderRedis : INodeProvider
+    {
+        private readonly IDistributedCache _cache;
+        private readonly List<IPEndPoint> _nodes;
+        private readonly object _lock;
+        private int _currentIndex;
+        
+        public NodeProviderRedis(IOptions<ServerRedirectorConfig> redirectorConfig, IDistributedCache cache)
+        {
+            _cache = cache;
+            _nodes = new List<IPEndPoint>();
+            _lock = new object();
+
+            foreach (var node in redirectorConfig.Value.Nodes)
+            {
+                _nodes.Add(new IPEndPoint(IPAddress.Parse(node.Ip), node.Port));
+            }
+        }
+
+        public IPEndPoint Get()
+        {
+            lock (_lock)
+            {
+                var node = _nodes[_currentIndex++];
+
+                if (_currentIndex == _nodes.Count)
+                {
+                    _currentIndex = 0;
+                }
+                
+                return node;
+            }
+        }
+
+        public IPEndPoint Find(string gameCode)
+        {
+            var entry = _cache.GetString(gameCode);
+            if (entry == null)
+            {
+                return null;
+            }
+            
+            return IPEndPoint.Parse(entry);
+        }
+
+        public void Save(string gameCode, IPEndPoint endPoint)
+        {
+            _cache.SetString(gameCode, endPoint.ToString(), new DistributedCacheEntryOptions
+            {
+                SlidingExpiration = TimeSpan.FromHours(1)
+            });
+        }
+
+        public void Remove(string gameCode)
+        {
+            _cache.Remove(gameCode);
+        }
+    }
+}
\ No newline at end of file
index 87a8028ddc4510584770e295b92248e3bf09349e..44048622592b591559737307f43a5372bd6fca47 100644 (file)
@@ -4,7 +4,7 @@ using Impostor.Shared.Innersloth;
 
 namespace Impostor.Server.Net.State
 {
-    public partial class ClientPlayer
+    internal partial class ClientPlayer
     {
         /// <summary>
         ///     Triggered when the connected client requests the game listing.
index d9c5c3dba533e67d95ee3232de7a8074ce0cea05..5242cab929f87b04497a827cd5e3b4dc296fcb01 100644 (file)
@@ -5,7 +5,7 @@ using Impostor.Shared.Innersloth.Data;
 
 namespace Impostor.Server.Net.State
 {
-    public partial class ClientPlayer
+    internal partial class ClientPlayer
     {
         private readonly GameManager _gameManager;
 
index 7c5f2b5c45d077102755e5224208ade88397803f..bf91cc98b426cea81eda95fd50d21347bf42de98 100644 (file)
@@ -7,7 +7,7 @@ using Impostor.Shared.Innersloth.Data;
 
 namespace Impostor.Server.Net.State
 {
-    public partial class Game
+    internal partial class Game
     {
         public void HandleStartGame(MessageReader message)
         {
index c03b3b42f360691465c539066e4de0449b2d706f..0938ee1acd42f7676ba635a96e847a460754ccbd 100644 (file)
@@ -5,7 +5,7 @@ using Impostor.Shared.Innersloth.Data;
 
 namespace Impostor.Server.Net.State
 {
-    public partial class Game
+    internal partial class Game
     {
         private void WriteRemovePlayerMessage(MessageWriter message, bool clear, int playerId, DisconnectReason reason)
         {
index 8a60c2f94786e49b9fa89981832bcb22a4691657..bb8ff6a95e932b956c5b02fbd6bd745185fbb2d2 100644 (file)
@@ -5,6 +5,7 @@ using System.Net;
 using Hazel;
 using Impostor.Server.Net.Manager;
 using Impostor.Server.Net.Messages;
+using Impostor.Server.Net.Redirector;
 using Impostor.Shared.Innersloth;
 using Impostor.Shared.Innersloth.Data;
 using Serilog;
@@ -12,17 +13,19 @@ using ILogger = Serilog.ILogger;
 
 namespace Impostor.Server.Net.State
 {
-    public partial class Game
+    internal partial class Game
     {
         private static readonly ILogger Logger = Log.ForContext<Game>();
         
         private readonly GameManager _gameManager;
+        private readonly INodeProvider _nodeProvider;
         private readonly ConcurrentDictionary<int, ClientPlayer> _players;
         private readonly HashSet<IPAddress> _bannedIps;
 
-        public Game(GameManager gameManager, IPEndPoint publicIp, int code, GameOptionsData options)
+        public Game(GameManager gameManager, INodeProvider nodeProvider, IPEndPoint publicIp, int code, GameOptionsData options)
         {
             _gameManager = gameManager;
+            _nodeProvider = nodeProvider;
             _players = new ConcurrentDictionary<int, ClientPlayer>();
             _bannedIps = new HashSet<IPAddress>();
 
index 2cc7f0c1ff4afa4e82f0a7bec2374aa103e09810..984c3143c877e00d9749f981b50980c8f391421e 100644 (file)
@@ -2,11 +2,11 @@
 using Impostor.Server.Data;
 using Impostor.Server.Net;
 using Impostor.Server.Net.Manager;
+using Impostor.Server.Net.Redirector;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
 using Serilog;
-using Serilog.Events;
 
 namespace Impostor.Server
 {
@@ -19,9 +19,8 @@ namespace Impostor.Server
                 .MinimumLevel.Verbose()
 #else
                 .MinimumLevel.Information()
-                
-#endif
                 .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
+#endif
                 .Enrich.FromLogContext()
                 .WriteTo.Console()
                 .CreateLogger();
@@ -48,14 +47,43 @@ namespace Impostor.Server
                 .ConfigureAppConfiguration(builder =>
                 {
                     builder.AddJsonFile("config.json", true);
+                    builder.AddJsonFile("config.Development.json", true);
                     builder.AddEnvironmentVariables(prefix: "IMPOSTOR_");
                     builder.AddCommandLine(args);
                 })
                 .ConfigureServices((host, services) =>
                 {
-                    services.Configure<ServerConfig>(host.Configuration.GetSection("Server"));
+                    var redirector = host.Configuration
+                        .GetSection(ServerRedirectorConfig.Section)
+                        .Get<ServerRedirectorConfig>();
+                    
+                    services.Configure<ServerConfig>(host.Configuration.GetSection(ServerConfig.Section));
+                    services.Configure<ServerRedirectorConfig>(host.Configuration.GetSection(ServerRedirectorConfig.Section));
 
-                    services.AddSingleton<GameManager>();
+                    if (redirector.Enabled)
+                    {
+                        services.AddSingleton<INodeProvider, NodeProviderRedis>();
+                        services.AddStackExchangeRedisCache(options =>
+                        {
+                            options.Configuration = redirector.Redis;
+                            options.InstanceName = "ImpostorRedis";
+                        });
+                    }
+                    else
+                    {
+                        services.AddSingleton<INodeProvider, NodeProviderNoOp>();
+                    }
+                    
+                    if (redirector.Enabled && redirector.Master)
+                    {
+                        services.AddSingleton<IClientManager, ClientManagerRedirector>();
+                    }
+                    else
+                    {
+                        services.AddSingleton<IClientManager, ClientManager>();
+                        services.AddSingleton<GameManager>();
+                    }
+                    
                     services.AddSingleton<Matchmaker>();
                     
                     services.AddHostedService<MatchmakerService>();
index f969cbf41f21b73249454f3566203e7de4027b0b..5e9f6347063be1ea62d1d8f9f3bb5967d32c994c 100644 (file)
@@ -4,5 +4,20 @@
     "PublicPort": 22023,
     "ListenIp": "0.0.0.0",
     "ListenPort": 22023
+  },
+  "ServerRedirector": {
+    "Enabled": false,
+    "Master": true,
+    "Redis": "127.0.0.1:6379",
+    "Nodes": [
+      {
+        "Ip": "127.0.0.1",
+        "Port": 22024
+      },
+      {
+        "Ip": "127.0.0.1",
+        "Port": 22025
+      }
+    ]
   }
 }
\ No newline at end of file