Successful load balance test

This commit is contained in:
cxxpxr 2021-04-05 18:25:43 -04:00
parent 6d72c947fe
commit c162be240c
5 changed files with 186 additions and 64 deletions

View file

@ -2,6 +2,7 @@
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -12,25 +13,35 @@ using HttpStatusCode = Grapevine.HttpStatusCode;
namespace LightReflectiveMirror.LoadBalancing namespace LightReflectiveMirror.LoadBalancing
{ {
[RestResource] [RestResource]
public class Endpoint public class Endpoint
{ {
/// <summary>
/// Sent from an LRM server node
/// adds it to the list if authenticated.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[RestRoute("Get", "/api/auth")] [RestRoute("Get", "/api/auth")]
public async Task ReceiveAuthKey(IHttpContext context) public async Task ReceiveAuthKey(IHttpContext context)
{ {
var req = context.Request; var req = context.Request;
string receivedAuthKey = req.Headers["Auth"]; string receivedAuthKey = req.Headers["Auth"];
string port = req.Headers["Port"]; string endpointPort = req.Headers["EndpointPort"];
string gamePort = req.Headers["GamePort"];
string address = context.Request.RemoteEndPoint.Address.ToString(); string address = context.Request.RemoteEndPoint.Address.ToString();
Console.WriteLine("Received auth req [" + receivedAuthKey + "] == [" + Program.conf.AuthKey+"]"); Console.WriteLine("Received auth req [" + receivedAuthKey + "] == [" + Program.conf.AuthKey+"]");
// if server is authenticated // if server is authenticated
if (receivedAuthKey != null && address != null && port != null && receivedAuthKey == Program.conf.AuthKey) if (receivedAuthKey != null && address != null && endpointPort != null && gamePort != null && receivedAuthKey == Program.conf.AuthKey)
{ {
Console.WriteLine($"Server accepted: {address}:{port}"); Console.WriteLine($"Server accepted: {address}:{gamePort}");
var _gamePort = Convert.ToUInt16(gamePort);
await Program.instance.AddServer($"{address}:{port}"); var _endpointPort = Convert.ToUInt16(endpointPort);
await Program.instance.AddServer(address, _gamePort, _endpointPort);
await context.Response.SendResponseAsync(HttpStatusCode.Ok); await context.Response.SendResponseAsync(HttpStatusCode.Ok);
} }
@ -57,7 +68,7 @@ namespace LightReflectiveMirror.LoadBalancing
return; return;
} }
KeyValuePair<string, RelayStats> lowest = new("Dummy", new RelayStats { ConnectedClients = int.MaxValue }); KeyValuePair<RelayAddress, RelayStats> lowest = new(new RelayAddress { Address = "Dummy" }, new RelayStats { ConnectedClients = int.MaxValue });
for (int i = 0; i < servers.Count; i++) for (int i = 0; i < servers.Count; i++)
{ {
@ -69,7 +80,16 @@ 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
await context.Response.SendResponseAsync(lowest.Key != "Dummy" ? lowest.Key : HttpStatusCode.InternalServerError); if (lowest.Key.Address != "Dummy")
{
// ping server to ensure its online.
await Program.instance.ManualPingServer(lowest.Key.Address, lowest.Key.Port);
await context.Response.SendResponseAsync(JsonConvert.SerializeObject(lowest.Key));
}
else
{
await context.Response.SendResponseAsync(HttpStatusCode.InternalServerError);
}
} }
} }

View file

@ -14,7 +14,7 @@ namespace LightReflectiveMirror.LoadBalancing
/// Keeps track of all available relays. /// Keeps track of all available relays.
/// Key is server address, value is CCU. /// Key is server address, value is CCU.
/// </summary> /// </summary>
public Dictionary<string, RelayStats> availableRelayServers = new(); public Dictionary<RelayAddress, RelayStats> availableRelayServers = new();
private int _pingDelay = 10000; private int _pingDelay = 10000;
const string API_PATH = "/api/stats"; const string API_PATH = "/api/stats";
@ -56,21 +56,21 @@ namespace LightReflectiveMirror.LoadBalancing
} }
public async Task AddServer(string serverIP) public async Task AddServer(string serverIP, ushort port, ushort endpointPort)
{ {
var stats = await ManualPingServer(serverIP); var stats = await ManualPingServer(serverIP, endpointPort);
if(stats.HasValue) if(stats.HasValue)
availableRelayServers.Add(serverIP, stats.Value); availableRelayServers.Add(new RelayAddress { Port = port, EndpointPort = endpointPort, Address = serverIP }, stats.Value);
} }
async Task<RelayStats?> ManualPingServer(string serverIP) public async Task<RelayStats?> ManualPingServer(string serverIP, ushort port)
{ {
using (WebClient wc = new WebClient()) using (WebClient wc = new WebClient())
{ {
try try
{ {
string receivedStats = await wc.DownloadStringTaskAsync($"http://{serverIP}{API_PATH}"); string receivedStats = await wc.DownloadStringTaskAsync($"http://{serverIP}:{port}{API_PATH}");
return JsonConvert.DeserializeObject<RelayStats>(receivedStats); return JsonConvert.DeserializeObject<RelayStats>(receivedStats);
} }
@ -89,26 +89,25 @@ namespace LightReflectiveMirror.LoadBalancing
WriteLogMessage("Pinging " + availableRelayServers.Count + " available relays"); WriteLogMessage("Pinging " + availableRelayServers.Count + " available relays");
// Create a new list so we can modify the collection in our loop. // Create a new list so we can modify the collection in our loop.
var keys = new List<string>(availableRelayServers.Keys); var keys = new List<RelayAddress>(availableRelayServers.Keys);
for(int i = 0; i < keys.Count; i++) for(int i = 0; i < keys.Count; i++)
{ {
string url = $"http://{keys[i]}{API_PATH}"; string url = $"http://{keys[i].Address}:{keys[i].EndpointPort}{API_PATH}";
using (WebClient wc = new WebClient()) using (WebClient wc = new WebClient())
{ {
try try
{ {
var serverStats = wc.DownloadString(url); var serverStats = wc.DownloadString(url);
Console.WriteLine(serverStats); var deserializedData = JsonConvert.DeserializeObject<RelayStats>(serverStats);
WriteLogMessage("Server " + keys[i] + " still exists, keeping in collection."); WriteLogMessage("Server " + keys[i].Address + " still exists, keeping in collection.");
if (availableRelayServers.ContainsKey(keys[i])) if (availableRelayServers.ContainsKey(keys[i]))
availableRelayServers[keys[i]] = JsonConvert.DeserializeObject<RelayStats>(serverStats); availableRelayServers[keys[i]] = deserializedData;
else else
availableRelayServers.Add(keys[i], JsonConvert.DeserializeObject<RelayStats>(serverStats)); availableRelayServers.Add(keys[i], deserializedData);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -174,4 +173,13 @@ namespace LightReflectiveMirror.LoadBalancing
public int PublicRoomCount; public int PublicRoomCount;
public TimeSpan Uptime; public TimeSpan Uptime;
} }
[Serializable]
public struct RelayAddress
{
public ushort Port;
public ushort EndpointPort;
public string Address;
}
} }

View file

@ -6,15 +6,34 @@ namespace LightReflectiveMirror
{ {
class Config class Config
{ {
//========================
// Required Settings
//========================
public string TransportDLL = "MultiCompiled.dll"; public string TransportDLL = "MultiCompiled.dll";
public string TransportClass = "Mirror.SimpleWebTransport"; public string TransportClass = "Mirror.SimpleWebTransport";
public string AuthenticationKey = "Secret Auth Key"; public string AuthenticationKey = "Secret Auth Key";
public int UpdateLoopTime = 10; public int UpdateLoopTime = 10;
public int UpdateHeartbeatInterval = 100; public int UpdateHeartbeatInterval = 100;
//========================
// Endpoint REST API Settings
//========================
public bool UseEndpoint = true; public bool UseEndpoint = true;
public ushort EndpointPort = 8080; public ushort EndpointPort = 8080;
public bool EndpointServerList = true; public bool EndpointServerList = true;
//========================
// Nat Puncher Settings
//========================
public bool EnableNATPunchtroughServer = true; public bool EnableNATPunchtroughServer = true;
public ushort NATPunchtroughPort = 7776; public ushort NATPunchtroughPort = 7776;
//========================
// Load Balancer Settings
//========================
public bool UseLoadBalancer = false;
public string LoadBalancerAuthKey = "AuthKey";
public string LoadBalancerAddress = "127.0.0.1";
public ushort LoadBalancerPort = 7070;
} }
} }

View file

@ -32,7 +32,6 @@ namespace LightReflectiveMirror
private BiDictionary<int, string> _pendingNATPunches = new BiDictionary<int, string>(); private BiDictionary<int, string> _pendingNATPunches = new BiDictionary<int, string>();
private int _currentHeartbeatTimer = 0; private int _currentHeartbeatTimer = 0;
private string _externalIp;
private byte[] _NATRequest = new byte[500]; private byte[] _NATRequest = new byte[500];
private int _NATRequestPosition = 0; private int _NATRequestPosition = 0;
@ -63,7 +62,6 @@ namespace LightReflectiveMirror
else else
{ {
conf = JsonConvert.DeserializeObject<Config>(File.ReadAllText(CONFIG_PATH)); conf = JsonConvert.DeserializeObject<Config>(File.ReadAllText(CONFIG_PATH));
_externalIp = await GetExternalIp();
WriteLogMessage("Loading Assembly... ", ConsoleColor.White, true); WriteLogMessage("Loading Assembly... ", ConsoleColor.White, true);
try try
@ -193,7 +191,8 @@ namespace LightReflectiveMirror
Environment.Exit(0); Environment.Exit(0);
} }
await RegisterSelfToLoadBalancer(); if(conf.UseLoadBalancer)
await RegisterSelfToLoadBalancer();
} }
while (true) while (true)
@ -217,45 +216,33 @@ namespace LightReflectiveMirror
} }
} }
private async Task<bool> RegisterSelfToLoadBalancer()
async Task<bool> RegisterSelfToLoadBalancer()
{ {
try try
{ {
// replace hard coded value for config value later // replace hard coded value for config value later
var uri = new Uri("http://localhost:7070/api/auth"); var uri = new Uri($"http://{conf.LoadBalancerAddress}:{conf.LoadBalancerPort}/api/auth");
string externalip = _externalIp.Normalize().Trim(); string endpointPort = conf.EndpointPort.ToString();
string port = conf.EndpointPort.ToString(); string gamePort = 7777.ToString();
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(uri); HttpWebRequest authReq = (HttpWebRequest)WebRequest.Create(uri);
myRequest.Headers.Add("Auth", "AuthKey"); authReq.Headers.Add("Auth", conf.LoadBalancerAuthKey);
myRequest.Headers.Add("Port", port); authReq.Headers.Add("EndpointPort", endpointPort);
authReq.Headers.Add("GamePort", gamePort);
WebResponse myResponse = await myRequest.GetResponseAsync(); var res = await authReq.GetResponseAsync();
return true; return true;
} }
catch catch
{ {
// error adding or load balancer unavailable // error adding or load balancer unavailable
WriteLogMessage("Error registering", ConsoleColor.Red); WriteLogMessage("Error registering - Load Balancer probably timed out.", ConsoleColor.Red);
return false; return false;
} }
} }
async Task<string> GetExternalIp()
{
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create("https://ipv4.icanhazip.com/");
WebResponse myResponse = await myRequest.GetResponseAsync();
Stream stream = myResponse.GetResponseStream();
var ip = new StreamReader(stream).ReadToEnd();
return ip;
}
void RunNATPunchLoop() void RunNATPunchLoop()
{ {
WriteLogMessage("OK\n", ConsoleColor.Green); WriteLogMessage("OK\n", ConsoleColor.Green);

View file

@ -23,18 +23,27 @@ namespace LightReflectiveMirror
public bool connectOnAwake = true; public bool connectOnAwake = true;
public string authenticationKey = "Secret Auth Key"; public string authenticationKey = "Secret Auth Key";
public UnityEvent diconnectedFromRelay; public UnityEvent diconnectedFromRelay;
[Header("NAT Punchthrough")] [Header("NAT Punchthrough")]
[Help("NAT Punchthrough will require the Direct Connect module attached.")] [Help("NAT Punchthrough will require the Direct Connect module attached.")]
public bool useNATPunch = true; public bool useNATPunch = true;
public ushort NATPunchtroughPort = 7776; public ushort NATPunchtroughPort = 7776;
[Header("Load Balancer")]
public bool useLoadBalancer = false;
public ushort loadBalancerPort = 7070;
public string loadBalancerAddress = "127.0.0.1";
[Header("Server Hosting Data")] [Header("Server Hosting Data")]
public string serverName = "My awesome server!"; public string serverName = "My awesome server!";
public string extraServerData = "Map 1"; public string extraServerData = "Map 1";
public int maxServerPlayers = 10; public int maxServerPlayers = 10;
public bool isPublicServer = true; public bool isPublicServer = true;
[Header("Server List")] [Header("Server List")]
public UnityEvent serverListUpdated; public UnityEvent serverListUpdated;
public List<RelayServerInfo> relayServerList { private set; get; } = new List<RelayServerInfo>(); public List<RelayServerInfo> relayServerList { private set; get; } = new List<RelayServerInfo>();
[Header("Server Information")] [Header("Server Information")]
public int serverId = -1; public int serverId = -1;
@ -58,7 +67,7 @@ namespace LightReflectiveMirror
private BiDictionary<IPEndPoint, SocketProxy> _serverProxies = new BiDictionary<IPEndPoint, SocketProxy>(); private BiDictionary<IPEndPoint, SocketProxy> _serverProxies = new BiDictionary<IPEndPoint, SocketProxy>();
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 override bool ClientConnected() => _isClient; public override bool ClientConnected() => _isClient;
private void OnConnectedToRelay() => _connectedToRelay = true; private void OnConnectedToRelay() => _connectedToRelay = true;
public bool IsAuthenticated() => _isAuthenticated; public bool IsAuthenticated() => _isAuthenticated;
@ -66,8 +75,8 @@ namespace LightReflectiveMirror
public override bool Available() => _connectedToRelay; public override bool Available() => _connectedToRelay;
public override void ClientConnect(Uri uri) => ClientConnect(uri.Host); public override void ClientConnect(Uri uri) => ClientConnect(uri.Host);
public override int GetMaxPacketSize(int channelId = 0) => clientToServerTransport.GetMaxPacketSize(channelId); public override int GetMaxPacketSize(int channelId = 0) => clientToServerTransport.GetMaxPacketSize(channelId);
public override string ServerGetClientAddress(int connectionId) public override string ServerGetClientAddress(int connectionId)
{ {
if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId)) if (_connectedRelayClients.TryGetBySecond(connectionId, out int relayId))
return relayId.ToString(); return relayId.ToString();
@ -110,7 +119,7 @@ namespace LightReflectiveMirror
{ {
if (_isServer) if (_isServer)
{ {
if(_serverProxies.TryGetByFirst(newClientEP, out SocketProxy foundProxy)) if (_serverProxies.TryGetByFirst(newClientEP, out SocketProxy foundProxy))
{ {
if (data.Length > 2) if (data.Length > 2)
foundProxy.RelayData(data, data.Length); foundProxy.RelayData(data, data.Length);
@ -124,7 +133,7 @@ namespace LightReflectiveMirror
if (_isClient) if (_isClient)
{ {
if(_clientProxy == null) if (_clientProxy == null)
{ {
_clientProxy = new SocketProxy(_NATIP.Port - 1); _clientProxy = new SocketProxy(_NATIP.Port - 1);
_clientProxy.dataReceived += ClientProcessProxyData; _clientProxy.dataReceived += ClientProcessProxyData;
@ -162,7 +171,7 @@ namespace LightReflectiveMirror
_directConnectModule = GetComponent<LRMDirectConnectModule>(); _directConnectModule = GetComponent<LRMDirectConnectModule>();
if(_directConnectModule != null) if (_directConnectModule != null)
{ {
if (useNATPunch && !_directConnectModule.SupportsNATPunch()) if (useNATPunch && !_directConnectModule.SupportsNATPunch())
{ {
@ -199,15 +208,86 @@ namespace LightReflectiveMirror
public void ConnectToRelay() public void ConnectToRelay()
{ {
if (!_connectedToRelay) if (!useLoadBalancer)
{ {
_clientSendBuffer = new byte[clientToServerTransport.GetMaxPacketSize()]; if (!_connectedToRelay)
{
clientToServerTransport.ClientConnect(serverIP); Connect(serverIP);
}
else
{
Debug.LogWarning("LRM | Already connected to relay!");
}
} }
else else
{ {
Debug.Log("Already connected to relay!"); if (!_connectedToRelay)
{
StartCoroutine(RelayConnect());
}
else
{
Debug.LogWarning("LRM | Already connected to relay!");
}
}
}
/// <summary>
/// Connects to the desired relay
/// </summary>
/// <param name="serverIP"></param>
private void Connect(string serverIP, ushort port = 7777)
{
// need to implement custom port
if (clientToServerTransport is LightReflectiveMirrorTransport)
throw new Exception("LRM | Client to Server Transport cannot be LRM.");
if (clientToServerTransport is kcp2k.KcpTransport kcp)
{
kcp.Port = port;
}
_clientSendBuffer = new byte[clientToServerTransport.GetMaxPacketSize()];
clientToServerTransport.ClientConnect(serverIP);
}
IEnumerator RelayConnect()
{
string url = $"http://{loadBalancerAddress}:{loadBalancerPort}/api/join/";
using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
{
// Request and wait for the desired page.
yield return webRequest.SendWebRequest();
var result = webRequest.downloadHandler.text;
#if UNITY_2020_1_OR_NEWER
switch (webRequest.result)
{
case UnityWebRequest.Result.ConnectionError:
case UnityWebRequest.Result.DataProcessingError:
case UnityWebRequest.Result.ProtocolError:
Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer.");
break;
case UnityWebRequest.Result.Success:
var parsedAddress = JsonConvert.DeserializeObject<RelayAddress>(result);
Connect(parsedAddress.Address, parsedAddress.Port);
endpointServerPort = parsedAddress.EndpointPort;
break;
}
#else
if (webRequest.isNetworkError || webRequest.isHttpError)
{
Debug.LogWarning("LRM | Network Error while getting a relay to join from Load Balancer.");
}
else
{
// join here
var parsedAddress = JsonConvert.DeserializeObject<RelayAddress>(result);
Connect(parsedAddress.Address, parsedAddress.Port);
endpointServerPort = parsedAddress.EndpointPort;
}
#endif
} }
} }
@ -274,7 +354,7 @@ namespace LightReflectiveMirror
if (_isServer) if (_isServer)
{ {
if(_connectedRelayClients.TryGetByFirst(data.ReadInt(ref pos), out int clientID)) if (_connectedRelayClients.TryGetByFirst(data.ReadInt(ref pos), out int clientID))
OnServerDataReceived?.Invoke(clientID, new ArraySegment<byte>(recvData), channel); OnServerDataReceived?.Invoke(clientID, new ArraySegment<byte>(recvData), channel);
} }
@ -294,7 +374,7 @@ namespace LightReflectiveMirror
int user = data.ReadInt(ref pos); int user = data.ReadInt(ref pos);
if (_connectedRelayClients.TryGetByFirst(user, out int clientID)) if (_connectedRelayClients.TryGetByFirst(user, out int clientID))
{ {
OnServerDisconnected?.Invoke(_connectedRelayClients.GetByFirst(clientID)); OnServerDisconnected?.Invoke(clientID);
_connectedRelayClients.Remove(user); _connectedRelayClients.Remove(user);
} }
} }
@ -358,19 +438,19 @@ namespace LightReflectiveMirror
initalData.WriteString(ref sendPos, data.ReadString(ref pos)); initalData.WriteString(ref sendPos, data.ReadString(ref pos));
// Send 3 to lower chance of it being dropped or corrupted when received on server. // Send 3 to lower chance of it being dropped or corrupted when received on server.
_NATPuncher.Send(initalData, sendPos,_relayPuncherIP); _NATPuncher.Send(initalData, sendPos, _relayPuncherIP);
_NATPuncher.Send(initalData, sendPos,_relayPuncherIP); _NATPuncher.Send(initalData, sendPos, _relayPuncherIP);
_NATPuncher.Send(initalData, sendPos, _relayPuncherIP); _NATPuncher.Send(initalData, sendPos, _relayPuncherIP);
_NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher); _NATPuncher.BeginReceive(new AsyncCallback(RecvData), _NATPuncher);
} }
break; break;
} }
} }
catch(Exception e) { print(e); } catch (Exception e) { print(e); }
} }
IEnumerator GetServerList() IEnumerator GetServerList()
{ {
string uri = $"http://{serverIP}:{endpointServerPort}/api/compressed/servers"; string uri = $"http://{serverIP}:{endpointServerPort}/api/compressed/servers";
using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) using (UnityWebRequest webRequest = UnityWebRequest.Get(uri))
@ -529,7 +609,7 @@ namespace LightReflectiveMirror
return true; return true;
} }
if(_connectedDirectClients.TryGetBySecond(connectionId, out int directId)) if (_connectedDirectClients.TryGetBySecond(connectionId, out int directId))
return _directConnectModule.KickClient(directId); return _directConnectModule.KickClient(directId);
return false; return false;
@ -573,7 +653,7 @@ namespace LightReflectiveMirror
var keys = new List<IPEndPoint>(_serverProxies.GetAllKeys()); var keys = new List<IPEndPoint>(_serverProxies.GetAllKeys());
for(int i = 0; i < keys.Count; i++) for (int i = 0; i < keys.Count; i++)
{ {
_serverProxies.GetByFirst(keys[i]).Dispose(); _serverProxies.GetByFirst(keys[i]).Dispose();
_serverProxies.Remove(keys[i]); _serverProxies.Remove(keys[i]);
@ -749,4 +829,12 @@ namespace LightReflectiveMirror
public int serverId; public int serverId;
public string serverData; public string serverData;
} }
[Serializable]
public struct RelayAddress
{
public ushort Port;
public ushort EndpointPort;
public string Address;
}
} }