This commit is contained in:
Derek S 2021-04-11 15:49:19 -05:00
commit a8be5cabdb
6 changed files with 191 additions and 155 deletions

View file

@ -4,7 +4,7 @@ using System.Text;
namespace LightReflectiveMirror namespace LightReflectiveMirror
{ {
class Config public class Config
{ {
//======================== //========================
// Required Settings // Required Settings

View file

@ -16,24 +16,14 @@ namespace LightReflectiveMirror
partial class Program partial class Program
{ {
public static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); public static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult();
public List<Room> GetRooms() => _relay.rooms;
public async Task MainAsync() public async Task MainAsync()
{ {
WriteTitle(); WriteTitle();
instance = this; instance = this;
_startupTime = DateTime.Now; _startupTime = DateTime.Now;
using (WebClient wc = new WebClient())
{ GetPublicIP();
try
{
publicIP = wc.DownloadString("http://ipv4.icanhazip.com").Replace("\\r", "").Replace("\\n", "").Trim();
}
catch {
WriteLogMessage("Failed to reach public IP endpoint! Using loopback address.", ConsoleColor.Yellow);
publicIP = "127.0.0.1";
}
}
if (!File.Exists(CONFIG_PATH)) if (!File.Exists(CONFIG_PATH))
{ {
@ -46,22 +36,7 @@ namespace LightReflectiveMirror
{ {
conf = JsonConvert.DeserializeObject<Config>(File.ReadAllText(CONFIG_PATH)); conf = JsonConvert.DeserializeObject<Config>(File.ReadAllText(CONFIG_PATH));
// Docker variables. ConfigureDocker();
if (ushort.TryParse(Environment.GetEnvironmentVariable("LRM_ENDPOINT_PORT"), out ushort endpointPort))
conf.EndpointPort = endpointPort;
if (ushort.TryParse(Environment.GetEnvironmentVariable("LRM_TRANSPORT_PORT"), out ushort transportPort))
conf.TransportPort = transportPort;
if (ushort.TryParse(Environment.GetEnvironmentVariable("LRM_PUNCHER_PORT"), out ushort puncherPort))
conf.NATPunchtroughPort = puncherPort;
string LBAuthKey = Environment.GetEnvironmentVariable("LRM_LB_AUTHKEY");
if (!string.IsNullOrWhiteSpace(LBAuthKey))
{
conf.LoadBalancerAuthKey = LBAuthKey;
WriteLogMessage("\nLoaded LB auth key from environment variable\n", ConsoleColor.Green);
}
WriteLogMessage("Loading Assembly... ", ConsoleColor.White, true); WriteLogMessage("Loading Assembly... ", ConsoleColor.White, true);
try try
@ -75,109 +50,13 @@ namespace LightReflectiveMirror
if (transport != null) if (transport != null)
{ {
var transportClass = asm.GetType(conf.TransportClass); ConfigureTransport(asm);
WriteLogMessage("OK", ConsoleColor.Green);
WriteLogMessage("\nLoading Transport Methods... ", ConsoleColor.White, true);
CheckMethods(transportClass);
WriteLogMessage("OK", ConsoleColor.Green);
WriteLogMessage("\nInvoking Transport Methods...");
if (_awakeMethod != null)
_awakeMethod.Invoke(transport, null);
if (_startMethod != null)
_startMethod.Invoke(transport, null);
WriteLogMessage("\nStarting Transport... ", ConsoleColor.White, true);
transport.OnServerError = (clientID, error) =>
{
WriteLogMessage($"Transport Error, Client: {clientID}, Error: {error}", ConsoleColor.Red);
};
transport.OnServerConnected = (clientID) =>
{
WriteLogMessage($"Transport Connected, Client: {clientID}", ConsoleColor.Cyan);
_currentConnections.Add(clientID);
_relay.ClientConnected(clientID);
if (conf.EnableNATPunchtroughServer)
{
string natID = Guid.NewGuid().ToString();
_pendingNATPunches.Add(clientID, natID);
_NATRequestPosition = 0;
_NATRequest.WriteByte(ref _NATRequestPosition, (byte)OpCodes.RequestNATConnection);
_NATRequest.WriteString(ref _NATRequestPosition, natID);
_NATRequest.WriteInt(ref _NATRequestPosition, conf.NATPunchtroughPort);
transport.ServerSend(clientID, 0, new ArraySegment<byte>(_NATRequest, 0, _NATRequestPosition));
}
};
_relay = new RelayHandler(transport.GetMaxPacketSize(0));
transport.OnServerDataReceived = _relay.HandleMessage;
transport.OnServerDisconnected = (clientID) =>
{
_currentConnections.Remove(clientID);
_relay.HandleDisconnect(clientID);
if (NATConnections.ContainsKey(clientID))
NATConnections.Remove(clientID);
if (_pendingNATPunches.TryGetByFirst(clientID, out _))
_pendingNATPunches.Remove(clientID);
};
transport.ServerStart(conf.TransportPort);
WriteLogMessage("OK", ConsoleColor.Green);
if (conf.UseEndpoint) if (conf.UseEndpoint)
{ ConfigureEndpoint();
WriteLogMessage("\nStarting Endpoint Service... ", ConsoleColor.White, true);
var endpointService = new EndpointServer();
if (endpointService.Start(conf.EndpointPort))
{
WriteLogMessage("OK", ConsoleColor.Green);
Endpoint.RoomsModified();
}
else
{
WriteLogMessage("FAILED\nPlease run as administrator or check if port is in use.", ConsoleColor.DarkRed);
}
}
if (conf.EnableNATPunchtroughServer) if (conf.EnableNATPunchtroughServer)
{ ConfigurePunchthrough();
WriteLogMessage("\nStarting NatPunchthrough Socket... ", ConsoleColor.White, true);
try
{
_punchServer = new UdpClient(conf.NATPunchtroughPort);
WriteLogMessage("OK\n", ConsoleColor.Green, true);
WriteLogMessage("\nStarting NatPunchthrough Thread... ", ConsoleColor.White, true);
var natThread = new Thread(new ThreadStart(RunNATPunchLoop));
try
{
natThread.Start();
}
catch (Exception e)
{
WriteLogMessage("FAILED\n" + e, ConsoleColor.DarkRed);
}
}
catch (Exception e)
{
WriteLogMessage("FAILED\nCheck if port is in use.", ConsoleColor.DarkRed, true);
Console.WriteLine(e);
}
}
} }
else else
{ {
@ -197,6 +76,11 @@ namespace LightReflectiveMirror
await RegisterSelfToLoadBalancer(); await RegisterSelfToLoadBalancer();
} }
await HeartbeatLoop();
}
private async Task HeartbeatLoop()
{
while (true) while (true)
{ {
try try
@ -209,7 +93,6 @@ namespace LightReflectiveMirror
WriteLogMessage("Error During Transport Update! " + e, ConsoleColor.Red); WriteLogMessage("Error During Transport Update! " + e, ConsoleColor.Red);
} }
_currentHeartbeatTimer++; _currentHeartbeatTimer++;
if (_currentHeartbeatTimer >= conf.UpdateHeartbeatInterval) if (_currentHeartbeatTimer >= conf.UpdateHeartbeatInterval)
@ -224,7 +107,9 @@ namespace LightReflectiveMirror
if (DateTime.Now > Endpoint.lastPing.AddSeconds(60)) if (DateTime.Now > Endpoint.lastPing.AddSeconds(60))
{ {
// Dont await that on main thread. It would cause a lag spike for clients. // Dont await that on main thread. It would cause a lag spike for clients.
#pragma warning disable CS4014
RegisterSelfToLoadBalancer(); RegisterSelfToLoadBalancer();
#pragma warning restore CS4014
} }
} }
@ -239,11 +124,8 @@ namespace LightReflectiveMirror
{ {
try try
{ {
using (WebClient wc = new()) webClient.Headers.Add("Authorization", conf.LoadBalancerAuthKey);
{ await webClient.DownloadStringTaskAsync($"http://{conf.LoadBalancerAddress}:{conf.LoadBalancerPort}/api/roomsupdated");
wc.Headers.Add("Authorization", conf.LoadBalancerAuthKey);
await wc.DownloadStringTaskAsync($"http://{conf.LoadBalancerAddress}:{conf.LoadBalancerPort}/api/roomsupdated");
}
} }
catch { } // LLB might be down, ignore. catch { } // LLB might be down, ignore.
} }
@ -279,13 +161,5 @@ namespace LightReflectiveMirror
return false; return false;
} }
} }
void CheckMethods(Type type)
{
_awakeMethod = type.GetMethod("Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
_startMethod = type.GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
_updateMethod = type.GetMethod("Update", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
_lateUpdateMethod = type.GetMethod("LateUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
} }
} }

View file

@ -0,0 +1,150 @@
using LightReflectiveMirror.Endpoints;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace LightReflectiveMirror
{
public partial class Program
{
private void ConfigureTransport(Assembly asm)
{
var transportClass = asm.GetType(conf.TransportClass);
WriteLogMessage("OK", ConsoleColor.Green);
WriteLogMessage("\nLoading Transport Methods... ", ConsoleColor.White, true);
CheckMethods(transportClass);
WriteLogMessage("OK", ConsoleColor.Green);
WriteLogMessage("\nInvoking Transport Methods...");
if (_awakeMethod != null)
_awakeMethod.Invoke(transport, null);
if (_startMethod != null)
_startMethod.Invoke(transport, null);
WriteLogMessage("\nStarting Transport... ", ConsoleColor.White, true);
transport.OnServerError = (clientID, error) =>
{
WriteLogMessage($"Transport Error, Client: {clientID}, Error: {error}", ConsoleColor.Red);
};
transport.OnServerConnected = (clientID) =>
{
WriteLogMessage($"Transport Connected, Client: {clientID}", ConsoleColor.Cyan);
_currentConnections.Add(clientID);
_relay.ClientConnected(clientID);
if (conf.EnableNATPunchtroughServer)
{
string natID = Guid.NewGuid().ToString();
_pendingNATPunches.Add(clientID, natID);
_NATRequestPosition = 0;
_NATRequest.WriteByte(ref _NATRequestPosition, (byte)OpCodes.RequestNATConnection);
_NATRequest.WriteString(ref _NATRequestPosition, natID);
_NATRequest.WriteInt(ref _NATRequestPosition, conf.NATPunchtroughPort);
transport.ServerSend(clientID, 0, new ArraySegment<byte>(_NATRequest, 0, _NATRequestPosition));
}
};
_relay = new RelayHandler(transport.GetMaxPacketSize(0));
transport.OnServerDataReceived = _relay.HandleMessage;
transport.OnServerDisconnected = (clientID) =>
{
_currentConnections.Remove(clientID);
_relay.HandleDisconnect(clientID);
if (NATConnections.ContainsKey(clientID))
NATConnections.Remove(clientID);
if (_pendingNATPunches.TryGetByFirst(clientID, out _))
_pendingNATPunches.Remove(clientID);
};
transport.ServerStart(conf.TransportPort);
WriteLogMessage("OK", ConsoleColor.Green);
}
private static void ConfigureEndpoint()
{
WriteLogMessage("\nStarting Endpoint Service... ", ConsoleColor.White, true);
var endpointService = new EndpointServer();
if (endpointService.Start(conf.EndpointPort))
{
WriteLogMessage("OK", ConsoleColor.Green);
Endpoint.RoomsModified();
}
else
{
WriteLogMessage("FAILED\nPlease run as administrator or check if port is in use.", ConsoleColor.DarkRed);
}
}
private void ConfigurePunchthrough()
{
WriteLogMessage("\nStarting NatPunchthrough Socket... ", ConsoleColor.White, true);
try
{
_punchServer = new UdpClient(conf.NATPunchtroughPort);
WriteLogMessage("OK\n", ConsoleColor.Green, true);
WriteLogMessage("\nStarting NatPunchthrough Thread... ", ConsoleColor.White, true);
var natThread = new Thread(new ThreadStart(RunNATPunchLoop));
try
{
natThread.Start();
}
catch (Exception e)
{
WriteLogMessage("FAILED\n" + e, ConsoleColor.DarkRed);
}
}
catch (Exception e)
{
WriteLogMessage("FAILED\nCheck if port is in use.", ConsoleColor.DarkRed, true);
Console.WriteLine(e);
}
}
private static void ConfigureDocker()
{
// Docker variables.
if (ushort.TryParse(Environment.GetEnvironmentVariable("LRM_ENDPOINT_PORT"), out ushort endpointPort))
conf.EndpointPort = endpointPort;
if (ushort.TryParse(Environment.GetEnvironmentVariable("LRM_TRANSPORT_PORT"), out ushort transportPort))
conf.TransportPort = transportPort;
if (ushort.TryParse(Environment.GetEnvironmentVariable("LRM_PUNCHER_PORT"), out ushort puncherPort))
conf.NATPunchtroughPort = puncherPort;
string LBAuthKey = Environment.GetEnvironmentVariable("LRM_LB_AUTHKEY");
if (!string.IsNullOrWhiteSpace(LBAuthKey))
{
conf.LoadBalancerAuthKey = LBAuthKey;
WriteLogMessage("\nLoaded LB auth key from environment variable\n", ConsoleColor.Green);
}
}
void CheckMethods(Type type)
{
_awakeMethod = type.GetMethod("Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
_startMethod = type.GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
_updateMethod = type.GetMethod("Update", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
_lateUpdateMethod = type.GetMethod("LateUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}
}
}

View file

@ -1,11 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace LightReflectiveMirror namespace LightReflectiveMirror
{ {
partial class Program partial class Program
{ {
public List<Room> GetRooms() => _relay.rooms;
public int GetConnections() => _currentConnections.Count; public int GetConnections() => _currentConnections.Count;
public TimeSpan GetUptime() => DateTime.Now - _startupTime; public TimeSpan GetUptime() => DateTime.Now - _startupTime;
public int GetPublicRoomCount() => _relay.rooms.Where(x => x.isPublic).Count(); public int GetPublicRoomCount() => _relay.rooms.Where(x => x.isPublic).Count();
@ -19,6 +20,20 @@ namespace LightReflectiveMirror
Console.WriteLine(message); Console.WriteLine(message);
} }
private static void GetPublicIP()
{
try
{
// easier to just ping an outside source to get our public ip
publicIP = webClient.DownloadString("http://ipv4.icanhazip.com").Replace("\\r", "").Replace("\\n", "").Trim();
}
catch
{
WriteLogMessage("Failed to reach public IP endpoint! Using loopback address.", ConsoleColor.Yellow);
publicIP = "127.0.0.1";
}
}
void WriteTitle() void WriteTitle()
{ {
string t = @" string t = @"

View file

@ -1,5 +1,4 @@
using LightReflectiveMirror.Endpoints; using Mirror;
using Mirror;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
@ -8,8 +7,9 @@ using System.Reflection;
namespace LightReflectiveMirror namespace LightReflectiveMirror
{ {
partial class Program public partial class Program
{ {
public static WebClient webClient = new();
public static Transport transport; public static Transport transport;
public static Program instance; public static Program instance;
public static Config conf; public static Config conf;
@ -22,9 +22,9 @@ namespace LightReflectiveMirror
private DateTime _startupTime; private DateTime _startupTime;
public static string publicIP; public static string publicIP;
private List<int> _currentConnections = new List<int>(); private List<int> _currentConnections = new();
public Dictionary<int, IPEndPoint> NATConnections = new Dictionary<int, IPEndPoint>(); public Dictionary<int, IPEndPoint> NATConnections = new();
private BiDictionary<int, string> _pendingNATPunches = new BiDictionary<int, string>(); private BiDictionary<int, string> _pendingNATPunches = new();
private int _currentHeartbeatTimer = 0; private int _currentHeartbeatTimer = 0;
private byte[] _NATRequest = new byte[500]; private byte[] _NATRequest = new byte[500];

View file

@ -99,15 +99,12 @@ namespace LightReflectiveMirror
else else
{ {
// ping load balancer here // ping load balancer here
using (WebClient wc = new())
{
var uri = new Uri($"http://{Program.conf.LoadBalancerAddress}:{Program.conf.LoadBalancerPort}/api/get/id"); var uri = new Uri($"http://{Program.conf.LoadBalancerAddress}:{Program.conf.LoadBalancerPort}/api/get/id");
string randomID = wc.DownloadString(uri).Replace("\\r", "").Replace("\\n", "").Trim(); string randomID = Program.webClient.DownloadString(uri).Replace("\\r", "").Replace("\\n", "").Trim();
return randomID; return randomID;
} }
} }
}
/// <summary> /// <summary>
/// Checks if a server id already is in use. /// Checks if a server id already is in use.