Files
RAGECOOP-V/Server/Networking/Server.cs

413 lines
16 KiB
C#
Raw Normal View History

2022-09-08 12:41:56 -07:00
using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Server.Scripting;
using System;
2021-08-14 21:49:23 +02:00
using System.IO;
2022-09-08 12:41:56 -07:00
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Security.Cryptography;
2022-09-08 12:41:56 -07:00
using System.Text;
using System.Threading;
using Timer = System.Timers.Timer;
2021-07-07 13:36:25 +02:00
2022-05-22 15:55:26 +08:00
namespace RageCoop.Server
2021-07-07 13:36:25 +02:00
{
2022-04-19 05:33:18 +02:00
2022-07-01 13:54:18 +08:00
/// <summary>
/// The instantiable RageCoop server class
/// </summary>
2022-08-10 20:42:47 +08:00
public partial class Server
2021-07-07 13:36:25 +02:00
{
2022-07-01 13:54:18 +08:00
/// <summary>
/// The API for controlling server and hooking events.
/// </summary>
2022-06-30 09:28:13 +08:00
public API API { get; private set; }
2022-07-12 17:16:28 +08:00
internal readonly BaseScript BaseScript;
2022-08-12 18:10:56 +08:00
internal readonly Settings Settings;
internal NetServer MainNetServer;
internal ServerEntities Entities;
internal readonly Dictionary<Command, Action<CommandContext>> Commands = new();
2022-09-08 12:41:56 -07:00
internal readonly Dictionary<long, Client> ClientsByNetHandle = new();
2022-07-09 19:32:11 +08:00
internal readonly Dictionary<string, Client> ClientsByName = new();
2022-08-06 12:32:04 +08:00
internal readonly Dictionary<int, Client> ClientsByID = new();
2022-07-10 16:13:08 +08:00
internal Client _hostClient;
2022-07-09 19:32:11 +08:00
2022-09-08 12:41:56 -07:00
private readonly Dictionary<int, FileTransfer> InProgressFileTransfers = new();
2022-07-29 17:44:02 +08:00
internal Resources Resources;
internal Logger Logger;
internal Security Security;
2022-06-30 09:28:13 +08:00
private bool _stopping = false;
private readonly Thread _listenerThread;
private readonly Timer _announceTimer = new();
private readonly Timer _playerUpdateTimer = new();
2022-08-22 00:23:46 +08:00
private readonly Timer _antiAssholesTimer = new();
private readonly Timer _updateTimer = new();
private readonly Worker _worker;
private readonly HashSet<char> _allowedCharacterSet;
2022-09-08 12:41:56 -07:00
private readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses = new();
internal Dictionary<PacketType, Func<NetIncomingMessage, Client, Packet>> RequestHandlers = new();
2022-08-16 23:17:04 +08:00
/// <summary>
/// Get the current server version
/// </summary>
public static readonly Version Version = typeof(Server).Assembly.GetName().Version;
2022-07-01 13:54:18 +08:00
/// <summary>
/// Instantiate a server.
/// </summary>
/// <param name="settings"></param>
/// <param name="logger"></param>
/// <exception cref="ArgumentNullException"></exception>
2022-09-08 12:41:56 -07:00
public Server(Settings settings, Logger logger = null)
2021-07-07 13:36:25 +02:00
{
2022-06-24 18:25:24 +08:00
Settings = settings;
2022-09-08 12:41:56 -07:00
if (settings == null) { throw new ArgumentNullException("Server settings cannot be null!"); }
Logger = logger;
if (Logger != null) { Logger.LogLevel = Settings.LogLevel; }
API = new API(this);
Resources = new Resources(this);
Security = new Security(Logger);
Entities = new ServerEntities(this);
BaseScript = new BaseScript(this);
_allowedCharacterSet = new HashSet<char>(Settings.AllowedUsernameChars.ToCharArray());
2022-07-31 20:56:51 +08:00
2022-09-08 12:41:56 -07:00
_worker = new Worker("ServerWorker", Logger);
2022-09-08 12:41:56 -07:00
_listenerThread = new Thread(() => Listen());
_announceTimer.Interval = 1;
_announceTimer.Elapsed += (s, e) =>
{
_announceTimer.Interval = 10000;
_announceTimer.Stop();
Announce();
_announceTimer.Start();
};
_playerUpdateTimer.Interval = 1000;
_playerUpdateTimer.Elapsed += (s, e) => SendPlayerUpdate();
2022-08-22 00:23:46 +08:00
_antiAssholesTimer.Interval = 5000;
_antiAssholesTimer.Elapsed += (s, e) => KickAssholes();
_updateTimer.Interval = 1;
_updateTimer.Elapsed += (s, e) =>
{
2022-09-08 12:41:56 -07:00
_updateTimer.Interval = 1000 * 60 * 10; // 10 minutes
_updateTimer.Stop();
CheckUpdate();
_updateTimer.Start();
};
2022-06-27 15:35:23 +08:00
}
2022-09-08 12:41:56 -07:00
2022-07-01 13:54:18 +08:00
/// <summary>
/// Spawn threads and start the server
/// </summary>
2022-06-27 15:35:23 +08:00
public void Start()
{
2022-09-08 12:41:56 -07:00
Logger?.Info("================");
Logger?.Info($"Listening port: {Settings.Port}");
2022-08-16 23:17:04 +08:00
Logger?.Info($"Server version: {Version}");
Logger?.Info($"Compatible client version: {Version.ToString(3)}");
2022-08-25 22:32:01 +08:00
Logger?.Info($"Runtime: {CoreUtils.GetInvariantRID()} => {System.Runtime.InteropServices.RuntimeInformation.RuntimeIdentifier}");
Logger?.Info("================");
Logger?.Info($"Listening addresses:");
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces())
{
Logger?.Info($"[{netInterface.Description}]:");
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses)
{
Logger.Info(string.Join(", ", addr.Address));
}
Logger.Info("");
}
2022-08-12 20:40:50 +08:00
if (Settings.UseZeroTier)
{
2022-09-08 12:41:56 -07:00
Logger?.Info($"Joining ZeroTier network: " + Settings.ZeroTierNetworkID);
if (ZeroTierHelper.Join(Settings.ZeroTierNetworkID) == null)
2022-08-12 20:40:50 +08:00
{
throw new Exception("Failed to obtain ZeroTier network IP");
}
}
else if (Settings.UseP2P)
{
Logger?.Warning("ZeroTier is not enabled, P2P connection may not work as expected.");
}
2022-04-06 02:18:24 +02:00
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
NetPeerConfiguration config = new("623c92c287cc392406e7aaaac1c0f3b0")
2021-07-07 13:36:25 +02:00
{
2022-06-24 18:25:24 +08:00
Port = Settings.Port,
MaximumConnections = Settings.MaxPlayers,
EnableUPnP = false,
2022-08-08 17:03:41 +08:00
AutoFlushSendQueue = true,
2022-09-08 12:41:56 -07:00
PingInterval = 5
2021-07-07 13:36:25 +02:00
};
config.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
2022-06-24 10:33:36 +08:00
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
2021-07-07 13:36:25 +02:00
MainNetServer = new NetServer(config);
MainNetServer.Start();
2022-09-08 12:41:56 -07:00
BaseScript.API = API;
2022-06-22 14:18:20 +08:00
BaseScript.OnStart();
Resources.LoadAll();
2022-06-27 15:35:23 +08:00
_listenerThread.Start();
Logger?.Info("Listening for clients");
2022-09-08 12:41:56 -07:00
_playerUpdateTimer.Enabled = true;
if (Settings.AnnounceSelf)
{
2022-09-08 12:41:56 -07:00
_announceTimer.Enabled = true;
}
if (Settings.AutoUpdate)
{
_updateTimer.Enabled = true;
}
2022-08-22 09:33:54 +08:00
_antiAssholesTimer.Enabled = true;
2022-06-27 15:35:23 +08:00
}
2022-07-01 13:54:18 +08:00
/// <summary>
/// Terminate threads and stop the server
/// </summary>
2022-06-27 15:35:23 +08:00
public void Stop()
{
Logger?.Flush();
Logger?.Dispose();
_stopping = true;
_listenerThread.Join();
_playerUpdateTimer.Enabled = false;
_announceTimer.Enabled = false;
2022-06-30 09:28:13 +08:00
_worker.Dispose();
2021-07-07 13:36:25 +02:00
}
2022-07-01 12:22:31 +08:00
internal void QueueJob(Action job)
{
_worker.QueueJob(job);
}
2021-07-07 13:36:25 +02:00
// Send a message to targets or all players
2022-09-08 12:41:56 -07:00
internal void ChatMessageReceived(string name, string message, Client sender = null)
2021-07-07 13:36:25 +02:00
{
if (message.StartsWith('/'))
2021-08-15 07:54:25 +02:00
{
string[] cmdArgs = message.Split(" ");
2022-06-30 09:28:13 +08:00
string cmdName = cmdArgs[0].Remove(0, 1);
2022-09-08 12:41:56 -07:00
QueueJob(() => API.Events.InvokeOnCommandReceived(cmdName, cmdArgs, sender));
2022-06-04 18:09:42 +08:00
return;
}
message = message.Replace("~", "");
2022-09-08 12:41:56 -07:00
2022-09-06 21:46:35 +08:00
QueueJob(() => API.Events.InvokeOnChatMessage(message, sender));
2022-09-08 12:41:56 -07:00
foreach (var c in ClientsByNetHandle.Values)
{
2022-07-29 18:17:45 +08:00
var msg = MainNetServer.CreateMessage();
var crypt = new Func<string, byte[]>((s) =>
{
return Security.Encrypt(s.GetBytes(), c.EndPoint);
});
new Packets.ChatMessage(crypt)
{
2022-09-08 12:41:56 -07:00
Username = name,
Message = message
}.Pack(msg);
MainNetServer.SendMessage(msg, c.Connection, NetDeliveryMethod.ReliableOrdered, (int)ConnectionChannel.Chat);
2022-07-29 18:17:45 +08:00
}
}
internal void SendChatMessage(string name, string message, Client target)
{
2022-09-08 12:41:56 -07:00
if (target == null) { return; }
var msg = MainNetServer.CreateMessage();
2022-07-29 18:17:45 +08:00
new Packets.ChatMessage(new Func<string, byte[]>((s) =>
{
return Security.Encrypt(s.GetBytes(), target.EndPoint);
}))
{
2022-09-08 12:41:56 -07:00
Username = name,
Message = message,
}.Pack(msg);
MainNetServer.SendMessage(msg, target.Connection, NetDeliveryMethod.ReliableOrdered, (int)ConnectionChannel.Chat);
}
2021-08-18 11:47:59 +02:00
internal void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
{
Command command = new(name) { Usage = usage, ArgsLength = argsLength };
if (Commands.ContainsKey(command))
{
throw new Exception("Command \"" + command.Name + "\" was already been registered!");
}
Commands.Add(command, callback);
}
internal void RegisterCommand(string name, Action<CommandContext> callback)
2021-08-18 11:47:59 +02:00
{
Command command = new(name);
2021-08-18 11:47:59 +02:00
if (Commands.ContainsKey(command))
{
throw new Exception("Command \"" + command.Name + "\" was already been registered!");
}
Commands.Add(command, callback);
}
internal void RegisterCommands<T>()
2021-08-18 11:47:59 +02:00
{
IEnumerable<MethodInfo> commands = typeof(T).GetMethods().Where(method => method.GetCustomAttributes(typeof(Command), false).Any());
2021-08-18 11:47:59 +02:00
foreach (MethodInfo method in commands)
{
Command attribute = method.GetCustomAttribute<Command>(true);
2021-08-18 11:47:59 +02:00
RegisterCommand(attribute.Name, attribute.Usage, attribute.ArgsLength, (Action<CommandContext>)Delegate.CreateDelegate(typeof(Action<CommandContext>), method));
2021-08-18 11:47:59 +02:00
}
}
2022-09-08 12:41:56 -07:00
internal T GetResponse<T>(Client client, Packet request, ConnectionChannel channel = ConnectionChannel.RequestResponse, int timeout = 5000) where T : Packet, new()
2022-07-01 12:22:31 +08:00
{
2022-09-08 12:41:56 -07:00
if (Thread.CurrentThread == _listenerThread)
2022-07-01 12:22:31 +08:00
{
throw new InvalidOperationException("Cannot wait for response from the listener thread!");
}
2022-09-08 12:41:56 -07:00
var received = new AutoResetEvent(false);
T response = new T();
2022-07-01 12:22:31 +08:00
var id = NewRequestID();
2022-09-08 12:41:56 -07:00
PendingResponses.Add(id, (type, m) =>
2022-07-01 12:22:31 +08:00
{
response.Deserialize(m);
2022-07-01 12:22:31 +08:00
received.Set();
});
var msg = MainNetServer.CreateMessage();
msg.Write((byte)PacketType.Request);
msg.Write(id);
request.Pack(msg);
2022-09-08 12:41:56 -07:00
MainNetServer.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableOrdered, (int)channel);
2022-07-01 12:22:31 +08:00
if (received.WaitOne(timeout))
{
return response;
2022-07-01 12:22:31 +08:00
}
2022-09-05 13:02:09 +02:00
return null;
2022-07-01 12:22:31 +08:00
}
2022-09-08 12:41:56 -07:00
internal void SendFile(string path, string name, Client client, Action<float> updateCallback = null)
2022-07-19 17:15:53 +08:00
{
2022-08-23 13:22:14 +08:00
var fs = File.OpenRead(path);
2022-09-08 12:41:56 -07:00
SendFile(fs, name, client, NewFileID(), updateCallback);
2022-08-23 13:22:14 +08:00
fs.Close();
fs.Dispose();
2022-07-19 17:15:53 +08:00
}
2022-09-08 12:41:56 -07:00
internal void SendFile(Stream stream, string name, Client client, int id = default, Action<float> updateCallback = null)
{
2022-07-19 17:15:53 +08:00
stream.Seek(0, SeekOrigin.Begin);
2022-09-08 12:41:56 -07:00
id = id == default ? NewFileID() : id;
2022-07-19 17:15:53 +08:00
var total = stream.Length;
Logger?.Debug($"Requesting file transfer:{name}, {total}");
2022-07-01 12:22:31 +08:00
if (GetResponse<Packets.FileTransferResponse>(client, new Packets.FileTransferRequest()
{
2022-09-08 12:41:56 -07:00
FileLength = total,
Name = name,
ID = id,
}, ConnectionChannel.File)?.Response != FileResponse.NeedToDownload)
2022-07-01 12:22:31 +08:00
{
Logger?.Info($"Skipping file transfer \"{name}\" to {client.Username}");
return;
}
Logger?.Debug($"Initiating file transfer:{name}, {total}");
FileTransfer transfer = new()
{
2022-09-08 12:41:56 -07:00
ID = id,
Name = name,
};
2022-07-01 12:22:31 +08:00
InProgressFileTransfers.Add(id, transfer);
int read = 0;
2022-07-01 12:22:31 +08:00
int thisRead;
do
{
// 4 KB chunk
byte[] chunk = new byte[4096];
2022-09-08 12:41:56 -07:00
read += thisRead = stream.Read(chunk, 0, 4096);
if (thisRead != chunk.Length)
{
2022-09-08 12:41:56 -07:00
if (thisRead == 0) { break; }
Logger?.Trace($"Purging chunk:{thisRead}");
Array.Resize(ref chunk, thisRead);
}
Send(
new Packets.FileTransferChunk()
{
2022-09-08 12:41:56 -07:00
ID = id,
FileChunk = chunk,
},
client, ConnectionChannel.File, NetDeliveryMethod.ReliableOrdered);
2022-09-08 12:41:56 -07:00
transfer.Progress = read / stream.Length;
if (updateCallback != null) { updateCallback(transfer.Progress); }
2022-09-08 12:41:56 -07:00
} while (thisRead > 0);
2022-07-19 17:15:53 +08:00
if (GetResponse<Packets.FileTransferResponse>(client, new Packets.FileTransferComplete()
2022-07-01 12:22:31 +08:00
{
2022-09-08 12:41:56 -07:00
ID = id,
}, ConnectionChannel.File)?.Response != FileResponse.Completed)
2022-07-01 12:22:31 +08:00
{
2022-09-08 12:41:56 -07:00
Logger.Warning($"File trasfer to {client.Username} failed: " + name);
2022-07-19 17:15:53 +08:00
}
Logger?.Debug($"All file chunks sent:{name}");
InProgressFileTransfers.Remove(id);
}
2022-07-19 17:15:53 +08:00
internal int NewFileID()
{
int ID = 0;
2022-09-08 12:41:56 -07:00
while ((ID == 0)
|| InProgressFileTransfers.ContainsKey(ID))
{
byte[] rngBytes = new byte[4];
RandomNumberGenerator.Create().GetBytes(rngBytes);
// Convert the bytes into an integer
ID = BitConverter.ToInt32(rngBytes, 0);
}
return ID;
}
2022-07-01 12:22:31 +08:00
private int NewRequestID()
{
int ID = 0;
2022-09-08 12:41:56 -07:00
while ((ID == 0)
2022-07-01 12:22:31 +08:00
|| PendingResponses.ContainsKey(ID))
{
byte[] rngBytes = new byte[4];
RandomNumberGenerator.Create().GetBytes(rngBytes);
// Convert the bytes into an integer
ID = BitConverter.ToInt32(rngBytes, 0);
}
return ID;
}
2022-09-08 12:41:56 -07:00
internal void Send(Packet p, Client client, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
p.Pack(outgoingMessage);
2022-09-08 12:41:56 -07:00
MainNetServer.SendMessage(outgoingMessage, client.Connection, method, (int)channel);
}
internal void Forward(Packet p, Client except, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
p.Pack(outgoingMessage);
MainNetServer.SendToAll(outgoingMessage, except.Connection, method, (int)channel);
}
internal void SendToAll(Packet p, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
p.Pack(outgoingMessage);
MainNetServer.SendToAll(outgoingMessage, method, (int)channel);
}
2021-07-07 13:36:25 +02:00
}
}