using System; using System.Collections.Concurrent; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; using System.Threading; namespace UdPunching.Serv { internal static class ServProgram { private static RSACryptoServiceProvider _serverRsaCryptoServiceProvider; private static readonly ConcurrentDictionary OnlineSessions = new ConcurrentDictionary(); private static int packetSeq; private static Socket _svrSocket; private static void ProcessPacket(SocketAsyncEventArgs sae) { //KeepAlive 1s -------- add endpoint to list if no exist, return list //Logout -------- remove from list //Knock -------- PeerID ++packetSeq; Console.WriteLine($"Incoming packet#{packetSeq:0,000,000} from {sae.RemoteEndPoint}, len {sae.BytesTransferred}"); var peerId = TransferCodec.ReadId(sae.Buffer); Console.WriteLine($"Incoming packet#{packetSeq:0,000,000} peer id {peerId}"); var rspMsg = new ExchangeMessage(); var isSessionCreateNew = false; var isSessionValidated = false; if (OnlineSessions.TryGetValue(peerId, out var session) && session.RemoteEndPoint.IpEndPointEqualsTo(sae.RemoteEndPoint)) { session.Actively(); isSessionValidated = true; } else { var peerRsa = LoadPeerRsa(peerId); if (null == peerRsa) { Console.WriteLine($"Incoming packet#{packetSeq:0,000,000} peer NOT FOUND"); rspMsg.Flags = ExchangeMessageFlags.PeerNoRegistered; } else { session = new OnlineSession(peerId, sae.RemoteEndPoint, peerRsa); OnlineSessions[peerId] = session; isSessionCreateNew = true; isSessionValidated = true; } } byte[] msgData = null; if (isSessionValidated) { try { msgData = TransferCodec.DecodeData(_serverRsaCryptoServiceProvider, sae.Buffer); Console.WriteLine($"Incoming packet#{packetSeq:0,000,000} MsgLength: {msgData.Length}"); } catch (Exception ex) { Console.WriteLine(ex); isSessionValidated = false; } } if (isSessionValidated) { var reqMsg = new ExchangeMessage(msgData); Console.WriteLine($"Incoming packet#{packetSeq:0,000,000} Flags: {reqMsg.Flags}"); if (isSessionCreateNew) rspMsg.Flags |= ExchangeMessageFlags.ServerSessionCreateNew; if (reqMsg.Flags.HasFlag(ExchangeMessageFlags.PeerKeepAlive)) { rspMsg.Flags |= ExchangeMessageFlags.EchoEndPoint; rspMsg.PeerEndPoint = (IPEndPoint)sae.RemoteEndPoint; } //TODO: handle request // Exchange request // Exchange relay var replyBytes = TransferCodec.Encode(session.PeerRsa, Guid.Empty, rspMsg.ToBytes()); _svrSocket.SendTo(replyBytes, sae.RemoteEndPoint); } else { _svrSocket.SendTo(TransferCodec.InvalidPeerId.ToByteArray(), sae.RemoteEndPoint); } } private static RSACryptoServiceProvider LoadPeerRsa(Guid id) { var peerKeyPath = Path.Combine("Peers", $"{id}.txt"); if (false == File.Exists(peerKeyPath)) return null; var peerRsa = new RSACryptoServiceProvider(); peerRsa.FromXmlString(File.ReadAllText(peerKeyPath)); return peerRsa; } private static void DeadLoopTimeOutKiller() { while (true) { //TimeOutKiller const int timeOutSeconds = 10; foreach (var session in OnlineSessions.Values.ToArray()) { if ((DateTime.Now - session.LastActively).TotalSeconds > timeOutSeconds) { OnlineSessions.TryRemove(session.Id, out _); Console.WriteLine($"Session removed by TimeOutKiller: {session.Id}"); } } Thread.Sleep(timeOutSeconds * 1000); } // ReSharper disable once FunctionNeverReturns } private class OnlineSession { public Guid Id { get; } public DateTime LastActively { get; private set; } public EndPoint RemoteEndPoint { get; } public RSACryptoServiceProvider PeerRsa { get; } public OnlineSession(Guid id, EndPoint remoteEndPoint, RSACryptoServiceProvider peerRsa) { Id = id; RemoteEndPoint = remoteEndPoint; PeerRsa = peerRsa; Actively(); } public void Actively() => LastActively = DateTime.Now; } private static void Main() { Console.WriteLine("Init..."); _serverRsaCryptoServiceProvider = new RSACryptoServiceProvider(); _serverRsaCryptoServiceProvider.FromXmlString(File.ReadAllText("ServerPrivateKey.txt")); _svrSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); _svrSocket.Bind(new IPEndPoint(IPAddress.Any, Properties.Settings.Default.ListenPort)); Console.WriteLine($"Server Bind on {_svrSocket.LocalEndPoint}"); const int receiveBufferSize = 1500; var sae = new SocketAsyncEventArgs(); sae.SetBuffer(new byte[receiveBufferSize], 0, receiveBufferSize); sae.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); Console.WriteLine($"Receive buffer:{receiveBufferSize}"); void SaeOnCompleted(object sender, SocketAsyncEventArgs e) { if (e.SocketError == SocketError.Success) { try { ProcessPacket(sae); } catch (Exception exception) { Console.WriteLine(exception); } } sae.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); if (false == _svrSocket.ReceiveFromAsync(sae)) SaeOnCompleted(null, null); } sae.Completed += SaeOnCompleted; if (false == _svrSocket.ReceiveFromAsync(sae)) { SaeOnCompleted(null, null); } Console.WriteLine($"Main thread enter dead sleep loop... to exit, kill me, pid:{Process.GetCurrentProcess().Id}"); DeadLoopTimeOutKiller(); } } }