Added Regions to Load Balancer Setup.

This commit is contained in:
Derek S 2021-04-07 00:30:38 -05:00
parent 36362af6ba
commit 14ea6243d0
12 changed files with 171 additions and 43 deletions

View file

@ -8,10 +8,10 @@ namespace LightReflectiveMirror.LoadBalancing
[Serializable] [Serializable]
public struct RelayServerInfo public struct RelayServerInfo
{ {
public int ConnectedClients; public int connectedClients;
public int RoomCount; public int roomCount;
public int PublicRoomCount; public int publicRoomCount;
public TimeSpan Uptime; public TimeSpan uptime;
[JsonIgnore] [JsonIgnore]
public List<Room> serversConnectedToRelay; public List<Room> serversConnectedToRelay;
@ -20,21 +20,22 @@ namespace LightReflectiveMirror.LoadBalancing
[Serializable] [Serializable]
internal struct LoadBalancerStats internal struct LoadBalancerStats
{ {
public int NodeCount; public int nodeCount;
public TimeSpan Uptime; public TimeSpan uptime;
public long CCU; public long CCU;
public long TotalServerCount; public long totalServerCount;
} }
// container for relay address info // container for relay address info
[JsonObject(MemberSerialization.OptOut)] [JsonObject(MemberSerialization.OptOut)]
public struct RelayAddress public struct RelayAddress
{ {
public ushort Port; public ushort port;
public ushort EndpointPort; public ushort endpointPort;
public string Address; public string address;
public LRMRegions serverRegion;
[JsonIgnore] [JsonIgnore]
public string EndpointAddress; public string endpointAddress;
} }
[Serializable] [Serializable]
@ -50,4 +51,6 @@ namespace LightReflectiveMirror.LoadBalancing
public RelayAddress relayInfo; public RelayAddress relayInfo;
} }
public enum LRMRegions { Any, NorthAmerica, SouthAmerica, Europe, Asia, Africa, Oceania }
} }

View file

@ -18,16 +18,30 @@ namespace LightReflectiveMirror.LoadBalancing
[RestResource] [RestResource]
public class Endpoint public class Endpoint
{ {
public static string cachedServerList = "[]"; public static string allCachedServers = "[]";
public static string NorthAmericaCachedServers = "[]";
public static string SouthAmericaCachedServers = "[]";
public static string EuropeCachedServers = "[]";
public static string AsiaCachedServers = "[]";
public static string AfricaCachedServers = "[]";
public static string OceaniaCachedServers = "[]";
private static List<Room> northAmericaServers = new();
private static List<Room> southAmericaServers = new();
private static List<Room> europeServers = new();
private static List<Room> africaServers = new();
private static List<Room> asiaServers = new();
private static List<Room> oceaniaServers = new();
private static List<Room> allServers = new();
private LoadBalancerStats _stats private LoadBalancerStats _stats
{ {
get => new() get => new()
{ {
NodeCount = Program.instance.availableRelayServers.Count, nodeCount = Program.instance.availableRelayServers.Count,
Uptime = DateTime.Now - Program.startupTime, uptime = DateTime.Now - Program.startupTime,
CCU = Program.instance.GetTotalCCU(), CCU = Program.instance.GetTotalCCU(),
TotalServerCount = Program.instance.GetTotalServers(), totalServerCount = Program.instance.GetTotalServers(),
}; };
} }
@ -45,12 +59,14 @@ namespace LightReflectiveMirror.LoadBalancing
string endpointPort = req.Headers["x-EndpointPort"]; string endpointPort = req.Headers["x-EndpointPort"];
string gamePort = req.Headers["x-GamePort"]; string gamePort = req.Headers["x-GamePort"];
string publicIP = req.Headers["x-PIP"]; string publicIP = req.Headers["x-PIP"];
string region = req.Headers["x-Region"];
int regionId = 1;
string address = context.Request.RemoteEndPoint.Address.ToString(); string address = context.Request.RemoteEndPoint.Address.ToString();
Logger.WriteLogMessage("Received auth req [" + receivedAuthKey + "] == [" + Program.conf.AuthKey + "]"); Logger.WriteLogMessage("Received auth req [" + receivedAuthKey + "] == [" + Program.conf.AuthKey + "]");
// if server is authenticated // if server is authenticated
if (receivedAuthKey != null && address != null && endpointPort != null && gamePort != null && receivedAuthKey == Program.conf.AuthKey) if (receivedAuthKey != null && region != null && int.TryParse(region, out regionId) && address != null && endpointPort != null && gamePort != null && receivedAuthKey == Program.conf.AuthKey)
{ {
Logger.WriteLogMessage($"Server accepted: {address}:{gamePort}"); Logger.WriteLogMessage($"Server accepted: {address}:{gamePort}");
@ -58,7 +74,7 @@ namespace LightReflectiveMirror.LoadBalancing
{ {
var _gamePort = Convert.ToUInt16(gamePort); var _gamePort = Convert.ToUInt16(gamePort);
var _endpointPort = Convert.ToUInt16(endpointPort); var _endpointPort = Convert.ToUInt16(endpointPort);
await Program.instance.AddServer(address, _gamePort, _endpointPort, publicIP); await Program.instance.AddServer(address, _gamePort, _endpointPort, publicIP, regionId);
} }
catch catch
{ {
@ -82,20 +98,66 @@ namespace LightReflectiveMirror.LoadBalancing
// Dont allow unauthorizated access waste computing resources. // Dont allow unauthorizated access waste computing resources.
string auth = context.Request.Headers["Authorization"]; string auth = context.Request.Headers["Authorization"];
if(!string.IsNullOrEmpty(auth) && auth == Program.conf.AuthKey) if (!string.IsNullOrEmpty(auth) && auth == Program.conf.AuthKey)
{ {
var relays = Program.instance.availableRelayServers.ToList(); var relays = Program.instance.availableRelayServers.ToList();
List<Room> masterList = new(); ClearAllServersLists();
List<Room> requestedRooms;
for(int i = 0; i < relays.Count; i++) for (int i = 0; i < relays.Count; i++)
{ {
masterList.AddRange(await Program.instance.RequestServerListFromNode(relays[i].Key.Address, relays[i].Key.EndpointPort)); requestedRooms = await Program.instance.RequestServerListFromNode(relays[i].Key.address, relays[i].Key.endpointPort);
allServers.AddRange(requestedRooms);
switch (relays[i].Key.serverRegion)
{
case (LRMRegions.NorthAmerica):
northAmericaServers.AddRange(requestedRooms);
break;
case (LRMRegions.SouthAmerica):
southAmericaServers.AddRange(requestedRooms);
break;
case (LRMRegions.Europe):
europeServers.AddRange(requestedRooms);
break;
case (LRMRegions.Africa):
africaServers.AddRange(requestedRooms);
break;
case (LRMRegions.Asia):
asiaServers.AddRange(requestedRooms);
break;
case (LRMRegions.Oceania):
oceaniaServers.AddRange(requestedRooms);
break;
}
} }
cachedServerList = JsonConvert.SerializeObject(masterList); CacheAllServers();
} }
} }
void CacheAllServers()
{
allCachedServers = JsonConvert.SerializeObject(allServers);
NorthAmericaCachedServers = JsonConvert.SerializeObject(northAmericaServers);
SouthAmericaCachedServers = JsonConvert.SerializeObject(southAmericaServers);
EuropeCachedServers = JsonConvert.SerializeObject(europeServers);
AsiaCachedServers = JsonConvert.SerializeObject(asiaServers);
AfricaCachedServers = JsonConvert.SerializeObject(africaServers);
OceaniaCachedServers = JsonConvert.SerializeObject(oceaniaServers);
}
void ClearAllServersLists()
{
northAmericaServers.Clear();
southAmericaServers.Clear();
europeServers.Clear();
asiaServers.Clear();
africaServers.Clear();
oceaniaServers.Clear();
allServers.Clear();
}
/// <summary> /// <summary>
/// Hooks into from unity side, client will call this to /// Hooks into from unity side, client will call this to
/// find the least populated server to join /// find the least populated server to join
@ -119,7 +181,7 @@ namespace LightReflectiveMirror.LoadBalancing
for (int i = 0; i < servers.Count; i++) for (int i = 0; i < servers.Count; i++)
{ {
if (servers[i].Value.ConnectedClients < lowest.Value.ConnectedClients) if (servers[i].Value.connectedClients < lowest.Value.connectedClients)
{ {
lowest = servers[i]; lowest = servers[i];
} }
@ -127,7 +189,7 @@ namespace LightReflectiveMirror.LoadBalancing
// respond with the server ip // respond with the server ip
// if the string is still dummy then theres no servers // if the string is still dummy then theres no servers
if (lowest.Key.Address != "Dummy") if (lowest.Key.address != "Dummy")
{ {
await context.Response.SendResponseAsync(JsonConvert.SerializeObject(lowest.Key)); await context.Response.SendResponseAsync(JsonConvert.SerializeObject(lowest.Key));
} }
@ -145,7 +207,40 @@ namespace LightReflectiveMirror.LoadBalancing
[RestRoute("Get", "/api/masterlist/")] [RestRoute("Get", "/api/masterlist/")]
public async Task GetMasterServerList(IHttpContext context) public async Task GetMasterServerList(IHttpContext context)
{ {
await context.Response.SendResponseAsync(cachedServerList); string region = context.Request.Headers["x-Region"];
if(int.TryParse(region, out int regionID))
{
switch ((LRMRegions)regionID)
{
case LRMRegions.Any:
await context.Response.SendResponseAsync(allCachedServers);
break;
case LRMRegions.NorthAmerica:
await context.Response.SendResponseAsync(NorthAmericaCachedServers);
break;
case LRMRegions.SouthAmerica:
await context.Response.SendResponseAsync(SouthAmericaCachedServers);
break;
case LRMRegions.Europe:
await context.Response.SendResponseAsync(EuropeCachedServers);
break;
case LRMRegions.Africa:
await context.Response.SendResponseAsync(AfricaCachedServers);
break;
case LRMRegions.Asia:
await context.Response.SendResponseAsync(AsiaCachedServers);
break;
case LRMRegions.Oceania:
await context.Response.SendResponseAsync(OceaniaCachedServers);
break;
}
return;
}
// They didnt submit a region header, just give them all servers as they probably are viewing in browser.
await context.Response.SendResponseAsync(allCachedServers);
} }
/// <summary> /// <summary>
@ -218,7 +313,7 @@ namespace LightReflectiveMirror.LoadBalancing
bool hasLocal = false; bool hasLocal = false;
for(int i = 0; i < bindableIPv4Addresses.Count; i++) for (int i = 0; i < bindableIPv4Addresses.Count; i++)
{ {
if (bindableIPv4Addresses[i] == "127.0.0.1") if (bindableIPv4Addresses[i] == "127.0.0.1")
hasLocal = true; hasLocal = true;
@ -233,4 +328,4 @@ namespace LightReflectiveMirror.LoadBalancing
} }
} }

View file

@ -71,9 +71,9 @@ namespace LightReflectiveMirror.LoadBalancing
/// <param name="endpointPort"></param> /// <param name="endpointPort"></param>
/// <param name="publicIP"></param> /// <param name="publicIP"></param>
/// <returns></returns> /// <returns></returns>
public async Task AddServer(string serverIP, ushort port, ushort endpointPort, string publicIP) public async Task AddServer(string serverIP, ushort port, ushort endpointPort, string publicIP, int regionId)
{ {
var relayAddr = new RelayAddress { Port = port, EndpointPort = endpointPort, Address = publicIP, EndpointAddress = serverIP }; var relayAddr = new RelayAddress { port = port, endpointPort = endpointPort, address = publicIP, endpointAddress = serverIP.Trim(), serverRegion = (LRMRegions)regionId };
if (availableRelayServers.ContainsKey(relayAddr)) if (availableRelayServers.ContainsKey(relayAddr))
{ {
@ -187,13 +187,14 @@ namespace LightReflectiveMirror.LoadBalancing
for (int i = 0; i < keys.Count; i++) for (int i = 0; i < keys.Count; i++)
{ {
if(!await HealthCheckNode(keys[i].EndpointAddress, keys[i].EndpointPort)) if(!await HealthCheckNode(keys[i].endpointAddress, keys[i].endpointPort))
{ {
Logger.ForceLogMessage($"Server {keys[i].Address}:{keys[i].Port} failed a health check, removing from load balancer.", ConsoleColor.Red); Logger.ForceLogMessage($"Server {keys[i].address}:{keys[i].port} failed a health check, removing from load balancer.", ConsoleColor.Red);
availableRelayServers.Remove(keys[i]); availableRelayServers.Remove(keys[i]);
} }
} }
GC.Collect();
await Task.Delay(_pingDelay); await Task.Delay(_pingDelay);
} }
} }
@ -229,5 +230,4 @@ namespace LightReflectiveMirror.LoadBalancing
Logger.ForceLogMessage(load, ConsoleColor.Cyan); Logger.ForceLogMessage(load, ConsoleColor.Cyan);
} }
} }
} }

View file

@ -35,5 +35,6 @@ namespace LightReflectiveMirror
public string LoadBalancerAuthKey = "AuthKey"; public string LoadBalancerAuthKey = "AuthKey";
public string LoadBalancerAddress = "127.0.0.1"; public string LoadBalancerAddress = "127.0.0.1";
public ushort LoadBalancerPort = 7070; public ushort LoadBalancerPort = 7070;
public LRMRegions LoadBalancerRegion = LRMRegions.NorthAmerica;
} }
} }

View file

@ -22,4 +22,19 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project> </Project>

View file

@ -225,13 +225,14 @@ namespace LightReflectiveMirror
var uri = new Uri($"http://{conf.LoadBalancerAddress}:{conf.LoadBalancerPort}/api/auth"); var uri = new Uri($"http://{conf.LoadBalancerAddress}:{conf.LoadBalancerPort}/api/auth");
string endpointPort = conf.EndpointPort.ToString(); string endpointPort = conf.EndpointPort.ToString();
string gamePort = 7777.ToString(); string gamePort = "7777";
HttpWebRequest authReq = (HttpWebRequest)WebRequest.Create(uri); HttpWebRequest authReq = (HttpWebRequest)WebRequest.Create(uri);
authReq.Headers.Add("Authorization", conf.LoadBalancerAuthKey); authReq.Headers.Add("Authorization", conf.LoadBalancerAuthKey);
authReq.Headers.Add("x-EndpointPort", endpointPort); authReq.Headers.Add("x-EndpointPort", endpointPort);
authReq.Headers.Add("x-GamePort", gamePort); authReq.Headers.Add("x-GamePort", gamePort);
authReq.Headers.Add("x-PIP", publicIP); // Public IP authReq.Headers.Add("x-PIP", publicIP); // Public IP
authReq.Headers.Add("x-Region", ((int)conf.LoadBalancerRegion).ToString());
var res = await authReq.GetResponseAsync(); var res = await authReq.GetResponseAsync();

View file

@ -34,4 +34,6 @@ namespace LightReflectiveMirror
private readonly string CONFIG_PATH = System.Environment.GetEnvironmentVariable("LRM_CONFIG_PATH") ?? "config.json"; private readonly string CONFIG_PATH = System.Environment.GetEnvironmentVariable("LRM_CONFIG_PATH") ?? "config.json";
} }
public enum LRMRegions { Any, NorthAmerica, SouthAmerica, Europe, Asia, Africa, Oceania }
} }

View file

@ -132,10 +132,10 @@ namespace LightReflectiveMirror
serverData = serverData, serverData = serverData,
clients = new List<int>(), clients = new List<int>(),
// hard coded for now REMEMBER TO UN-HARDCODE RETARD // hard coded for now REMEMBER TO UN-HARDCODE
// this is needed for load balancer to know which server this room // this is needed for load balancer to know which server this room
// belongs to // belongs to
relayInfo = new RelayAddress { Address = Program.publicIP, Port = 7777, EndpointPort = Program.conf.EndpointPort }, relayInfo = new RelayAddress { address = Program.publicIP, port = 7777, endpointPort = Program.conf.EndpointPort },
serverId = GetRandomServerID(), serverId = GetRandomServerID(),
hostIP = hostIP, hostIP = hostIP,

View file

@ -33,8 +33,8 @@ namespace LightReflectiveMirror
[Serializable] [Serializable]
public struct RelayAddress public struct RelayAddress
{ {
public ushort Port; public ushort port;
public ushort EndpointPort; public ushort endpointPort;
public string Address; public string address;
} }
} }

View file

@ -152,22 +152,23 @@ namespace LightReflectiveMirror
} }
} }
} }
else if(lrm.NATPunchtroughPort < 0) else if (lrm.NATPunchtroughPort < 0)
{ {
// NAT Punchthrough configuration. // NAT Punchthrough configuration.
EditorGUILayout.HelpBox("Do you wish to use NAT Punchthrough? This can reduce load by up to 80% on your LRM nodes, but exposes players IP's to other players.", MessageType.None); EditorGUILayout.HelpBox("Do you wish to use NAT Punchthrough? This can reduce load by up to 80% on your LRM nodes, but exposes players IP's to other players.", MessageType.None);
if(GUILayout.Button("Use NAT Punchthrough")) if (GUILayout.Button("Use NAT Punchthrough"))
{ {
lrm.NATPunchtroughPort = 1; lrm.NATPunchtroughPort = 1;
lrm.useNATPunch = true; lrm.useNATPunch = true;
lrm.gameObject.AddComponent<LRMDirectConnectModule>(); lrm.gameObject.AddComponent<LRMDirectConnectModule>();
} }
if(GUILayout.Button("Do NOT use NAT Punchthrough")) if (GUILayout.Button("Do NOT use NAT Punchthrough"))
lrm.NATPunchtroughPort = 1; lrm.NATPunchtroughPort = 1;
}else if(directModule != null && directModule.directConnectTransport == null) }
else if (directModule != null && directModule.directConnectTransport == null)
{ {
// NAT Punchthrough setup. // NAT Punchthrough setup.
EditorGUILayout.HelpBox("To use direct connecting, we need a transport to communicate with the other clients. Please select a transport to use.", MessageType.None); EditorGUILayout.HelpBox("To use direct connecting, we need a transport to communicate with the other clients. Please select a transport to use.", MessageType.None);
@ -217,7 +218,7 @@ namespace LightReflectiveMirror
break; break;
case 1: case 1:
// NAT punch tab. // NAT punch tab.
if(directModule == null) if (directModule == null)
{ {
EditorGUILayout.HelpBox("If you wish to use NAT punch, you will need to add a \"Direct Connect Module\" to this gameobject.", MessageType.Info); EditorGUILayout.HelpBox("If you wish to use NAT punch, you will need to add a \"Direct Connect Module\" to this gameobject.", MessageType.Info);
} }
@ -230,8 +231,14 @@ namespace LightReflectiveMirror
case 2: case 2:
// Load balancer tab // Load balancer tab
lrm.useLoadBalancer = EditorGUILayout.Toggle("Use Load Balancer", lrm.useLoadBalancer); lrm.useLoadBalancer = EditorGUILayout.Toggle("Use Load Balancer", lrm.useLoadBalancer);
if (!lrm.useLoadBalancer)
GUI.enabled = false;
lrm.loadBalancerAddress = EditorGUILayout.TextField("Load Balancer Address", lrm.loadBalancerAddress); lrm.loadBalancerAddress = EditorGUILayout.TextField("Load Balancer Address", lrm.loadBalancerAddress);
lrm.loadBalancerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Load Balancer Port", lrm.loadBalancerPort), ushort.MinValue, ushort.MaxValue); lrm.loadBalancerPort = (ushort)Mathf.Clamp(EditorGUILayout.IntField("Load Balancer Port", lrm.loadBalancerPort), ushort.MinValue, ushort.MaxValue);
lrm.region = (LRMRegions)EditorGUILayout.EnumPopup("Node Region", lrm.region);
if (!lrm.useLoadBalancer)
GUI.enabled = true;
break; break;
case 3: case 3:
// Other tab... // Other tab...

View file

@ -148,6 +148,7 @@ namespace LightReflectiveMirror
using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) using (UnityWebRequest webRequest = UnityWebRequest.Get(uri))
{ {
webRequest.SetRequestHeader("x-Region", ((int)region).ToString());
// Request and wait for the desired page. // Request and wait for the desired page.
yield return webRequest.SendWebRequest(); yield return webRequest.SendWebRequest();
var result = webRequest.downloadHandler.text; var result = webRequest.downloadHandler.text;

View file

@ -42,6 +42,7 @@ namespace LightReflectiveMirror
private LRMDirectConnectModule _directConnectModule; private LRMDirectConnectModule _directConnectModule;
public LRMRegions region = LRMRegions.NorthAmerica;
private byte[] _clientSendBuffer; private byte[] _clientSendBuffer;
private bool _connectedToRelay = false; private bool _connectedToRelay = false;
private bool _isClient = false; private bool _isClient = false;
@ -61,4 +62,6 @@ namespace LightReflectiveMirror
private BiDictionary<int, int> _connectedRelayClients = new BiDictionary<int, int>(); private BiDictionary<int, int> _connectedRelayClients = new BiDictionary<int, int>();
private BiDictionary<int, int> _connectedDirectClients = new BiDictionary<int, int>(); private BiDictionary<int, int> _connectedDirectClients = new BiDictionary<int, int>();
} }
public enum LRMRegions { Any, NorthAmerica, SouthAmerica, Europe, Asia, Africa, Oceania }
} }