using Quasar.Common.Extensions; using Quasar.Common.Messages; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; namespace Quasar.Server.Networking { public class Server { /// /// Occurs when the state of the server changes. /// public event ServerStateEventHandler ServerState; /// /// Represents a method that will handle a change in the server's state. /// /// The server which changed its state. /// The new listening state of the server. /// The port the server is listening on, if listening is True. public delegate void ServerStateEventHandler(Server s, bool listening, ushort port); /// /// Fires an event that informs subscribers that the server has changed it's state. /// /// The new listening state of the server. private void OnServerState(bool listening) { if (Listening == listening) return; Listening = listening; var handler = ServerState; handler?.Invoke(this, listening, Port); } /// /// Occurs when the state of a client changes. /// public event ClientStateEventHandler ClientState; /// /// Represents a method that will handle a change in a client's state. /// /// The server, the client is connected to. /// The client which changed its state. /// The new connection state of the client. public delegate void ClientStateEventHandler(Server s, Client c, bool connected); /// /// Fires an event that informs subscribers that a client has changed its state. /// /// The client which changed its state. /// The new connection state of the client. private void OnClientState(Client c, bool connected) { if (!connected) RemoveClient(c); var handler = ClientState; handler?.Invoke(this, c, connected); } /// /// Occurs when a message is received by a client. /// public event ClientReadEventHandler ClientRead; /// /// Represents a method that will handle a message received from a client. /// /// The server, the client is connected to. /// The client that has received the message. /// The message that received by the client. public delegate void ClientReadEventHandler(Server s, Client c, IMessage message); /// /// Fires an event that informs subscribers that a message has been /// received from the client. /// /// The client that has received the message. /// The message that received by the client. /// The length of the message. private void OnClientRead(Client c, IMessage message, int messageLength) { BytesReceived += messageLength; var handler = ClientRead; handler?.Invoke(this, c, message); } /// /// Occurs when a message is sent by a client. /// public event ClientWriteEventHandler ClientWrite; /// /// Represents the method that will handle the sent message by a client. /// /// The server, the client is connected to. /// The client that has sent the message. /// The message that has been sent by the client. public delegate void ClientWriteEventHandler(Server s, Client c, IMessage message); /// /// Fires an event that informs subscribers that the client has sent a message. /// /// The client that has sent the message. /// The message that has been sent by the client. /// The length of the message. private void OnClientWrite(Client c, IMessage message, int messageLength) { BytesSent += messageLength; var handler = ClientWrite; handler?.Invoke(this, c, message); } /// /// The port on which the server is listening. /// public ushort Port { get; private set; } /// /// The total amount of received bytes. /// public long BytesReceived { get; set; } /// /// The total amount of sent bytes. /// public long BytesSent { get; set; } /// /// The buffer size for receiving data in bytes. /// private const int BufferSize = 1024 * 16; // 16 KB /// /// The keep-alive time in ms. /// private const uint KeepAliveTime = 25000; // 25 s /// /// The keep-alive interval in ms. /// private const uint KeepAliveInterval = 25000; // 25 s /// /// The buffer pool to hold the receive-buffers for the clients. /// private readonly BufferPool _bufferPool = new BufferPool(BufferSize, 1) { ClearOnReturn = false }; /// /// The listening state of the server. True if listening, else False. /// public bool Listening { get; private set; } /// /// Gets the clients currently connected to the server. /// protected Client[] Clients { get { lock (_clientsLock) { return _clients.ToArray(); } } } /// /// Handle of the Server Socket. /// private Socket _handle; /// /// The server certificate. /// protected readonly X509Certificate2 ServerCertificate; /// /// The event to accept new connections asynchronously. /// private SocketAsyncEventArgs _item; /// /// List of the clients connected to the server. /// private readonly List _clients = new List(); /// /// The UPnP service used to discover, create and delete port mappings. /// private UPnPService _UPnPService; /// /// Lock object for the list of clients. /// private readonly object _clientsLock = new object(); /// /// Determines if the server is currently processing Disconnect method. /// protected bool ProcessingDisconnect { get; set; } /// /// Constructor of the server, initializes serializer types. /// /// The server certificate. protected Server(X509Certificate2 serverCertificate) { ServerCertificate = serverCertificate; TypeRegistry.AddTypesToSerializer(typeof(IMessage), TypeRegistry.GetPacketTypes(typeof(IMessage)).ToArray()); } /// /// Begins listening for clients. /// /// Port to listen for clients on. /// If set to true, use a dual-stack socket to allow IPv4/6 connections. Otherwise use IPv4-only socket. /// Enables the automatic UPnP port forwarding. public void Listen(ushort port, bool ipv6, bool enableUPnP) { if (Listening) return; this.Port = port; if (enableUPnP) { _UPnPService = new UPnPService(); _UPnPService.CreatePortMapAsync(port); } if (Socket.OSSupportsIPv6 && ipv6) { _handle = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); _handle.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0); _handle.Bind(new IPEndPoint(IPAddress.IPv6Any, port)); } else { _handle = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _handle.Bind(new IPEndPoint(IPAddress.Any, port)); } _handle.Listen(1000); OnServerState(true); _item = new SocketAsyncEventArgs(); _item.Completed += AcceptClient; if (!_handle.AcceptAsync(_item)) AcceptClient(this, _item); } /// /// Accepts and begins authenticating an incoming client. /// /// The sender. /// Asynchronous socket event. private void AcceptClient(object s, SocketAsyncEventArgs e) { try { do { switch (e.SocketError) { case SocketError.Success: SslStream sslStream = null; try { Socket clientSocket = e.AcceptSocket; clientSocket.SetKeepAliveEx(KeepAliveInterval, KeepAliveTime); sslStream = new SslStream(new NetworkStream(clientSocket, true), false); // the SslStream owns the socket and on disposing also disposes the NetworkStream and Socket sslStream.BeginAuthenticateAsServer(ServerCertificate, false, SslProtocols.Tls12, false, EndAuthenticateClient, new PendingClient {Stream = sslStream, EndPoint = (IPEndPoint) clientSocket.RemoteEndPoint}); } catch (Exception) { sslStream?.Close(); } break; case SocketError.ConnectionReset: break; default: throw new SocketException((int) e.SocketError); } e.AcceptSocket = null; // enable reuse } while (!_handle.AcceptAsync(e)); } catch (ObjectDisposedException) { } catch (Exception) { Disconnect(); } } private class PendingClient { public SslStream Stream { get; set; } public IPEndPoint EndPoint { get; set; } } /// /// Ends the authentication process of a newly connected client. /// /// The status of the asynchronous operation. private void EndAuthenticateClient(IAsyncResult ar) { var con = (PendingClient) ar.AsyncState; try { con.Stream.EndAuthenticateAsServer(ar); Client client = new Client(_bufferPool, con.Stream, con.EndPoint); AddClient(client); OnClientState(client, true); } catch (Exception) { con.Stream.Close(); } } /// /// Adds a connected client to the list of clients, /// subscribes to the client's events. /// /// The client to add. private void AddClient(Client client) { lock (_clientsLock) { client.ClientState += OnClientState; client.ClientRead += OnClientRead; client.ClientWrite += OnClientWrite; _clients.Add(client); } } /// /// Removes a disconnected client from the list of clients, /// unsubscribes from the client's events. /// /// The client to remove. private void RemoveClient(Client client) { if (ProcessingDisconnect) return; lock (_clientsLock) { client.ClientState -= OnClientState; client.ClientRead -= OnClientRead; client.ClientWrite -= OnClientWrite; _clients.Remove(client); } } /// /// Disconnect the server from all of the clients and discontinue /// listening (placing the server in an "off" state). /// public void Disconnect() { if (ProcessingDisconnect) return; ProcessingDisconnect = true; if (_handle != null) { _handle.Close(); _handle = null; } if (_item != null) { _item.Dispose(); _item = null; } if (_UPnPService != null) { _UPnPService.DeletePortMapAsync(Port); _UPnPService = null; } lock (_clientsLock) { while (_clients.Count != 0) { try { _clients[0].Disconnect(); _clients[0].ClientState -= OnClientState; _clients[0].ClientRead -= OnClientRead; _clients[0].ClientWrite -= OnClientWrite; _clients.RemoveAt(0); } catch { } } } ProcessingDisconnect = false; OnServerState(false); } } }