using System; using System.Collections.Concurrent; using System.Collections.Generic; 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 readonly ConcurrentDictionary OnlineSessions = new ConcurrentDictionary(); private static RSACryptoServiceProvider _serverKey; private static IReadOnlyDictionary _peerKeyRegister; private static volatile 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; var peerId = TransferCodec.ReadId(sae.Buffer); _peerKeyRegister.TryGetValue(peerId, out var peerKey); if (null == peerKey) { Console.WriteLine($"pacet #{_packetSeq} peer id {peerId} no registered"); _svrSocket.SendTo(BuildInPeerId.Invalid.ToByteArray(), sae.RemoteEndPoint); return; } ExchangeMessage requestMessage; try { var msgData = TransferCodec.DecodeData(_serverKey, sae.Buffer); requestMessage = new ExchangeMessage(msgData); } catch (Exception exception) { Console.WriteLine($"pacet #{_packetSeq} decode fail:{exception}"); _svrSocket.SendTo(BuildInPeerId.Invalid.ToByteArray(), sae.RemoteEndPoint); return; } var responseMessage = new ExchangeMessage { TimeStamp = DateTime.Now }; if (false == requestMessage.TimeStamp.HasValue || Math.Abs((DateTime.Now - requestMessage.TimeStamp.Value).TotalSeconds) > 10) { responseMessage.Id = ExchangeMessageId.ErrTimeStamp; } else { OnlineSessions.TryGetValue(peerId, out var session); if (null == session) { if (ExchangeMessageId.KeepAliveReq == requestMessage.Id) { session = new OnlineSession(peerId, sae.RemoteEndPoint); OnlineSessions[peerId] = session; responseMessage.Id = ExchangeMessageId.KeepAliveAckSessionCreated; responseMessage.PeerEndPoint = (IPEndPoint)sae.RemoteEndPoint; Console.WriteLine($"pacet #{_packetSeq} Session created: {peerId} from {sae.RemoteEndPoint}"); } else { responseMessage.Id = ExchangeMessageId.ErrSessionNoCreated; } } else { session.Actively(); switch (requestMessage.Id) { case ExchangeMessageId.KeepAliveReq: if (false == session.RemoteEndPoint.IpEndPointEqualsTo(sae.RemoteEndPoint)) { session = new OnlineSession(peerId, sae.RemoteEndPoint); OnlineSessions[peerId] = session; responseMessage.Id = ExchangeMessageId.KeepAliveAckSessionCreated; responseMessage.PeerEndPoint = (IPEndPoint)sae.RemoteEndPoint; Console.WriteLine($"pacet #{_packetSeq} Session created: {peerId} from {sae.RemoteEndPoint}"); } else { responseMessage.Id = ExchangeMessageId.KeepAliveAckNoChg; } break; case ExchangeMessageId.PeerKnockReq: if (false == requestMessage.PeerId.HasValue || false == _peerKeyRegister.TryGetValue(requestMessage.PeerId.Value, out var knockKey) || false == OnlineSessions.TryGetValue(requestMessage.PeerId.Value, out var knockSession) ) { Console.WriteLine($"pacet #{_packetSeq} bad knock request: peer id no available {requestMessage.PeerId} by {peerId}"); responseMessage.Id = ExchangeMessageId.PeerKnockReqErrPeerNoAvailable; responseMessage.PeerId = requestMessage.PeerId; break; } Console.WriteLine($"pacet #{_packetSeq} knock to {requestMessage.PeerId} by {peerId}"); var reqRelayMessage = new ExchangeMessage(ExchangeMessageId.PeerKnockReqRelay) { PeerId = peerId, PeerEndPoint = (IPEndPoint)sae.RemoteEndPoint, }; var reqRelayBytes = TransferCodec.Encode(knockKey, BuildInPeerId.Server, reqRelayMessage.ToBytes()); _svrSocket.SendTo(reqRelayBytes, knockSession.RemoteEndPoint); responseMessage.Id = ExchangeMessageId.PeerKnockReqRelayed; responseMessage.PeerId = requestMessage.PeerId; break; case ExchangeMessageId.PeerKnockAck: if (false == requestMessage.PeerId.HasValue || false == _peerKeyRegister.TryGetValue(requestMessage.PeerId.Value, out var knockAckKey) || false == OnlineSessions.TryGetValue(requestMessage.PeerId.Value, out var knockAckSession) ) { Console.WriteLine($"pacet #{_packetSeq} bad knock ack: peer id no available {requestMessage.PeerId}"); responseMessage.Id = ExchangeMessageId.PeerKnockReqErrPeerNoAvailable; responseMessage.PeerId = requestMessage.PeerId; break; } Console.WriteLine($"pacet #{_packetSeq} knock ack {requestMessage.PeerId} by {peerId}"); var ackRelayMessage = new ExchangeMessage(ExchangeMessageId.PeerKnockAckRelay) { PeerId = peerId, PeerEndPoint = (IPEndPoint)sae.RemoteEndPoint, }; _svrSocket.SendExchangeMessageTo(BuildInPeerId.Server, ackRelayMessage, knockAckSession.RemoteEndPoint, knockAckKey); responseMessage.Id = ExchangeMessageId.PeerKnockAckRelayed; responseMessage.PeerId = requestMessage.PeerId; break; case ExchangeMessageId.PeerKnockDenied: break; //case ExchangeMessageId.PeerKnockConnectionReq: // break; //case ExchangeMessageId.PeerKnockConnectionAck: // break; //case ExchangeMessageId.ErrSessionNoCreated: // break; default: throw new ArgumentOutOfRangeException(); } } } _svrSocket.SendExchangeMessageTo(BuildInPeerId.Server, responseMessage, sae.RemoteEndPoint, peerKey); } 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 OnlineSession(Guid id, EndPoint remoteEndPoint) { Id = id; RemoteEndPoint = remoteEndPoint; Actively(); } public void Actively() => LastActively = DateTime.Now; } private static void Main() { Console.WriteLine("Init..."); _serverKey = new RSACryptoServiceProvider(); _serverKey.FromXmlString(File.ReadAllText("ServerPrivateKey.txt")); _peerKeyRegister = Directory.GetFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PeerPublicKey")) .ToDictionary( s => new Guid(Path.GetFileNameWithoutExtension(s)), p => { var rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(File.ReadAllText(p)); return rsa; } ); _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 (sae.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(); } } }