/************************************************************************* * ModernUO * * Copyright 2019-2020 - ModernUO Development Team * * Email: hi@modernuo.com * * File: TcpServer.cs * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *************************************************************************/ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using Server.Exceptions; namespace Server.Network { public static class TcpServer { private static NetworkState m_NetworkState = NetworkState.ResumeState; // Sanity. 256 * 1024 * 5000 = ~1.3GB of ram public static int MaxConnections { get; set; } = 5000; private const long _listenerErrorMessageDelay = 10000; // 10 seconds private static long _nextMaximumSocketsReachedMessage; // AccountLoginReject BadComm private static readonly byte[] socketRejected = { 0x82, 0xFF }; public static IPEndPoint[] ListeningAddresses { get; private set; } public static TcpListener[] Listeners { get; private set; } public static HashSet Instances { get; } = new(128); public static ConcurrentQueue m_ConnectedQueue = new(); public static void Configure() { MaxConnections = ServerConfiguration.GetOrUpdateSetting("tcpServer.maxConnections", MaxConnections); } public static void Start() { HashSet listeningAddresses = new HashSet(); List listeners = new List(); foreach (var ipep in ServerConfiguration.Listeners) { var listener = CreateListener(ipep); if (listener == null) { continue; } if (ipep.Address.Equals(IPAddress.Any) || ipep.Address.Equals(IPAddress.IPv6Any)) { listeningAddresses.UnionWith(GetListeningAddresses(ipep)); } else { listeningAddresses.Add(ipep); } listeners.Add(listener); listener.BeginAcceptingSockets(); } foreach (var ipep in listeningAddresses) { Console.WriteLine("Listening: {0}:{1}", ipep.Address, ipep.Port); } ListeningAddresses = listeningAddresses.ToArray(); Listeners = listeners.ToArray(); } public static IEnumerable GetListeningAddresses(IPEndPoint ipep) => NetworkInterface.GetAllNetworkInterfaces().SelectMany(adapter => adapter.GetIPProperties().UnicastAddresses .Where(uip => ipep.AddressFamily == uip.Address.AddressFamily) .Select(uip => new IPEndPoint(uip.Address, ipep.Port)) ); public static TcpListener CreateListener(IPEndPoint ipep) { var listener = new TcpListener(ipep); listener.Server.ExclusiveAddressUse = false; try { listener.Start(8); return listener; } catch (SocketException se) { // WSAEADDRINUSE if (se.ErrorCode == 10048) { Console.WriteLine("Listener: {0}:{1}: Failed (In Use)", ipep.Address, ipep.Port); } // WSAEADDRNOTAVAIL else if (se.ErrorCode == 10049) { Console.WriteLine("Listener {0}:{1}: Failed (Unavailable)", ipep.Address, ipep.Port); } else { Console.WriteLine("Listener Exception:"); Console.WriteLine(se); } } return null; } /** * Pauses the TcpServer and stops accepting new sockets. * This is thread-safe without using locks. */ public static void Pause() { NetworkState.Pause(ref m_NetworkState); } /** * Resumes accepting sockets on the TcpServer. * This is thread-safe using a lock on the listeners */ public static void Resume() { if (!NetworkState.Resume(ref m_NetworkState)) { return; } lock (Listeners) { foreach (var listener in Listeners) { listener.BeginAcceptingSockets(); } } } public static void Slice() { int count = 0; while (count++ < 250) { if (!m_ConnectedQueue.TryDequeue(out var ns)) { break; } Instances.Add(ns); ns.WriteConsole("Connected. [{0} Online]", Instances.Count); } } private static async void BeginAcceptingSockets(this TcpListener listener) { while (true) { if (m_NetworkState.Paused) { return; } Socket socket = null; try { socket = await listener.AcceptSocketAsync().ConfigureAwait(false); if (Instances.Count >= MaxConnections) { socket.Send(socketRejected, SocketFlags.None); socket.Shutdown(SocketShutdown.Both); socket.Close(); throw new MaxConnectionsException(); } var args = new SocketConnectEventArgs(socket); EventSink.InvokeSocketConnect(args); if (!args.AllowConnection) { socket.Send(socketRejected, SocketFlags.None); socket.Shutdown(SocketShutdown.Both); socket.Close(); } } catch (MaxConnectionsException) { var ticks = Core.TickCount; if (_nextMaximumSocketsReachedMessage <= ticks) { if (socket?.RemoteEndPoint is IPEndPoint ipep) { Console.WriteLine("Listener {0}:{1}: Failed (Maximum connections reached)", ipep.Address, ipep.Port); } _nextMaximumSocketsReachedMessage = ticks + _listenerErrorMessageDelay; } } catch { // ignored } var ns = new NetState(new NetworkSocket(socket)); m_ConnectedQueue.Enqueue(ns); ns.Start(); } } } }