From 3fb344b4ad80262134c8a1b5b8eda07cb84c2c08 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Fri, 19 Jul 2024 13:46:46 +0200 Subject: [PATCH 01/27] refactor on dictionary access on relay client. To be tested --- Benchmarks/RelayBenchmark/Program.cs | 24 +- NetworkLibrary/P2P/Generic/ClientPeerData.cs | 30 +++ NetworkLibrary/P2P/Generic/RelayClientBase.cs | 212 +++++++++++------- NetworkLibrary/P2P/Generic/RelayServerBase.cs | 1 + 4 files changed, 179 insertions(+), 88 deletions(-) create mode 100644 NetworkLibrary/P2P/Generic/ClientPeerData.cs diff --git a/Benchmarks/RelayBenchmark/Program.cs b/Benchmarks/RelayBenchmark/Program.cs index ef52737..38367d5 100644 --- a/Benchmarks/RelayBenchmark/Program.cs +++ b/Benchmarks/RelayBenchmark/Program.cs @@ -187,20 +187,22 @@ private static void RelayTest() //string ip = "79.52.134.220"; string ip = "127.0.0.1"; + MiniLogger.AllLog += Console.WriteLine; - var cert = new X509Certificate2("client.pfx", "greenpass"); - var scert = new X509Certificate2("server.pfx", "greenpass"); + X509Certificate2 cert = null;//new X509Certificate2("client.pfx", "greenpass"); + X509Certificate2 scert = null;//new X509Certificate2("server.pfx", "greenpass"); if (Console.ReadLine() == "e") { var server = new SecureProtoRelayServer(20020, scert); server.StartServer(); - Task.Run(async () => { while (true) { await Task.Delay(5000); server.GetTcpStatistics(out var generalStats, out _); Console.WriteLine(generalStats.ToString()); } }); + Task.Run(async () => { while (true) { await Task.Delay(5000); server.GetTcpStatistics(out var generalStats, out _); Console.WriteLine(generalStats.ToString()); + server.GetUdpStatistics(out var s, out _); Console.WriteLine(s.ToString()); } }); } Console.ReadLine(); //Task.Run(async () => { while (true) { await Task.Delay(10000); server.GetTcpStatistics(out var generalStats, out _); Console.WriteLine(generalStats.ToString()); } }); var clients = new List(); - int numclients = 50; + int numclients = 2; var pending = new Task[numclients]; Task.Run(async () => { while (true) { await Task.Delay(1000); Console.WriteLine(Interlocked.Exchange(ref sumsum, 0).ToString("N0")); } }); @@ -234,6 +236,7 @@ private static void RelayTest() { if (client.SessionId == Guid.Empty) throw new Exception(); + client.StartPingService(); // Console.WriteLine("--- -- - | "+client.sessionId+" count: " + client.Peers.Count); foreach (var peer in client.Peers) { @@ -242,10 +245,10 @@ private static void RelayTest() if (peer.Key == Guid.Empty) throw new Exception(); - //var a = client.RequestTcpHolePunchAsync(peer.Key); - //pndg.Add(a); - // var aa = client.RequestHolePunchAsync(peer.Key, 10000, false); - //pndg.Add(aa); + //var a = client.RequestTcpHolePunchAsync(peer.Key); + //pndg.Add(a); + var aa = client.RequestHolePunchAsync(peer.Key, 10000, false); + pndg.Add(aa); //client.TestHP(peer.Key, 10000, false); // Console.WriteLine(peer.Key+" cnt=> "+ ++cc); } @@ -277,8 +280,8 @@ private static void RelayTest() foreach (var peer in client.Peers.Keys) { //await client.SendRequestAndWaitResponse(peer, testMessage,1000); - client.SendAsyncMessage(peer, testMessage); - //client.SendUdpMessage(peer, testMessage); + //client.SendAsyncMessage(peer, testMessage); + // client.SendUdpMessage(peer, testMessage); // client.SendRudpMessage(peer, testMessage); // client.BroadcastMessage(testMessage); //client.BroadcastUdpMessage(testMessage); @@ -306,6 +309,7 @@ void ClientMsgReceived(RelayClient client, MessageEnvelope reply) void ClientUdpReceived(RelayClient client, MessageEnvelope reply) { + Interlocked.Increment(ref sumsum); // Interlocked.Increment(ref totMsgCl); //for (int i = 0; i < reply.PayloadCount; i++) diff --git a/NetworkLibrary/P2P/Generic/ClientPeerData.cs b/NetworkLibrary/P2P/Generic/ClientPeerData.cs new file mode 100644 index 0000000..105fd16 --- /dev/null +++ b/NetworkLibrary/P2P/Generic/ClientPeerData.cs @@ -0,0 +1,30 @@ +using NetworkLibrary.P2P.Components.Modules; +using System; +using System.Collections.Generic; +using System.Text; +using NetworkLibrary.UDP.Jumbo; +using System.Collections.Concurrent; +using NetworkLibrary.MessageProtocol; + +namespace NetworkLibrary.P2P.Generic +{ + public class ClientPeerData where S : ISerializer, new() + { + internal PeerInformation PeerInfo; + internal List RUdpModules = new List(); + internal JumboModule JumboUdpModule; + internal EndpointGroup punchedEndpoints; + internal AesTcpModule punchedTcpModule; + + internal void Clear() + { + foreach (var item in RUdpModules) + { + item?.Release(); + } + JumboUdpModule.Release(); + + punchedTcpModule.Dispose(); + } + } +} diff --git a/NetworkLibrary/P2P/Generic/RelayClientBase.cs b/NetworkLibrary/P2P/Generic/RelayClientBase.cs index d0964e2..bf42091 100644 --- a/NetworkLibrary/P2P/Generic/RelayClientBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayClientBase.cs @@ -87,6 +87,8 @@ private set } } + public AesMode AESMode = AesMode.GCM; + public ConcurrentDictionary Peers = new ConcurrentDictionary(); internal ConcurrentDictionary PeerInfos { get; private set; } = new ConcurrentDictionary(); internal ConcurrentDictionary> RUdpModules = new ConcurrentDictionary>(); @@ -111,7 +113,6 @@ private set private ConcurrentDictionary> punchedTcpModules = new ConcurrentDictionary>(); internal ConcurrentDictionary peerCryptos = new ConcurrentDictionary(); private ClientStateManager clientStateManager; - public AesMode AESMode = AesMode.GCM; private X509Certificate2 clientCert; private int udpPort = 0; private bool initialised = false; @@ -307,7 +308,7 @@ private void HandleDisconnect() Peers.Clear(); peerCryptos.Clear(); - punchedEndpoints.Clear(); + ClearPunchedEndpoints(); foreach (var item in RUdpModules) { @@ -427,7 +428,7 @@ private void HandlePing(MessageEnvelope message, bool isTcp = true) if (isTcp) { message.To = message.From; - if(punchedTcpModules.TryGetValue(message.From,out var module)) + if(TryGetAesTcpModule(message.From,out var module)) { message.From = sessionId; @@ -473,13 +474,10 @@ public Dictionary GetUdpPingStatus() #endregion Ping - #region Send - - private void SendUdpMesssageInternal(Guid toId, MessageEnvelope message) + #region Wrap + private void GetUdpSendParams(Guid toId, out ConcurrentAesAlgorithm algo, out IPEndPoint endpoint) { - ConcurrentAesAlgorithm algo; - IPEndPoint endpoint; - if (punchedEndpoints.TryGetValue(toId, out EndpointGroup endpoints)) + if (TryGetTraversedEndpoint(toId, out var endpoints)) { endpoint = endpoints.ToSend; algo = peerCryptos[endpoint].algorithm; @@ -488,7 +486,76 @@ private void SendUdpMesssageInternal(Guid toId, MessageEnvelope message) { endpoint = relayServerEndpoint; algo = udpEncryiptor; + } + } + + private bool IsUdpDirectConnectionAvailable(Guid clientId) + { + return punchedEndpoints.ContainsKey(clientId); + } + + private bool TryGetTraversedEndpoint(Guid target, out EndpointGroup ep) + { + return punchedEndpoints.TryGetValue(target, out ep); + } + + private bool AddPunchedEndpoint(Guid clientId,EndpointGroup endpoints) + { + return punchedEndpoints.TryAdd(clientId, endpoints); + } + + private bool RemovePunchedEndpoint(Guid clientId, out EndpointGroup endpoints) + { + return punchedEndpoints.TryRemove(clientId, out endpoints); + } + + private void ClearPunchedEndpoints() + { + punchedEndpoints.Clear(); + } + + private bool TryGetAesTcpModule(Guid clientId, out AesTcpModule module) + { + return punchedTcpModules.TryGetValue(clientId, out module); + } + + private bool AddPunchedTcpModule(Guid clientId, AesTcpModule tcpModule) + { + return punchedTcpModules.TryAdd(clientId, tcpModule); + } + + private bool RemovePunchedTcpModule(Guid clientId, out AesTcpModule tcpModule) + { + return punchedTcpModules.TryRemove(clientId, out tcpModule); + } + + private void ClearPunchedTcpModules() + { + punchedTcpModules.Clear(); + } + + private bool TryGetRudpModules(Guid clientId, out List mod) + { + return RUdpModules.TryGetValue(clientId, out mod); + } + + private bool TryAddRUdpModules(Guid clientId, List mod) + { + return RUdpModules.TryAdd(clientId, mod); + } + + private void TryGetJumboUdpModule() + { + + } + #endregion + + #region Send + + private void SendUdpMesssageInternal(Guid toId, MessageEnvelope message) + { + GetUdpSendParams(toId, out ConcurrentAesAlgorithm algo, out IPEndPoint endpoint); if (message.PayloadCount > maxUdpPackageSize) { @@ -509,7 +576,7 @@ private void SendUdpMesssageInternal(Guid toId, MessageEnvelope message) [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe void SendLargeUdpMessage(Guid toId, MessageEnvelope message) { - // you cat store buffer outside the scope.. + // you cant store buffer outside the scope.. if (message.KeyValuePairs != null) { var buffer = stackalloc byte[65000]; @@ -536,19 +603,7 @@ private unsafe void SendLargeUdpMessage(Guid toId, MessageEnvelope message) private void SendUdpMesssageInternal(Guid toId, MessageEnvelope message, T innerMessage) { - ConcurrentAesAlgorithm algo; - IPEndPoint endpoint; - if (punchedEndpoints.TryGetValue(toId, out var endpoints)) - { - endpoint = endpoints.ToSend; - algo = peerCryptos[endpoint].algorithm; - } - else - { - endpoint = relayServerEndpoint; - algo = udpEncryiptor; - - } + GetUdpSendParams(toId, out ConcurrentAesAlgorithm algo, out IPEndPoint endpoint); if (!udpServer.TrySendAsync(endpoint, message, innerMessage, algo, out var excessStream)) { @@ -563,21 +618,10 @@ private void SendUdpMesssageInternal(Guid toId, MessageEnvelope message, T in private void SendUdpMesssageInternal(Guid toId, MessageEnvelope message, Action serializationCallback) { - ConcurrentAesAlgorithm algo; - IPEndPoint endpoint; - if (punchedEndpoints.TryGetValue(toId, out var endpoints)) - { - endpoint = endpoints.ToSend; - algo = peerCryptos[endpoint].algorithm; - } - else - { - endpoint = relayServerEndpoint; - algo = udpEncryiptor; - - } + + GetUdpSendParams(toId, out ConcurrentAesAlgorithm algo, out IPEndPoint endpoint); - if (!udpServer.TrySendAsync(endpoint, message, serializationCallback, algo, out var excessStream,false)) + if (!udpServer.TrySendAsync(endpoint, message, serializationCallback, algo, out var excessStream, false)) { if (JumboUdpModules.TryGetValue(toId, out var mod)) mod.Send(excessStream.GetBuffer(), 0, excessStream.Position32); @@ -588,7 +632,6 @@ private void SendUdpMesssageInternal(Guid toId, MessageEnvelope message, Action< } - /// /// Sends the UDP message with bytes provided in envelope payload. /// @@ -643,7 +686,10 @@ public void SendUdpMessage(Guid toId, MessageEnvelope message, T innerMessage } - #region Broadcast/Multicast Udp + #region Broadcast/Multicast Udp + + + /// /// Broadcasts the UDP message to all connected peers. /// @@ -656,7 +702,7 @@ public void BroadcastUdpMessage(MessageEnvelope message) bool sendToRelay = false; foreach (var item in Peers) { - if (punchedEndpoints.ContainsKey(item.Key)) + if (IsUdpDirectConnectionAvailable(item.Key)) { message.To = item.Key; SendUdpMesssageInternal(item.Key, message); @@ -675,7 +721,7 @@ public void BroadcastUdpMessage(MessageEnvelope message) // unicast, message is too large. foreach (var item in Peers) { - if (!punchedEndpoints.ContainsKey(item.Key)) + if (!IsUdpDirectConnectionAvailable(item.Key)) { message.To = item.Key; SendUdpMesssageInternal(item.Key, message); @@ -701,7 +747,7 @@ public void BroadcastUdpMessage(MessageEnvelope message, T innerMessage) bool sendToRelay = false; foreach (var item in Peers) { - if (punchedEndpoints.ContainsKey(item.Key)) + if (IsUdpDirectConnectionAvailable(item.Key)) { message.To = item.Key; SendUdpMesssageInternal(item.Key, message, innerMessage); @@ -720,7 +766,7 @@ public void BroadcastUdpMessage(MessageEnvelope message, T innerMessage) // unicast, message is too large. foreach (var item in Peers) { - if (!punchedEndpoints.ContainsKey(item.Key)) + if (!IsUdpDirectConnectionAvailable(item.Key)) { message.To = item.Key; SendUdpMesssageInternal(item.Key, message, innerMessage); @@ -733,13 +779,14 @@ public void BroadcastUdpMessage(MessageEnvelope message, T innerMessage) } internal void MulticastUdpMessage(MessageEnvelope message, ICollection targets) { + if (Peers.Count < 0) return; message.From = sessionId; bool sendToRelay = false; foreach (var target in targets) { - if (punchedEndpoints.TryGetValue(target, out var ep)) + if (TryGetTraversedEndpoint(target, out var ep)) { message.To = target; SendUdpMesssageInternal(target, message); @@ -759,7 +806,7 @@ internal void MulticastUdpMessage(MessageEnvelope message, ICollection tar { if (target == sessionId) continue; - if (!punchedEndpoints.ContainsKey(target)) + if (!IsUdpDirectConnectionAvailable(target)) { message.To = target; SendUdpMesssageInternal(target, message); @@ -778,7 +825,7 @@ internal void MulticastUdpMessage(MessageEnvelope message, ICollection bool sendToRelay = false; foreach (var target in targets) { - if (punchedEndpoints.TryGetValue(target, out var ep)) + if (TryGetTraversedEndpoint(target, out var ep)) { message.To = target; SendUdpMesssageInternal(target, message,innerMessage); @@ -795,7 +842,7 @@ internal void MulticastUdpMessage(MessageEnvelope message, ICollection { foreach (var target in targets) { - if (!punchedEndpoints.ContainsKey(target)) + if (!IsUdpDirectConnectionAvailable(target)) { if (target == sessionId) continue; @@ -804,11 +851,10 @@ internal void MulticastUdpMessage(MessageEnvelope message, ICollection SendUdpMesssageInternal(target, message,innerMessage); } } - } } - } + #endregion /// @@ -823,7 +869,7 @@ public void BroadcastMessage(MessageEnvelope message) //message.To = Guid.Empty; foreach (var peer in Peers) { - if (punchedTcpModules.TryGetValue(peer.Key, out var module)) + if (TryGetAesTcpModule(peer.Key, out var module)) { // message.To = peer.Key; module.SendAsync(message); @@ -866,7 +912,7 @@ public void SendAsyncMessage(Guid toId, MessageEnvelope message) message.From = sessionId; message.To = toId; - if(punchedTcpModules.TryGetValue(toId, out var module)) + if(TryGetAesTcpModule(toId, out var module)) { module.SendAsync(message); } @@ -888,7 +934,7 @@ public void SendAsyncMessage(Guid toId, MessageEnvelope envelope, T message) envelope.From = sessionId; envelope.To = toId; - if (punchedTcpModules.TryGetValue(toId, out var module)) + if (TryGetAesTcpModule(toId, out var module)) { module.SendAsync(envelope, message); } @@ -909,7 +955,7 @@ public void SendAsyncMessage(Guid toId, MessageEnvelope envelope, Action(Guid toId, T message, string messageHeader = nul }; envelope.Header = messageHeader == null ? typeof(T).Name : messageHeader; - if (punchedTcpModules.TryGetValue(toId, out var module)) + if (TryGetAesTcpModule(toId, out var module)) { module.SendAsync(envelope,message); } @@ -963,13 +1009,14 @@ public Task SendRequestAndWaitResponse(Guid toId, T message, MessageId = Guid.NewGuid(), Header = messageHeader == null ? typeof(T).Name : messageHeader }; + Task task; - if (punchedTcpModules.TryGetValue(toId, out var module)) + if (TryGetAesTcpModule(toId, out var module)) { task = module.SendMessageAndWaitResponse(envelope, message,timeoutMs); } else - task = tcpMessageClient.SendMessageAndWaitResponse(envelope, message, timeoutMs); + task = tcpMessageClient.SendMessageAndWaitResponse(envelope, message, timeoutMs); return task; } @@ -987,7 +1034,7 @@ public Task SendRequestAndWaitResponse(Guid toId, MessageEnvelo message.To = toId; Task task; - if (punchedTcpModules.TryGetValue(toId, out var module)) + if (TryGetAesTcpModule(toId, out var module)) { task = module.SendMessageAndWaitResponse(message, timeoutMs); } @@ -1011,7 +1058,7 @@ public Task SendRequestAndWaitResponse(Guid toId, MessageEnv envelope.From = sessionId; envelope.To = toId; Task task; - if (punchedTcpModules.TryGetValue(toId, out var module)) + if (TryGetAesTcpModule(toId, out var module)) { task = module.SendMessageAndWaitResponse(envelope, message, timeoutMs); } @@ -1061,7 +1108,16 @@ void ParseMessage(byte[] decryptedBytes, int byteOffset, int byteCount) if(msg.To == Guid.Empty) msg.To = sessionId; if(adress != relayServerEndpoint && msg.From == Guid.Empty) - msg.From = peerCryptos[adress].id; + { + if (crypto == null) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, "Unable to find decrptor from " + adress); + return; + } + + msg.From = crypto.id; + } + if (!clientStateManager.HandleMessage(adress, msg)) { HandleUdpMessageReceived(msg); @@ -1073,7 +1129,7 @@ void ParseMessage(byte[] decryptedBytes, int byteOffset, int byteCount) catch (Exception e) { var b = decryptBuffer; - MiniLogger.Log(MiniLogger.LogLevel.Error, "Relay Client Failed to deserialise envelope message " + e.Message); + MiniLogger.Log(MiniLogger.LogLevel.Error, "Relay Client Failed to deserialise envelope message " + e.Message +"\n"+ e.StackTrace); } } @@ -1102,19 +1158,19 @@ private void HandleUdpMessageReceived(MessageEnvelope message) break; // reliable udp case Constants.Rudp: - if (RUdpModules.TryGetValue(message.From, out var mod)) + if (TryGetRudpModules(message.From, out var mod)) { mod[0].HandleBytes(message.Payload, message.PayloadOffset, message.PayloadCount); } break; case Constants.Rudp1: - if (RUdpModules.TryGetValue(message.From, out var mod2)) + if (TryGetRudpModules(message.From, out var mod2)) { mod2[1].HandleBytes(message.Payload, message.PayloadOffset, message.PayloadCount); } break; case Constants.Rudp2: - if (RUdpModules.TryGetValue(message.From, out var mod3)) + if (TryGetRudpModules(message.From, out var mod3)) { mod3[2].HandleBytes(message.Payload, message.PayloadOffset, message.PayloadCount); } @@ -1207,7 +1263,7 @@ public Task RequestHolePunchAsync(Guid peerId, int timeOut, bool encrypted { return Task.FromResult(false); } - if (punchedEndpoints.ContainsKey(peerId)) + if (IsUdpDirectConnectionAvailable(peerId)) { return Task.FromResult(true); } @@ -1322,8 +1378,8 @@ internal void HandleHolepunchSuccess(ClientHolepunchState state) algorithm = new ConcurrentAesAlgorithm(state.cryptoKey, AESMode) }); } - - punchedEndpoints.TryAdd(state.destinationId, new EndpointGroup() { ToReceive = state.succesfulEpToReceive,ToSend = state.succesfullEpToSend }); + + AddPunchedEndpoint(state.destinationId, new EndpointGroup() { ToReceive = state.succesfulEpToReceive,ToSend = state.succesfullEpToSend }); MiniLogger.Log(MiniLogger.LogLevel.Info, $"HolePunched, Receive Endpoint: {state.succesfulEpToReceive}, Send Endpoint {state.succesfullEpToSend}"); } @@ -1432,13 +1488,13 @@ protected internal void HandleUnRegistered(Guid peerId) { Peers.TryRemove(peerId, out _); - if (punchedEndpoints.TryRemove(peerId, out var ep) && ep != null) + if (RemovePunchedEndpoint(peerId, out var ep) && ep != null) { peerCryptos.TryRemove(ep.ToReceive, out _); peerCryptos.TryRemove(ep.ToSend, out _); } - if(punchedTcpModules.TryRemove(peerId,out var module)) + if(RemovePunchedTcpModule(peerId,out var module)) { module.Dispose(); } @@ -1503,7 +1559,7 @@ private void SendUdpRaw(Guid toId, MessageEnvelope message, byte[] b, int o, int if (!Peers.TryGetValue(toId, out _) && toId != sessionId) return; int offset = 0; - if (punchedEndpoints.ContainsKey(toId)) + if (IsUdpDirectConnectionAvailable(toId)) { message.To = Guid.Empty; message.From = Guid.Empty; @@ -1526,7 +1582,7 @@ private void SendUdpRaw(Guid toId, MessageEnvelope message, byte[] b, int o, int ConcurrentAesAlgorithm algo; IPEndPoint endpoint; - if (punchedEndpoints.TryGetValue(toId, out var endpoints)) + if (TryGetTraversedEndpoint(toId, out var endpoints)) { endpoint = endpoints.ToSend; algo = peerCryptos[endpoint].algorithm; @@ -1596,7 +1652,7 @@ private void CreateRudpModule(Guid peer) mod1, mod2 }; - RUdpModules.TryAdd(peer, l); + TryAddRUdpModules(peer, l); } @@ -1643,7 +1699,7 @@ private void FrameRudpMessage(Guid toId,int ch, byte[] b, int o, int c) /// The channel. public void SendRudpMessage(Guid to, MessageEnvelope msg, RudpChannel channel = RudpChannel.Ch1) { - if (RUdpModules.TryGetValue(to, out var mod)) + if (TryGetRudpModules(to, out var mod)) { //WriteRudpRouterHeaderIfNeeded(msg, to); msg = MessageEnvelope.CloneWithNoRouter(msg); @@ -1675,7 +1731,7 @@ public void SendRudpMessage(Guid to, MessageEnvelope msg, RudpChannel channel = /// The channel. public void SendRudpMessage(Guid to, MessageEnvelope msg, T innerMessage, RudpChannel channel = RudpChannel.Ch1) { - if (RUdpModules.TryGetValue(to, out var mod)) + if (TryGetRudpModules(to, out var mod)) { //WriteRudpRouterHeaderIfNeeded(msg, to); msg = MessageEnvelope.CloneWithNoRouter(msg); @@ -1699,7 +1755,7 @@ public void SendRudpMessage(Guid to, MessageEnvelope msg, T innerMessage, Rud /// public Task SendRudpMessageAndWaitResponse(Guid to, MessageEnvelope msg, int timeoutMs = 10000, RudpChannel channel = RudpChannel.Ch1) { - if (RUdpModules.TryGetValue(to, out var mod)) + if (TryGetRudpModules(to, out var mod)) { //WriteRudpRouterHeaderIfNeeded(msg, to); msg = MessageEnvelope.CloneWithNoRouter(msg); @@ -1739,7 +1795,7 @@ public Task SendRudpMessageAndWaitResponse(Guid to, MessageEnve /// public Task SendRudpMessageAndWaitResponse(Guid to, MessageEnvelope msg, T innerMessage, int timeoutMs = 10000, RudpChannel channel = RudpChannel.Ch1) { - if (RUdpModules.TryGetValue(to, out var mod)) + if (TryGetRudpModules(to, out var mod)) { //WriteRudpRouterHeaderIfNeeded(msg, to); msg = MessageEnvelope.CloneWithNoRouter(msg); @@ -1765,7 +1821,7 @@ private void WriteRudpRouterHeaderIfNeeded(MessageEnvelope msg,Guid to) msg.To = Guid.Empty; msg.From = Guid.Empty; return; - if (punchedEndpoints.ContainsKey(to)) + if (IsUdpDirectConnectionAvailable(to)) { msg.To = Guid.Empty; msg.From = Guid.Empty; @@ -1840,7 +1896,7 @@ public void Dispose() { module.Value.Dispose(); } - punchedTcpModules.Clear(); + ClearPunchedTcpModules(); } } @@ -1860,7 +1916,7 @@ internal void RegisterTcpNode(IState istate) } module.OnMessageReceived += HandleMessageReceived; - punchedTcpModules.TryAdd(state.destinationId, module); + AddPunchedTcpModule(state.destinationId, module); Console.WriteLine("REG TCP HP!!"); } diff --git a/NetworkLibrary/P2P/Generic/RelayServerBase.cs b/NetworkLibrary/P2P/Generic/RelayServerBase.cs index 529c049..88afd36 100644 --- a/NetworkLibrary/P2P/Generic/RelayServerBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayServerBase.cs @@ -252,6 +252,7 @@ protected virtual void HandleMessageReceivedInternal(Guid clientId, MessageEnvel break; default: + //Console.WriteLine($"Internal message getting routed Header = {message.Header}"); SendAsyncMessage(message.To, message); break; } From 49a1803cdc9c59e2d423c881a5dff2de14f29115 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Thu, 1 Aug 2024 15:52:07 +0200 Subject: [PATCH 02/27] lobby server/client features. Distributed clock sync(NTP,PTP), Batch room messages. --- .../MessageBuffer/EncryptedMessageBuffer.cs | 26 + .../Interface/IMessageProcessQueue.cs | 2 + .../Components/MessageBuffer/MessageBuffer.cs | 26 + .../Components/MessageBuffer/MessageQueue.cs | 25 +- .../Serialization/PrimitiveEncoder.cs | 22 + .../HolePunch/KnownTypeSerializer.cs | 10 + NetworkLibrary/P2P/Components/InfoMessages.cs | 1 + NetworkLibrary/P2P/Constants.cs | 5 +- NetworkLibrary/P2P/Generic/RelayClientBase.cs | 179 ++++++- NetworkLibrary/P2P/Generic/RelayServerBase.cs | 26 +- .../P2P/Generic/Room/SecureLobbyClient.cs | 89 +++- .../P2P/Generic/Room/SecureLobbyServer.cs | 476 +++++++++++++++--- NetworkLibrary/TCP/Base/Core/IAsyncSession.cs | 3 + NetworkLibrary/TCP/Base/TcpSession.cs | 64 +++ NetworkLibrary/TCP/SSL/SslServer.cs | 7 + NetworkLibrary/TCP/SSL/SslSession.cs | 62 +++ NetworkLibrary/Utils/AsyncDispatcher.cs | 118 +++++ NetworkLibrary/Utils/Statistics.cs | 49 ++ Tests/UnitTests/LobbyServerTest.cs | 215 ++++++++ 19 files changed, 1313 insertions(+), 92 deletions(-) create mode 100644 NetworkLibrary/Utils/AsyncDispatcher.cs create mode 100644 NetworkLibrary/Utils/Statistics.cs diff --git a/NetworkLibrary/Components/MessageBuffer/EncryptedMessageBuffer.cs b/NetworkLibrary/Components/MessageBuffer/EncryptedMessageBuffer.cs index cf2f815..2e32215 100644 --- a/NetworkLibrary/Components/MessageBuffer/EncryptedMessageBuffer.cs +++ b/NetworkLibrary/Components/MessageBuffer/EncryptedMessageBuffer.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Drawing; using System.Threading; namespace NetworkLibrary.Components.MessageBuffer @@ -152,5 +154,29 @@ public void Flush() { flushStream.Clear(); } + + public bool TryEnqueueMessage(List> segments) + { + foreach (var segment in segments) + { + if (currentIndexedMemory < MaxIndexedMemory && !disposedValue) + { + + + writeStream.Reserve(segment.Count + 256); + + int amount = aesAlgorithm.EncryptInto(segment.Array, segment.Offset, segment.Count, writeStream.GetBuffer(), writeStream.Position32 + 4); + + writeStream.WriteInt(amount); + writeStream.Position32 += amount; + + currentIndexedMemory += segment.Count; + } + else return false; + } + TotalMessageDispatched++; + + return true; + } } } diff --git a/NetworkLibrary/Components/MessageBuffer/Interface/IMessageProcessQueue.cs b/NetworkLibrary/Components/MessageBuffer/Interface/IMessageProcessQueue.cs index 26e0153..786501c 100644 --- a/NetworkLibrary/Components/MessageBuffer/Interface/IMessageProcessQueue.cs +++ b/NetworkLibrary/Components/MessageBuffer/Interface/IMessageProcessQueue.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace NetworkLibrary.Components { @@ -17,6 +18,7 @@ public interface IMessageQueue : IDisposable /// /// true if message is enqueued. bool TryEnqueueMessage(byte[] bytes, int offset, int count); + bool TryEnqueueMessage(List> segments); /// /// Flushes the queue if there is anything to flush. diff --git a/NetworkLibrary/Components/MessageBuffer/MessageBuffer.cs b/NetworkLibrary/Components/MessageBuffer/MessageBuffer.cs index c01bcf1..47234e2 100644 --- a/NetworkLibrary/Components/MessageBuffer/MessageBuffer.cs +++ b/NetworkLibrary/Components/MessageBuffer/MessageBuffer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Drawing; using System.Threading; @@ -153,5 +154,30 @@ public void Flush() { flushStream.Clear(); } + + public bool TryEnqueueMessage(List> segments) + { + lock (bufferMtex) + { + foreach (var segment in segments) + { + if (currentIndexedMemory < MaxIndexedMemory && !disposedValue) + { + if (writeLengthPrefix) + { + currentIndexedMemory += 4; + writeStream.WriteInt(segment.Count); + } + + writeStream.Write(segment.Array, segment.Offset, segment.Count); + currentIndexedMemory += segment.Count; + + } + else return false; + } + TotalMessageDispatched++; + } + return true; + } } } diff --git a/NetworkLibrary/Components/MessageBuffer/MessageQueue.cs b/NetworkLibrary/Components/MessageBuffer/MessageQueue.cs index 8a5e0b3..f9b487c 100644 --- a/NetworkLibrary/Components/MessageBuffer/MessageQueue.cs +++ b/NetworkLibrary/Components/MessageBuffer/MessageQueue.cs @@ -1,5 +1,8 @@ using NetworkLibrary.Utils; +using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Drawing; using System.Runtime.CompilerServices; using System.Threading; @@ -30,6 +33,7 @@ public bool TryEnqueueMessage(byte[] bytes) { Interlocked.Add(ref currentIndexedMemory, bytes.Length); SendQueue.Enqueue(bytes); + totalMessageFlushed++; return true; } return false; @@ -52,8 +56,6 @@ public bool TryFlushQueue(ref byte[] buffer, int offset, out int amountWritten) int memcount = 0; while (SendQueue.TryDequeue(out byte[] bytes)) { - totalMessageFlushed++; - memcount += bytes.Length; if (!processor.ProcessMessage(bytes)) { @@ -85,5 +87,24 @@ public void Dispose() } public void Flush() { } + + public bool TryEnqueueMessage(List> segments) + { + foreach (var segment in segments) + { + var array = ByteCopy.ToArray(segment.Array, segment.Offset, segment.Count); + if (Volatile.Read(ref currentIndexedMemory) < MaxIndexedMemory) + { + Interlocked.Add(ref currentIndexedMemory, array.Length); + SendQueue.Enqueue(array); + } + else + { + return false; + } + } + totalMessageFlushed++; + return true; + } } } diff --git a/NetworkLibrary/MessageProtocol/Serialization/PrimitiveEncoder.cs b/NetworkLibrary/MessageProtocol/Serialization/PrimitiveEncoder.cs index a2335ac..db665c2 100644 --- a/NetworkLibrary/MessageProtocol/Serialization/PrimitiveEncoder.cs +++ b/NetworkLibrary/MessageProtocol/Serialization/PrimitiveEncoder.cs @@ -545,6 +545,28 @@ public static unsafe void WriteDouble(PooledMemoryStream stream, double value) ulong v = *(ulong*)&value; WriteUInt64(stream, v); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void WriteFixedDouble(byte[] buffer, int offset, double value) + { + + fixed( byte* b = &buffer[offset]) + { + *(double*)b = value; + } + + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe double ReadFixedDouble(byte[] buffer, int offset) + { + double ret = 0; + fixed (byte* b = &buffer[offset]) + { + ret = *(double*)b; + } + return ret; + + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe double ReadDouble(PooledMemoryStream stream) diff --git a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs index fa3876a..8d91d93 100644 --- a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs +++ b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs @@ -145,6 +145,11 @@ public static void SerializePeerInfo(PooledMemoryStream stream, PeerInfo data) stream.WriteUshort(data.Port); index += 2; } + if (data.RegisteryTime != default) + { + PrimitiveEncoder.WriteDouble(stream, data.RegisteryTime); + index += 4; + } var buf = stream.GetBuffer(); buf[oldPos] = index; @@ -170,6 +175,11 @@ public static PeerInfo DeserializePeerInfo(byte[] buffer, ref int offset) offset += 2; } + if ((index & 1 << 2) != 0) + { + data.RegisteryTime = PrimitiveEncoder.ReadDouble(buffer, ref offset); + } + return data; } #endregion diff --git a/NetworkLibrary/P2P/Components/InfoMessages.cs b/NetworkLibrary/P2P/Components/InfoMessages.cs index 189e934..bbc4387 100644 --- a/NetworkLibrary/P2P/Components/InfoMessages.cs +++ b/NetworkLibrary/P2P/Components/InfoMessages.cs @@ -12,6 +12,7 @@ public class PeerInfo { public byte[] Address { get; set; } public ushort Port { get; set; } + public double RegisteryTime { get; set; } } public class RoomPeerList diff --git a/NetworkLibrary/P2P/Constants.cs b/NetworkLibrary/P2P/Constants.cs index 03c2d90..3842a30 100644 --- a/NetworkLibrary/P2P/Constants.cs +++ b/NetworkLibrary/P2P/Constants.cs @@ -22,6 +22,7 @@ public class Constants public const string RoomUpdate = "12"; + public const string RoomAvalibalilityUpdate = "12a"; public const string JoinRoom = "13"; public const string LeaveRoom = "14"; public const string GetAvailableRooms = "15"; @@ -32,7 +33,9 @@ public class Constants public const string Judp = "20"; // key of a dict, picked some unusual control char. - public const string RoomName = "\v"; + public const string RoomBroadcast = "\v"; + public const string RoomBroadcastBatched = "\t"; + public const string TimeSync = "ntp"; public const string ReqTCPHP = "ReqTCPHP"; public const string TcpPortMap = "TcpPortMap"; diff --git a/NetworkLibrary/P2P/Generic/RelayClientBase.cs b/NetworkLibrary/P2P/Generic/RelayClientBase.cs index bf42091..bac1a4c 100644 --- a/NetworkLibrary/P2P/Generic/RelayClientBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayClientBase.cs @@ -14,7 +14,10 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; +using System.IO; +using System.Linq; using System.Net; using System.Net.Security; using System.Net.Sockets; @@ -116,6 +119,9 @@ private set private X509Certificate2 clientCert; private int udpPort = 0; private bool initialised = false; + private Stopwatch clientClock = new Stopwatch(); + double timeOffset; + long syncCount = 0; public RelayClientBase(X509Certificate2 clientCert, int udpPort = 0) { if (clientCert == null) @@ -142,8 +148,175 @@ private void Initialise() udpServer.SocketSendBufferSize = 12800000; udpServer.OnBytesRecieved += HandleUdpBytesReceived; udpServer.StartServer(); + + clientClock.Start(); + } + public double GetTime() + { + return clientClock.Elapsed.TotalMilliseconds + timeOffset; + } + + AsyncDispatcher timesyncOperation; + public void StartAutoTimeSync(int periodMs,bool disconnectOnTimeout,bool usePTP = false) + { + Interlocked.Exchange(ref timesyncOperation, new AsyncDispatcher())?.Abort(); + timesyncOperation.LoopPeriodicTask(async () => + { + try + { + if (Interlocked.CompareExchange(ref disposed, 0, 0) == 0) + { + if (IsConnected) + { + bool result = await SyncTime(usePTP).ConfigureAwait(false); + if (disconnectOnTimeout && result == false) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, "TimeSync operation timed out "); + Disconnect(); + } + } + + } + else + { + timesyncOperation.Abort(); + } + } + catch(Exception e) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, "TimeSync operation failed with exception " + e.Message); + Disconnect(); + } + + }, periodMs).ConfigureAwait(false); + } + + public void StopAutoTimeSync() + { + timesyncOperation?.Abort(); + } + + + List timesHistory = new List(); + private static readonly SemaphoreSlim asyncLock = new SemaphoreSlim(1, 1); + public async Task SyncTime(bool usePtp = false) + { + try + { + await asyncLock.WaitAsync(); + + if (!IsConnected) + return false; + + int sampleSize = 3; + var sCnt = Interlocked.CompareExchange(ref syncCount, 0, 0); + + if (sCnt == 0) + sampleSize = 12; + + if (sCnt > 50) + sampleSize = 2; + + if (sCnt > 100) + sampleSize = 1; + + + for (int i = 0; i < sampleSize; i++) + { + var result = usePtp?await GetOffsetPTP(): await GetOffsetNTP(); + if (result.Succes) + { + timesHistory.Add(result.Value); + } + else return false; + } + + if(timesHistory.Count<4) + return false; + + var times = Statistics.FilterOutliers(timesHistory); + if (timesHistory.Count > 600) + { + timesHistory = timesHistory.Skip(60).ToList(); + } + + double average = times.Sum() / times.Count(); + timeOffset = average; + + Interlocked.Increment(ref syncCount); + return true; + + } + finally + { + asyncLock.Release(); + } + } - public void GetTcpStatistics(out TcpStatistics stats) => tcpMessageClient.GetStatistics(out stats); + class TimeResult { public double Value; public bool Succes; } + private async Task GetOffsetNTP() + { + var msg = new MessageEnvelope() + { + Header = Constants.TimeSync, + IsInternal = true, + }; + var now = clientClock.Elapsed.TotalMilliseconds; + var response = await tcpMessageClient.SendMessageAndWaitResponse(msg, 10000); + if (response.Header != MessageEnvelope.RequestTimeout) + { + + var serverTime = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); + var now1 = clientClock.Elapsed.TotalMilliseconds; + + var timeOffset = ((serverTime - now) + (serverTime - now1)) / 2; + return new TimeResult() { Value = timeOffset, Succes = true }; + } + return new TimeResult(); + + } + + private async Task GetOffsetPTP() + { + + var t1 = await GetServerTime(); + if (t1.Succes) + { + var t2 = clientClock.Elapsed.TotalMilliseconds; + + var t3 = t2; + var t4 = await GetServerTime(); + + if (t4.Succes) + { + double offset = ( ((t4.Value - t3) - (t2 - t1.Value)) / 2); + return new TimeResult() { Value = offset, Succes = true }; + } + else + return new TimeResult(); + } + else + return new TimeResult(); + + } + + private async Task GetServerTime() + { + var msg = new MessageEnvelope() + { + Header = Constants.TimeSync, + IsInternal = true, + }; + var response = await tcpMessageClient.SendMessageAndWaitResponse(msg, 10000); + if (response.Header != MessageEnvelope.RequestTimeout) + { + var serverTime = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); + return new TimeResult() { Value = serverTime, Succes = true }; + } + return new TimeResult(); + + } + public void GetTcpStatistics(out TcpStatistics stats) => tcpMessageClient.GetStatistics(out stats); private bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { @@ -242,8 +415,10 @@ public async Task ConnectAsync(string host, int port) IsConnected = true; pinger.PeerRegistered(sessionId); + timesHistory.Clear(); + syncCount = 0; + stateCompletion.SetResult(true); - } else stateCompletion.TrySetException(new TimeoutException()); }; diff --git a/NetworkLibrary/P2P/Generic/RelayServerBase.cs b/NetworkLibrary/P2P/Generic/RelayServerBase.cs index 88afd36..6fd0e57 100644 --- a/NetworkLibrary/P2P/Generic/RelayServerBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayServerBase.cs @@ -11,6 +11,8 @@ 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; @@ -27,7 +29,7 @@ namespace NetworkLibrary.P2P.Generic private AsyncUdpServer udpServer; internal GenericMessageSerializer serialiser = new GenericMessageSerializer(); - private ConcurrentDictionary RegisteredPeers = new ConcurrentDictionary(); + protected ConcurrentDictionary RegisteredPeers = new ConcurrentDictionary(); private ConcurrentDictionary RegisteredUdpEndpoints = new ConcurrentDictionary(); private ConcurrentDictionary> ClientUdpEndpoints = new ConcurrentDictionary>(); private ConcurrentDictionary UdpCryptos = new ConcurrentDictionary(); @@ -42,6 +44,7 @@ internal ConcurrentDictionary> peerReac public AesMode AESMode; bool initialised = false; int port = 0; + Stopwatch serverClock = new Stopwatch(); public SecureRelayServerBase(int port, X509Certificate2 certificate, string serverName = "Andromeda") : base(port, certificate) { this.port = port; @@ -64,9 +67,15 @@ public SecureRelayServerBase(int port, string serverName = "Andromeda") : base(p } public override void StartServer() { + serverClock.Start(); Initialise(); base.StartServer(); } + + public virtual double GetTime() + { + return serverClock.Elapsed.TotalMilliseconds; + } private void Initialise() { if(initialised) return; @@ -141,7 +150,9 @@ protected virtual void NotifyCurrentPeerList(Guid clientId) peerList[peer.Key] = new PeerInfo() { Address = GetIPEndPoint(peer.Key).Address.GetAddressBytes(), - Port = (ushort)GetIPEndPoint(peer.Key).Port + Port = (ushort)GetIPEndPoint(peer.Key).Port, + RegisteryTime = peer.Value + }; } } @@ -169,7 +180,7 @@ internal void Register(Guid clientId, IPEndPoint remoteEndpoint, List client.SessionId; public bool IsConnected => client.IsConnected; - public Action OnPeerJoinedRoom; - public Action OnPeerLeftRoom; + public Action OnPeerJoinedRoom; + public Action OnPeerLeftRoom; public Action OnPeerDisconnected; //public Action OnTcpRoomMesssageReceived; //public Action OnUdpRoomMesssageReceived; public Action OnTcpMessageReceived; public Action OnUdpMessageReceived; + public Action> AvailableRoomsChanged; public Action OnDisconnected; public RemoteCertificateValidationCallback RemoteCertificateValidationCallback; private RelayClientBase client; @@ -35,7 +37,6 @@ private ConcurrentDictionary // [peerId] => Collection private ConcurrentDictionary> peersInRooms = new ConcurrentDictionary>(); - public SecureLobbyClient(X509Certificate2 clientCert) { client = new RelayClientBase(clientCert); @@ -43,9 +44,14 @@ public SecureLobbyClient(X509Certificate2 clientCert) client.OnUdpMessageReceived += HandleUdpMessage; client.OnDisconnected += HandleDisconnected; client.RemoteCertificateValidationCallback += CertificateValidation; - + } + public Task SyncTime() => client.SyncTime(); + public void StartAutoTimeSync(int periodMS, bool disconnectOnTimeout, bool usePTP = false) => client.StartAutoTimeSync(periodMS,disconnectOnTimeout, usePTP); + + public void StopAutoTimeSync()=>client.StopAutoTimeSync(); + public double GetTime() => client.GetTime(); private bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (RemoteCertificateValidationCallback == null) @@ -78,14 +84,15 @@ public Task RequestTcpHolePunchAsync(Guid destinationId, int timeot = 1000 return client.RequestTcpHolePunchAsync(destinationId); } - public void CreateOrJoinRoom(string roomName) + public RoomPeerList CreateOrJoinRoom(string roomName) { - _ = CreateOrJoinRoomAsync(roomName).Result; + return CreateOrJoinRoomAsync(roomName).Result; } - public Task CreateOrJoinRoomAsync(string roomName) + + public Task CreateOrJoinRoomAsync(string roomName) { - var returnVal = new TaskCompletionSource(); + var returnVal = new TaskCompletionSource(); var message = new MessageEnvelope(); message.IsInternal = true; @@ -102,12 +109,13 @@ public Task CreateOrJoinRoomAsync(string roomName) { if (response.Result.Header != MessageEnvelope.RequestTimeout) { - returnVal.TrySetResult(true); + var currentList = KnownTypeSerializer.DeserializeRoomPeerList(response.Result.Payload, response.Result.PayloadOffset); + returnVal.TrySetResult(currentList); } else { rooms.TryRemove(roomName, out _); - returnVal.TrySetResult(false); + returnVal.TrySetResult(null); } }); return returnVal.Task; @@ -167,9 +175,19 @@ private void PrepareEnvelopeBC(string roomName, ref MessageEnvelope messageEnvel if (messageEnvelope.KeyValuePairs == null) messageEnvelope.KeyValuePairs = new Dictionary(); - messageEnvelope.KeyValuePairs[Constants.RoomName] = roomName; + messageEnvelope.KeyValuePairs[Constants.RoomBroadcast] = roomName; messageEnvelope.To = Guid.Empty; - messageEnvelope.From = client.SessionId; + messageEnvelope.From = Guid.Empty; + } + + private void PrepareEnvelopeBCBatched(string roomName, ref MessageEnvelope messageEnvelope) + { + if (messageEnvelope.KeyValuePairs == null) + messageEnvelope.KeyValuePairs = new Dictionary(); + + messageEnvelope.KeyValuePairs[Constants.RoomBroadcastBatched] = roomName; + messageEnvelope.To = Guid.Empty; + messageEnvelope.From = Guid.Empty; } private void PrepareEnvelopeDM(string roomName, ref MessageEnvelope messageEnvelope) @@ -177,7 +195,7 @@ private void PrepareEnvelopeDM(string roomName, ref MessageEnvelope messageEnvel if (messageEnvelope.KeyValuePairs == null) messageEnvelope.KeyValuePairs = new Dictionary(); - messageEnvelope.KeyValuePairs[Constants.RoomName] = roomName; + messageEnvelope.KeyValuePairs[Constants.RoomBroadcast] = roomName; messageEnvelope.From = client.SessionId; } @@ -252,6 +270,27 @@ public void BroadcastRudpMessageToRoom(string roomName, MessageEnvelope messa } } } + + //--- + public void BroadcastMessageToRoomBatched(string roomName, MessageEnvelope message) + { + if (CanSend(roomName)) + { + PrepareEnvelopeBCBatched(roomName, ref message); + client.tcpMessageClient.SendAsyncMessage(message); + } + } + + public void BroadcastMessageToRoomBatched(string roomName, MessageEnvelope message, T innerMessage) + { + if (CanSend(roomName)) + { + PrepareEnvelopeBCBatched(roomName, ref message); + client.tcpMessageClient.SendAsyncMessage(message, innerMessage); + } + } + + //--- #endregion #region Direct Messages @@ -334,6 +373,10 @@ private void HandleMessage(MessageEnvelope message) { UpdateRooms(message); } + else if(message.Header == Constants.RoomAvalibalilityUpdate) + { + AvailableRoomsChanged?.Invoke(message.KeyValuePairs?.Keys?.ToList()?? new List()); + } else if (message.Header == Constants.PeerDisconnected) { HandlePeerDisconnected(message); @@ -359,13 +402,16 @@ private void UpdateRooms(MessageEnvelope message) var roomUpdateMessage = KnownTypeSerializer.DeserializeRoomPeerList(message.Payload, message.PayloadOffset); Dictionary JoinedList = new Dictionary(); - List LeftList = new List(); + Dictionary LeftList = new Dictionary(); var remoteList = roomUpdateMessage.Peers.PeerIds; if (!rooms.TryGetValue(roomUpdateMessage.RoomName, out var localRoom)) { return; } + + if(remoteList.ContainsKey(SessionId)) + remoteList.Remove(SessionId); foreach (var remotePeer in remoteList) { @@ -380,7 +426,8 @@ private void UpdateRooms(MessageEnvelope message) { if (!remoteList.ContainsKey(localPeerId)) { - LeftList.Add(localPeerId); + localRoom.TryGetPeerInfo(localPeerId, out PeerInfo peerInfo);// cant fail + LeftList[localPeerId] = peerInfo; } } @@ -399,28 +446,28 @@ private void UpdateRooms(MessageEnvelope message) peersInRooms[peerKV.Key].TryAdd(roomUpdateMessage.RoomName, null); client.HandleRegistered(peerKV.Key, roomUpdateMessage.Peers.PeerIds); - OnPeerJoinedRoom?.Invoke(roomUpdateMessage.RoomName, peerKV.Key); + OnPeerJoinedRoom?.Invoke(roomUpdateMessage.RoomName, peerKV.Key, peerKV.Value); } foreach (var peerId in LeftList) { if (rooms.TryGetValue(roomUpdateMessage.RoomName, out var room)) { - room.Remove(peerId); + room.Remove(peerId.Key); } - if (peersInRooms.TryGetValue(peerId, out var roomList)) + if (peersInRooms.TryGetValue(peerId.Key, out var roomList)) { roomList.TryRemove(roomUpdateMessage.RoomName, out _); if(roomList.Count == 0) { - peersInRooms.TryRemove(peerId, out _); - HandlePeerDisconnected_(peerId); + peersInRooms.TryRemove(peerId.Key, out _); + HandlePeerDisconnected_(peerId.Key); } } //client.HandleUnRegistered(peerId); - OnPeerLeftRoom?.Invoke(roomUpdateMessage.RoomName, peerId); + OnPeerLeftRoom?.Invoke(roomUpdateMessage.RoomName, peerId.Key, peerId.Value); } } diff --git a/NetworkLibrary/P2P/Generic/Room/SecureLobbyServer.cs b/NetworkLibrary/P2P/Generic/Room/SecureLobbyServer.cs index fd1e6ba..720be21 100644 --- a/NetworkLibrary/P2P/Generic/Room/SecureLobbyServer.cs +++ b/NetworkLibrary/P2P/Generic/Room/SecureLobbyServer.cs @@ -3,10 +3,12 @@ using NetworkLibrary.P2P.Components; using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.P2P.Generic; +using NetworkLibrary.UDP.Jumbo; using NetworkLibrary.Utils; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -22,14 +24,159 @@ private ConcurrentDictionary> private ConcurrentDictionary> peerToRoomMap = new ConcurrentDictionary>(); - // representspeers that have contacted. used for broadasting disconnect message + // represents peers that have contacted. used for broadasting disconnect message private ConcurrentDictionary> peerRelationalMap = new ConcurrentDictionary>(); private object roomLock = new object(); + public int dispatchms = 10; + + AsyncDispatcher batchBroadcastDispatcher = new AsyncDispatcher(); + AsyncDispatcher roomPublishOperation = new AsyncDispatcher(); + ConcurrentDictionary> tcpBatchBrodacastCollector = new ConcurrentDictionary>(); + public int NumClients=> RegisteredPeers.Count; public SecureLobbyServer(int port, X509Certificate2 cerificate) : base(port, cerificate) { + + } + + public override void StartServer() + { + StartRoomPublisher(); + Task.Run(() => { StartPeriodicMessageDispatcher(); }); + base.StartServer(); + } + private async void StartRoomPublisher() + { + try + { + await roomPublishOperation.ExecuteBySignal(() => + { + + MessageEnvelope roomList = CreateRoomListMsg(); + + foreach (var rp in RegisteredPeers) + { + SendAsyncMessage(rp.Key, roomList); + } + + + }).ConfigureAwait(false); + } + catch (AggregateException e) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, + $"An Error occured on Room publisher: \n{e.InnerException.Message}\n{e.InnerException.StackTrace}"); + + StartRoomPublisher(); + } + + } + + private async void StartPeriodicMessageDispatcher() + { + Dictionary> toDispatch = new Dictionary>(); + + try + { + await batchBroadcastDispatcher.LoopPeriodic(() => + { + + foreach (var roomBCMsgs in tcpBatchBrodacastCollector) + { + + string roomName = roomBCMsgs.Key; + ICollection roomPeerIds; + + if (TryGetRoom(roomName, out Room room)) + { + roomPeerIds = room.GetPeerIdList(); + } + else + { + if (tcpBatchBrodacastCollector.TryRemove(roomBCMsgs.Key, out ConcurrentQueue queue)) + { + while (queue.TryDequeue(out BroadcastBytes msg)) + { + msg.ReturnBuffer(); + } + } + + continue; + } + + List disposalList = new List(roomBCMsgs.Value.Count+10); + while (roomBCMsgs.Value.TryDequeue(out BroadcastBytes msgToBc)) + { + Guid senderId = msgToBc.From; + disposalList.Add(msgToBc); + + foreach (var destination in roomPeerIds) + { + if (destination != senderId) + { + if (!toDispatch.ContainsKey(destination)) + { + toDispatch[destination] = new Queue(); + } + toDispatch[destination].Enqueue(msgToBc);// has duplicates.. + } + } + } + + + Parallel.ForEach(toDispatch, toDispatchKv => + { + var segments = new List>(); + + while (toDispatchKv.Value.Count > 0) + { + BroadcastBytes msg = toDispatchKv.Value.Dequeue(); + segments.Add(new ArraySegment(msg.Data, msg.Offset, msg.Count)); + + } + + if (segments.Count > 0) + SendSegmentsToClient(toDispatchKv.Key, segments); + + }); + + foreach (var container in disposalList) + { + container.ReturnBuffer(); + } + disposalList.Clear(); + + } + + }, dispatchms).ConfigureAwait(false); + + } + catch (AggregateException e) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, + $"An Error occured on Batch broadcast dispatch: \n{e.InnerException.Message}\n{e.InnerException.StackTrace}"); + + StartPeriodicMessageDispatcher(); + } + } + + private MessageEnvelope CreateRoomListMsg() + { + MessageEnvelope roomList = new MessageEnvelope(); + roomList.Header = Constants.RoomAvalibalilityUpdate; + roomList.KeyValuePairs = new Dictionary(); + lock (roomLock) + { + foreach (var item in rooms) + { + roomList.KeyValuePairs.Add(item.Key, null); + } + return roomList; + } + } + #region Overrides #region Override Disbale Behaviour protected override void NotifyCurrentPeerList(Guid clientId) @@ -40,12 +187,15 @@ protected override void NotifyCurrentPeerList(Guid clientId) protected override void PublishPeerRegistered(Guid clientId) { // this on base sets the task to publish peer list - // we dont care + // only send available room to newcomer + var roomList = CreateRoomListMsg(); + SendAsyncMessage(clientId, roomList); } #endregion protected override void PublishPeerUnregistered(Guid clientId) { + // here we care because it could be dc. //TODO Send a Special Disconnect Message.... to who ?? HashSet notificationList = new HashSet(); @@ -58,14 +208,21 @@ protected override void PublishPeerUnregistered(Guid clientId) foreach (var item in roomList) { var roomName = item.Key; - if (rooms.TryGetValue(roomName, out var room)) + if (TryGetRoom(roomName, out var room)) { - room.Remove(clientId); + int remaining = room.Remove(clientId); var peerList = room.GetPeerIdList(); foreach (var id in peerList) { notificationList.Add(id); } + if (remaining == 0) + { + TryRemoveRoom(roomName, out _); + MiniLogger.Log(MiniLogger.LogLevel.Info, $"[{roomName}] Room Destroyed"); + room.Close(); + } + } } } @@ -104,39 +261,55 @@ protected override void HandleMessageReceivedInternal(Guid clientId, MessageEnve private void SendAvailableRooms(Guid clientId, MessageEnvelope message) { - message.KeyValuePairs = new Dictionary(); - foreach (var item in rooms) + lock (roomLock) { - message.KeyValuePairs.Add(item.Key, null); + message.KeyValuePairs = new Dictionary(); + foreach (var item in rooms) + { + message.KeyValuePairs.Add(item.Key, null); + } } + SendAsyncMessage(clientId, message); } #endregion - private void CreateOrJoinRoom(Guid clientID, MessageEnvelope message) + private void CreateOrJoinRoom(Guid clientId, MessageEnvelope message) { if (message.KeyValuePairs != null) { var roomName = message.KeyValuePairs.Keys.First(); Room room = null; + MessageEnvelope peerListMsg = new MessageEnvelope(); lock (roomLock) { - if (!rooms.TryGetValue(roomName, out room)) + if (!TryGetRoom(roomName, out room)) { room = new Room(roomName, this); - rooms.TryAdd(roomName, room); - //Console.WriteLine(roomName + " Added"); + TryAddRoom(roomName, room); MiniLogger.Log(MiniLogger.LogLevel.Info, $"Room Created [{roomName}]"); } - room.Add(clientID); - // Console.WriteLine(roomName + " Joined"); - MiniLogger.Log(MiniLogger.LogLevel.Info, $"{clientID} Joined the room [{roomName}]"); + try + { + room.LockRoom(); + room.Add(clientId); + + if (!peerToRoomMap.ContainsKey(clientId)) + peerToRoomMap.TryAdd(clientId, new ConcurrentDictionary()); + + peerToRoomMap[clientId].TryAdd(roomName, null); + + peerListMsg = room.GetPeerListMsg(); + peerListMsg.MessageId = message.MessageId; + SendAsyncMessage(clientId, peerListMsg); + + MiniLogger.Log(MiniLogger.LogLevel.Info, $"{clientId} Joined the room [{roomName}]"); + } + finally { room.UnlockRoom(); } + - if (!peerToRoomMap.ContainsKey(clientID)) - peerToRoomMap.TryAdd(clientID, new ConcurrentDictionary()); - peerToRoomMap[clientID].TryAdd(roomName, null); } - SendAsyncMessage(clientID, new MessageEnvelope() { MessageId = message.MessageId }); + // here send the room state, all the peers inside. } } @@ -147,92 +320,250 @@ private void LeaveRoom(Guid clientID, MessageEnvelope message) var roomName = message.KeyValuePairs.Keys.First(); lock (roomLock) { - if (rooms.TryGetValue(roomName, out var room)) + if (TryGetRoom(roomName, out var room)) { - room.Remove(clientID); + try + { + room.LockRoom(); + + int remaining = room.Remove(clientID); + if (remaining == 0) + { + TryRemoveRoom(roomName, out _); + MiniLogger.Log(MiniLogger.LogLevel.Info, $"[{roomName}] Room Destroyed"); + room.Close(); + } + + if (peerToRoomMap.ContainsKey(clientID)) + peerToRoomMap[clientID].TryRemove(roomName, out _); + } + finally + { + room.UnlockRoom(); + } + } - if (peerToRoomMap.ContainsKey(clientID)) - peerToRoomMap[clientID].TryRemove(roomName, out _); + + } + } + } + + class BroadcastBytes + { + public Guid From; + public int Offset; + public int Count; + public byte[] Data => GetData(); + private byte[] GetData() + { + if (Interlocked.CompareExchange(ref released, 0, 0) == 1) + { + throw new InvalidOperationException("Attempted to access released buffer"); + } + return data; + } + + private byte[] data; + private int released = 0; + private PooledMemoryStream stream; + + + public BroadcastBytes(MessageEnvelope msg, GenericMessageSerializer serializer) + { + this.From = msg.From; + + stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.Position = 0; + serializer.EnvelopeMessageWithBytes(stream, msg, msg.Payload, msg.PayloadOffset, msg.PayloadCount); + + var buff = stream.GetBuffer(); + this.data = buff; + this.Offset = 0; + this.Count = stream.Position32; + } + + internal void ReturnBuffer() + { + if(Interlocked.Exchange(ref released, 1) == 0) + { + var buff = Interlocked.Exchange(ref data, null); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } + } } - protected override void BroadcastMessage(Guid guid, byte[] bytes, int offset, int count) + + protected override void BroadcastMessage(Guid senderId, byte[] bytes, int offset, int count) { var messageEnvelope = serialiser.DeserialiseEnvelopedMessage(bytes, offset, count); - if (messageEnvelope.KeyValuePairs != null && messageEnvelope.KeyValuePairs.TryGetValue(Constants.RoomName, out string roomName)) + messageEnvelope.From = senderId; + + if (messageEnvelope.KeyValuePairs != null) { - rooms.TryGetValue(roomName, out var room); - var snapshot = room.GetPeerIdList(); - foreach (var peerId in snapshot) + + if (messageEnvelope.KeyValuePairs.TryGetValue(Constants.RoomBroadcast, out string roomName)) { - if (peerId != guid) - SendBytesToClient(peerId, bytes, offset, count); + TryGetRoom(roomName, out var room); + var snapshot = room.GetPeerIdList(); + + messageEnvelope.KeyValuePairs.Remove(Constants.RoomBroadcast); + if (messageEnvelope.KeyValuePairs.Count == 0) + messageEnvelope.KeyValuePairs = null; + + foreach (var peerId in snapshot) + { + if (peerId != senderId) + SendAsyncMessage(peerId, messageEnvelope); + } + } + else if (messageEnvelope.KeyValuePairs.TryGetValue(Constants.RoomBroadcastBatched, out roomName)) + { + messageEnvelope.KeyValuePairs.Remove(Constants.RoomBroadcastBatched); + if (messageEnvelope.KeyValuePairs.Count == 0) + messageEnvelope.KeyValuePairs = null; + + PushBatchQueueTcp(roomName, messageEnvelope); } } } + private void PushBatchQueueTcp(string roomName,MessageEnvelope msg) + { + var queue = tcpBatchBrodacastCollector.GetOrAdd(roomName, new ConcurrentQueue()); + queue.Enqueue(new BroadcastBytes(msg,serialiser)); + } + // we have to do something about this in future when we have reliable udp. protected override void BroadcastUdp(byte[] buffer, int lenght) { var messageEnvelope = serialiser.DeserialiseEnvelopedMessage(buffer, 0, lenght); peerReachabilityMatrix.TryGetValue(messageEnvelope.From, out var edgeMap); - if (messageEnvelope.KeyValuePairs != null && messageEnvelope.KeyValuePairs.TryGetValue(Constants.RoomName, out string roomName)) + if (messageEnvelope.KeyValuePairs != null) { - // find the room - rooms.TryGetValue(roomName, out var room); - var peerIdList = room.GetPeerIdList(); - foreach (var peerId in peerIdList) + if (messageEnvelope.KeyValuePairs.TryGetValue(Constants.RoomBroadcast, out string roomName)) { - if (edgeMap != null && edgeMap.TryGetValue(peerId, out _)) + // find the room + TryGetRoom(roomName, out var room); + var peerIdList = room.GetPeerIdList(); + foreach (var peerId in peerIdList) { - continue; - } + if (edgeMap != null && edgeMap.TryGetValue(peerId, out _)) + { + continue; + } - if (messageEnvelope.From != peerId) - RelayUdpMessage(peerId, buffer, 0, lenght); + if (messageEnvelope.From != peerId) + RelayUdpMessage(peerId, buffer, 0, lenght); + } } + + } + } + + private bool TryGetRoom(string roomName, out Room room) + { + return rooms.TryGetValue(roomName, out room); + } + + private bool TryRemoveRoom(string roomName, out Room room) + { + if( rooms.TryRemove(roomName, out room)) + { + roomPublishOperation.Signal(); + return true; } + return false; } - internal class Room where S : ISerializer, new() + + private bool TryAddRoom(string roomName, Room room) + { + if( rooms.TryAdd(roomName, room)) + { + roomPublishOperation.Signal(); + return true; + } + return false; + } + + public override void ShutdownServer() + { + batchBroadcastDispatcher.Abort(); + roomPublishOperation.Abort(); + + base.ShutdownServer(); + } + + internal class Room where T : ISerializer, new() { public readonly string RoomName; - private readonly SecureLobbyServer server; - private ConcurrentDictionary roomMates = new ConcurrentDictionary(); - private TaskCompletionSource PublishSignal = new TaskCompletionSource(); - public Room(string roomName, SecureLobbyServer server) + private readonly SecureLobbyServer server; + private ConcurrentDictionary roomMates = new ConcurrentDictionary(); + private AsyncDispatcher publishChangesOperation = new AsyncDispatcher(); + + SemaphoreSlim publishLock = new SemaphoreSlim(1,1); + public Room(string roomName, SecureLobbyServer server ) { RoomName = roomName; this.server = server; PublishRotune(); } - + public void LockRoom() + { + publishLock.Wait(); + } + public void UnlockRoom() + { + publishLock.Release(); + } private async void PublishRotune() { - while (true) + await publishChangesOperation.ExecuteBySignalEfficient(async () => { try { - await Task.Delay(1000).ConfigureAwait(false); - await PublishSignal.Task.ConfigureAwait(false); - - Interlocked.Exchange(ref PublishSignal, new TaskCompletionSource()); - - RoomPeerList rpl = GenerateRoomPeerList(); - MessageEnvelope message = PrepareMessage(rpl, out var toReturn); - lock (roomMates) + await publishLock.WaitAsync(); + try { + RoomPeerList rpl = GenerateRoomPeerList(); + MessageEnvelope message = PrepareMessage(rpl, out var toReturn); + foreach (var peer in roomMates) { server.SendAsyncMessage(peer.Key, message); } SharerdMemoryStreamPool.ReturnStreamStatic(toReturn); + } + finally + { + publishLock.Release(); + } + } - catch (Exception ex) { MiniLogger.Log(MiniLogger.LogLevel.Error, ex.Message); } + catch(Exception e) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, + $"An Error occured on Batch broadcast dispatch: " + + $"\n{e.InnerException.Message}" + + $"\n{e.InnerException.StackTrace}"); - } + PublishRotune(); + } + + }); + + + } + + public MessageEnvelope GetPeerListMsg() + { + RoomPeerList rpl = GenerateRoomPeerList(); + MessageEnvelope message = PrepareMessage(rpl, out var toReturn); + message.LockBytes(); + SharerdMemoryStreamPool.ReturnStreamStatic(toReturn); + return message; } private MessageEnvelope PrepareMessage(RoomPeerList rpl, out PooledMemoryStream returnAfterFinished) @@ -256,37 +587,56 @@ private RoomPeerList GenerateRoomPeerList() rpl.RoomName = RoomName; rpl.Peers = new PeerList(); rpl.Peers.PeerIds = new Dictionary(); - + + foreach (var item in roomMates) { var ep = server.GetIPEndPoint(item.Key); var peerInfo = new PeerInfo() { Address = ep.Address.GetAddressBytes(), - Port = (ushort)ep.Port + Port = (ushort)ep.Port, + RegisteryTime = item.Value + }; rpl.Peers.PeerIds[item.Key] = peerInfo; } + + return rpl; } public void Add(Guid peerId) { - if (roomMates.TryAdd(peerId, null)) - PublishSignal.TrySetResult(true); + if (roomMates.TryAdd(peerId, server.GetTime())) + publishChangesOperation.Signal(); } - public void Remove(Guid peerId) + public int Remove(Guid peerId) { if (roomMates.TryRemove(peerId, out _)) - PublishSignal.TrySetResult(true); + { + publishChangesOperation.Signal(); + } + + + var cnt = roomMates.Count; + return cnt; } internal ICollection GetPeerIdList() { - return roomMates.Keys; + return roomMates.Keys; + } + + + public void Close() + { + publishChangesOperation.Abort(); } } + + } } diff --git a/NetworkLibrary/TCP/Base/Core/IAsyncSession.cs b/NetworkLibrary/TCP/Base/Core/IAsyncSession.cs index 8f3008b..ecb6f2d 100644 --- a/NetworkLibrary/TCP/Base/Core/IAsyncSession.cs +++ b/NetworkLibrary/TCP/Base/Core/IAsyncSession.cs @@ -1,5 +1,6 @@ using NetworkLibrary.Components.Statistics; using System; +using System.Collections.Generic; using System.Net; namespace NetworkLibrary.TCP.Base @@ -35,6 +36,8 @@ internal interface IAsyncSession : IDisposable /// void SendAsync(byte[] buffer, int offset, int count); + void SendAsync(List> batch); + /// /// Starts the session /// diff --git a/NetworkLibrary/TCP/Base/TcpSession.cs b/NetworkLibrary/TCP/Base/TcpSession.cs index 043d9c9..8fd1fee 100644 --- a/NetworkLibrary/TCP/Base/TcpSession.cs +++ b/NetworkLibrary/TCP/Base/TcpSession.cs @@ -3,6 +3,7 @@ using NetworkLibrary.Components.Statistics; using NetworkLibrary.Utils; using System; +using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; @@ -229,6 +230,69 @@ protected virtual void HandleReceived(byte[] buffer, int offset, int count) #endregion Recieve #region Send + + public void SendAsync(List> batch) + { + if (IsSessionClosing()) + return; + try + { + SendAsync_(batch); + } + catch (Exception e) + { + if (!IsSessionClosing()) + MiniLogger.Log(MiniLogger.LogLevel.Error, + "Unexpected error while sending async with ssl session" + e.Message + "Trace " + e.StackTrace); + } + } + + private void SendAsync_(List> batch) + { + enqueueLock.Take(); + if (IsSessionClosing()) + { + ReleaseSendResourcesIdempotent(); + return; + } + if (SendSemaphore.IsTaken()) + { + foreach (var segment in batch) + { + if (!messageBuffer.TryEnqueueMessage(segment.Array, segment.Offset, segment.Count)) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, "Message is too large to fit on buffer"); + EndSession(); + return; + } + } + } + enqueueLock.Release(); + + if (DropOnCongestion && SendSemaphore.IsTaken()) return; + + SendSemaphore.Take(); + if (IsSessionClosing()) + { + ReleaseSendResourcesIdempotent(); + SendSemaphore.Release(); + return; + } + + foreach (var segment in batch) + { + // you have to push it to queue because queue also does the processing. + if (!messageBuffer.TryEnqueueMessage(segment.Array, segment.Offset, segment.Count)) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, "Message is too large to fit on buffer"); + EndSession(); + return; + } + } + + messageBuffer.TryFlushQueue(ref sendBuffer, 0, out int amountWritten); + FlushSendBuffer(0, amountWritten); + } public virtual void SendAsync(byte[] bytes) { if (IsSessionClosing()) diff --git a/NetworkLibrary/TCP/SSL/SslServer.cs b/NetworkLibrary/TCP/SSL/SslServer.cs index c41e441..ff9bdc9 100644 --- a/NetworkLibrary/TCP/SSL/SslServer.cs +++ b/NetworkLibrary/TCP/SSL/SslServer.cs @@ -4,6 +4,7 @@ using NetworkLibrary.Utils; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Net; using System.Net.Security; using System.Net.Sockets; @@ -217,6 +218,12 @@ public override void SendBytesToClient(Guid clientId, byte[] bytes) session.SendAsync(bytes); } + public void SendSegmentsToClient(Guid clientId, List> segments) + { + if (Sessions.TryGetValue(clientId, out var session)) + session.SendAsync(segments); + } + public void SendBytesToClient(Guid clientId, byte[] bytes, int offset, int count) { if (Sessions.TryGetValue(clientId, out var session)) diff --git a/NetworkLibrary/TCP/SSL/SslSession.cs b/NetworkLibrary/TCP/SSL/SslSession.cs index fa0cc2a..02dae28 100644 --- a/NetworkLibrary/TCP/SSL/SslSession.cs +++ b/NetworkLibrary/TCP/SSL/SslSession.cs @@ -4,6 +4,8 @@ using NetworkLibrary.TCP.Base; using NetworkLibrary.Utils; using System; +using System.Collections.Generic; +using System.Drawing; using System.Net; using System.Net.Security; using System.Runtime.CompilerServices; @@ -84,6 +86,66 @@ protected virtual IMessageQueue CreateMessageQueue() } + public void SendAsync(List> batch) + { + if (IsSessionClosing()) + return; + try + { + SendAsync_( batch); + } + catch (Exception e) + { + if (!IsSessionClosing()) + MiniLogger.Log(MiniLogger.LogLevel.Error, + "Unexpected error while sending async with ssl session" + e.Message + "Trace " + e.StackTrace); + } + } + private void SendAsync_(List> batch) + { + enqueueLock.Take(); + if (IsSessionClosing()) + { + ReleaseSendResourcesIdempotent(); + return; + } + if (SendSemaphore.IsTaken()) + { + + if(!messageQueue.TryEnqueueMessage(batch)) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, "Message is too large to fit on buffer"); + EndSession(); + return; + } + + } + enqueueLock.Release(); + + if (DropOnCongestion && SendSemaphore.IsTaken()) return; + + SendSemaphore.Take(); + if (IsSessionClosing()) + { + ReleaseSendResourcesIdempotent(); + SendSemaphore.Release(); + return; + } + + + // you have to push it to queue because queue also does the processing. + if (!messageQueue.TryEnqueueMessage(batch)) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, "Message is too large to fit on buffer"); + EndSession(); + return; + } + + + FlushAndSend(); + } + + public void SendAsync(byte[] buffer, int offset, int count) { if (IsSessionClosing()) diff --git a/NetworkLibrary/Utils/AsyncDispatcher.cs b/NetworkLibrary/Utils/AsyncDispatcher.cs new file mode 100644 index 0000000..9a60c54 --- /dev/null +++ b/NetworkLibrary/Utils/AsyncDispatcher.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkLibrary.Utils +{ + internal class AsyncDispatcher + { + TaskCompletionSource signal = new TaskCompletionSource(); + int cancel = 0; + public void Signal() + { + signal.TrySetResult(true); + } + + public void Abort() + { + Interlocked.Increment(ref cancel); + signal.TrySetResult(true); + } + + public async Task ExecuteBySignalEfficient(Action execution, int delayBetweenMs = 1000) + { + while (true) + { + try + { + if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0) return; + await signal.Task.ConfigureAwait(false); + if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0)return; + + Interlocked.Exchange(ref signal, new TaskCompletionSource()); + + execution?.Invoke(); + await Task.Delay(delayBetweenMs).ConfigureAwait(false); + } + catch(Exception e) + { + throw new AggregateException(e); + } + } + } + + public async Task ExecuteBySignal(Action execution) + { + while (true) + { + try + { + if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0) return; + await signal.Task.ConfigureAwait(false); + if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0) return; + + Interlocked.Exchange(ref signal, new TaskCompletionSource()); + + execution?.Invoke(); + } + catch (Exception e) + { + throw new AggregateException(e); + } + } + } + + public async Task LoopPeriodic(Action execution, int ms) + { + Stopwatch sw = new Stopwatch(); + while (true) + { + try + { + if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0) return; + + sw.Stop(); + int sleep = ms-(int)sw.ElapsedMilliseconds; + sleep = Math.Max(sleep, 0); + await Task.Delay(ms).ConfigureAwait(false); + + if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0) return; + + sw.Restart(); + execution.Invoke(); + } + catch (Exception e) + { + throw new AggregateException(e); + } + + + } + } + //Func + + public async Task LoopPeriodicTask(Func asyncTask, int ms) + { + while (true) + { + try + { + if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0) return; + await Task.Delay(ms).ConfigureAwait(false); + + if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0) return; + await asyncTask.Invoke().ConfigureAwait(false); + } + catch (Exception e) + { + throw new AggregateException(e); + } + + + } + } + } +} diff --git a/NetworkLibrary/Utils/Statistics.cs b/NetworkLibrary/Utils/Statistics.cs new file mode 100644 index 0000000..bde8eeb --- /dev/null +++ b/NetworkLibrary/Utils/Statistics.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NetworkLibrary.Utils +{ + public class Statistics + { + public static IEnumerable FilterOutliers(List times) + { + if (times == null || times.Count < 4) + { + throw new ArgumentException("The list must contain at least 4 elements."); + } + + // Sort the list + var sortedTimes = times.OrderBy(t => t).ToList(); + + // Calculate the quartiles + double Q1 = GetPercentile(sortedTimes, 25); + double Q3 = GetPercentile(sortedTimes, 75); + + // Calculate the interquartile range (IQR) + double IQR = Q3 - Q1; + + // Define the acceptable range for non-outliers + double lowerBound = Q1 - 1.5 * IQR; + double upperBound = Q3 + 1.5 * IQR; + + // Filter out the outliers + var filteredTimes = sortedTimes.Where(t => t >= lowerBound && t <= upperBound); + + return filteredTimes; + } + + private static double GetPercentile(List sortedList, double percentile) + { + int N = sortedList.Count; + double rank = (percentile / 100.0) * (N - 1); + int lowIndex = (int)Math.Floor(rank); + int highIndex = (int)Math.Ceiling(rank); + double fraction = rank - lowIndex; + + if (highIndex >= N) return sortedList[lowIndex]; + return sortedList[lowIndex] + fraction * (sortedList[highIndex] - sortedList[lowIndex]); + } + } +} diff --git a/Tests/UnitTests/LobbyServerTest.cs b/Tests/UnitTests/LobbyServerTest.cs index 96d3552..c06012a 100644 --- a/Tests/UnitTests/LobbyServerTest.cs +++ b/Tests/UnitTests/LobbyServerTest.cs @@ -6,9 +6,11 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Threading; +using System.Threading.Tasks; namespace UnitTests { @@ -89,6 +91,219 @@ void TcpReceived(SecureProtoRoomClient cl, MessageEnvelope m) server.ShutdownServer(); } + [TestMethod] + public void TestBatchRoomBroadcast() + { + var expectedClientsTcp = new ConcurrentDictionary(); + var expectedClientsUdp = new ConcurrentDictionary(); + + int totalUdp = 0; + int totalTcp = 0; + + var scert = new X509Certificate2("server.pfx", "greenpass"); + var cert = new X509Certificate2("client.pfx", "greenpass"); + string ip = "127.0.0.1"; + int port = 22222; + int numClients = 10; + int numMsgs = 1000; + var server = new RoomServer(port, scert); + server.StartServer(); + + List clients = new List(); + + for (int i = 0; i < numClients; i++) + { + var cl = new SecureProtoRoomClient(cert); + cl.OnTcpMessageReceived += (m) => TcpReceived(cl, m); + cl.OnUdpMessageReceived += (m) => UdpReceived(cl, m); + cl.Connect(ip, port); + cl.CreateOrJoinRoom("WA"); + clients.Add(cl); + } + + Thread.Sleep(1000); + for (int j = 0; j < numMsgs; j++) + { + for (int i = 0; i < numClients; i++) + { + clients[i].BroadcastMessageToRoomBatched("WA", new MessageEnvelope() { Header = "Tcp Yo" }); + } + } + + + + + + void UdpReceived(SecureProtoRoomClient cl, MessageEnvelope m) + { + expectedClientsUdp.TryAdd(cl, null); + Interlocked.Increment(ref totalUdp); + } + + void TcpReceived(SecureProtoRoomClient cl, MessageEnvelope m) + { + expectedClientsTcp.TryAdd(cl, null); + Interlocked.Increment(ref totalTcp); + + } + Thread.Sleep(2000); + Assert.AreEqual(clients.Count, expectedClientsTcp.Count); + Assert.AreEqual(totalTcp, numClients*numMsgs*(clients.Count - 1)); + + server.ShutdownServer(); + } + + [TestMethod] + public void TestTimeSync() + { + var expectedClientsTcp = new ConcurrentDictionary(); + var expectedClientsUdp = new ConcurrentDictionary(); + + int totalUdp = 0; + int totalTcp = 0; + + var scert = new X509Certificate2("server.pfx", "greenpass"); + var cert = new X509Certificate2("client.pfx", "greenpass"); + string ip = "127.0.0.1"; + int port = 22222; + int numClients = 10; + var server = new RoomServer(port, scert); + server.StartServer(); + Thread.Sleep(1000); + + List clients = new List(); + + for (int i = 0; i < numClients; i++) + { + var cl = new SecureProtoRoomClient(cert); + + cl.Connect(ip, port); + cl.CreateOrJoinRoom("WA"); + clients.Add(cl); + Thread.Sleep(1); + } + + Thread.Sleep(1000); + + for (int j = 0; j < 10; j++) + { + for (int i = 0; i < numClients; i++) + { + var suc = clients[i].SyncTime().Result; + } + Thread.Sleep(100); + } + + + + + Thread.Sleep(1000); + + var t1 = clients[0].GetTime(); + var tn = clients[numClients-1].GetTime(); + + Trace.WriteLine(t1); + Trace.WriteLine(tn); + Assert.IsTrue(Math.Abs(t1-tn)<1); + + + + + server.ShutdownServer(); + } + [TestMethod] + public void TestTimeSync2() + { + var expectedClientsTcp = new ConcurrentDictionary(); + var expectedClientsUdp = new ConcurrentDictionary(); + + int totalUdp = 0; + int totalTcp = 0; + + var scert = new X509Certificate2("server.pfx", "greenpass"); + var cert = new X509Certificate2("client.pfx", "greenpass"); + string ip = "127.0.0.1"; + int port = 22222; + int numClients = 10; + var server = new RoomServer(port, scert); + server.StartServer(); + Thread.Sleep(1000); + + List clients = new List(); + + for (int i = 0; i < numClients; i++) + { + var cl = new SecureProtoRoomClient(cert); + cl.StartAutoTimeSync(1000, false); + cl.Connect(ip, port); + + cl.CreateOrJoinRoom("MA"); + clients.Add(cl); + Thread.Sleep(1); + } + + Thread.Sleep(2400); + + + var t1 = clients[0].GetTime(); + var tn = clients[numClients - 1].GetTime(); + + Trace.WriteLine(t1); + Trace.WriteLine(tn); + Assert.IsTrue(Math.Abs(t1 - tn) < 1); + + server.ShutdownServer(); + } + + [TestMethod] + public void TestRoomList() + { + var expectedClientsTcp = new ConcurrentDictionary(); + var expectedClientsUdp = new ConcurrentDictionary(); + + int totalUdp = 0; + int totalTcp = 0; + + var scert = new X509Certificate2("server.pfx", "greenpass"); + var cert = new X509Certificate2("client.pfx", "greenpass"); + string ip = "127.0.0.1"; + int port = 22222; + int numClients = 10; + var server = new RoomServer(port, scert); + server.StartServer(); + Thread.Sleep(1000); + + ConcurrentDictionary clients = new ConcurrentDictionary(); + + Parallel.For(0, numClients, i => + { + var cl = new SecureProtoRoomClient(cert); + + cl.Connect(ip, port); + clients.TryAdd(cl.SessionId, cl); + + var list = cl.CreateOrJoinRoom("WA"); + + Assert.IsTrue(list != null); + Assert.IsTrue(list.Peers != null); + Assert.IsTrue(list.Peers.PeerIds != null); + Assert.IsTrue(list.Peers.PeerIds.Count > 0); + + foreach (var kv in list.Peers.PeerIds) + { + Assert.IsTrue(clients.ContainsKey(kv.Key)); + } + + + }); + + Thread.Sleep(1000); + + + + server.ShutdownServer(); + } + [TestMethod] public void TestRoomBroadcastWithLeave() { From 2aec271e2f221382bb942e9203015ee66edfd969 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Wed, 27 Nov 2024 15:44:57 +0100 Subject: [PATCH 03/27] lobby server distributed clock sync --- NetworkLibrary/P2P/Generic/RelayClientBase.cs | 80 ++++++++++++++++--- NetworkLibrary/P2P/Generic/RelayServerBase.cs | 2 +- .../P2P/Generic/Room/SecureLobbyClient.cs | 1 + NetworkLibrary/TCP/SSL/SslClient.cs | 14 ++++ NetworkLibrary/Utils/AsyncDispatcher.cs | 4 +- NetworkLibrary/Utils/Statistics.cs | 46 +++++++++++ 6 files changed, 131 insertions(+), 16 deletions(-) diff --git a/NetworkLibrary/P2P/Generic/RelayClientBase.cs b/NetworkLibrary/P2P/Generic/RelayClientBase.cs index bac1a4c..8067098 100644 --- a/NetworkLibrary/P2P/Generic/RelayClientBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayClientBase.cs @@ -121,6 +121,7 @@ private set private bool initialised = false; private Stopwatch clientClock = new Stopwatch(); double timeOffset; + TimeSpan timeOffsetd; long syncCount = 0; public RelayClientBase(X509Certificate2 clientCert, int udpPort = 0) { @@ -156,10 +157,16 @@ public double GetTime() return clientClock.Elapsed.TotalMilliseconds + timeOffset; } + public DateTime GetDateTime() + { + return DateTime.UtcNow.Add(timeOffsetd); + } + AsyncDispatcher timesyncOperation; public void StartAutoTimeSync(int periodMs,bool disconnectOnTimeout,bool usePTP = false) { Interlocked.Exchange(ref timesyncOperation, new AsyncDispatcher())?.Abort(); + int failureCount = 0; timesyncOperation.LoopPeriodicTask(async () => { try @@ -169,10 +176,18 @@ public void StartAutoTimeSync(int periodMs,bool disconnectOnTimeout,bool usePTP if (IsConnected) { bool result = await SyncTime(usePTP).ConfigureAwait(false); - if (disconnectOnTimeout && result == false) + if (disconnectOnTimeout && result == false ) + { + if (++failureCount > 2) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, "TimeSync operation timed out "); + Disconnect(); + } + + } + else { - MiniLogger.Log(MiniLogger.LogLevel.Error, "TimeSync operation timed out "); - Disconnect(); + failureCount = 0; } } @@ -198,12 +213,13 @@ public void StopAutoTimeSync() List timesHistory = new List(); + List timesHistoryd = new List(); private static readonly SemaphoreSlim asyncLock = new SemaphoreSlim(1, 1); public async Task SyncTime(bool usePtp = false) { try { - await asyncLock.WaitAsync(); + await asyncLock.WaitAsync().ConfigureAwait(false); if (!IsConnected) return false; @@ -223,10 +239,11 @@ public async Task SyncTime(bool usePtp = false) for (int i = 0; i < sampleSize; i++) { - var result = usePtp?await GetOffsetPTP(): await GetOffsetNTP(); + var result = usePtp?await GetOffsetPTP().ConfigureAwait(false): await GetOffsetNTP().ConfigureAwait(false); if (result.Succes) { - timesHistory.Add(result.Value); + timesHistory.Add(result.PreciseTime); + timesHistoryd.Add(result.DateTimeOffset); } else return false; } @@ -235,14 +252,30 @@ public async Task SyncTime(bool usePtp = false) return false; var times = Statistics.FilterOutliers(timesHistory); + var timesd = Statistics.FilterOutliers(timesHistoryd); if (timesHistory.Count > 600) { timesHistory = timesHistory.Skip(60).ToList(); + timesHistoryd = timesHistoryd.Skip(60).ToList(); } double average = times.Sum() / times.Count(); + double averaged = timesd.Sum(ts => ts.Ticks) / timesd.Count(); timeOffset = average; + if (sCnt > 0) + { + var calculatedAvg = TimeSpan.FromTicks((long)averaged); + if (timeOffsetd < calculatedAvg) + { + timeOffsetd = calculatedAvg; + } + } + else + { + timeOffsetd = TimeSpan.FromTicks((long)averaged); + } + Interlocked.Increment(ref syncCount); return true; @@ -253,7 +286,7 @@ public async Task SyncTime(bool usePtp = false) } } - class TimeResult { public double Value; public bool Succes; } + class TimeResult { public double PreciseTime; public bool Succes; public DateTime ServerUTC; public TimeSpan DateTimeOffset; } private async Task GetOffsetNTP() { var msg = new MessageEnvelope() @@ -262,15 +295,23 @@ private async Task GetOffsetNTP() IsInternal = true, }; var now = clientClock.Elapsed.TotalMilliseconds; - var response = await tcpMessageClient.SendMessageAndWaitResponse(msg, 10000); + var nowd = DateTime.UtcNow; + + var response = await tcpMessageClient.SendMessageAndWaitResponse(msg, 10000).ConfigureAwait(false); if (response.Header != MessageEnvelope.RequestTimeout) { var serverTime = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); + var serverTimed = response.TimeStamp; + var now1 = clientClock.Elapsed.TotalMilliseconds; + var now1d = DateTime.UtcNow; var timeOffset = ((serverTime - now) + (serverTime - now1)) / 2; - return new TimeResult() { Value = timeOffset, Succes = true }; + var timeOffsetd = ((serverTimed - nowd) + (serverTimed - now1d)).TotalMilliseconds / 2; + TimeSpan offd = TimeSpan.FromMilliseconds(timeOffsetd); + + return new TimeResult() { PreciseTime = timeOffset, DateTimeOffset = offd, Succes = true }; } return new TimeResult(); @@ -283,14 +324,19 @@ private async Task GetOffsetPTP() if (t1.Succes) { var t2 = clientClock.Elapsed.TotalMilliseconds; + var t2d = DateTime.UtcNow; var t3 = t2; + var t3d = t2d; + var t4 = await GetServerTime(); if (t4.Succes) { - double offset = ( ((t4.Value - t3) - (t2 - t1.Value)) / 2); - return new TimeResult() { Value = offset, Succes = true }; + double offset = ( ((t4.PreciseTime - t3) - (t2 - t1.PreciseTime)) / 2); + double offsetd = ( ((t4.ServerUTC - t3d) - (t2d - t1.ServerUTC)).TotalMilliseconds / 2); + TimeSpan offd =TimeSpan.FromMilliseconds(offsetd); + return new TimeResult() { PreciseTime = offset, DateTimeOffset = offd, Succes = true }; } else return new TimeResult(); @@ -311,7 +357,7 @@ private async Task GetServerTime() if (response.Header != MessageEnvelope.RequestTimeout) { var serverTime = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); - return new TimeResult() { Value = serverTime, Succes = true }; + return new TimeResult() { PreciseTime = serverTime, ServerUTC = response.TimeStamp, Succes = true }; } return new TimeResult(); @@ -392,7 +438,15 @@ public async Task ConnectAsync(string host, int port) relayServerEndpoint = new IPEndPoint(IPAddress.Parse(connectHost), connectPort); - await tcpMessageClient.ConnectAsync(host, port).ConfigureAwait(false); + bool result = await tcpMessageClient.ConnectAsync(host, port).ConfigureAwait(false); + if (!result) + { + throw new TimeoutException(); + } + else + { + MiniLogger.Log(MiniLogger.LogLevel.Info, "Connection established"); + } var stateCompletion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); clientStateManager.CreateConnectionState() diff --git a/NetworkLibrary/P2P/Generic/RelayServerBase.cs b/NetworkLibrary/P2P/Generic/RelayServerBase.cs index 6fd0e57..a3ca2af 100644 --- a/NetworkLibrary/P2P/Generic/RelayServerBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayServerBase.cs @@ -260,7 +260,7 @@ protected virtual void HandleMessageReceivedInternal(Guid clientId, MessageEnvel byte[] time = new byte[8]; message.Payload = time; PrimitiveEncoder.WriteFixedDouble(time, 0, serverClock.Elapsed.TotalMilliseconds); - + message.TimeStamp = DateTime.UtcNow; SendAsyncMessage(clientId, message); break; diff --git a/NetworkLibrary/P2P/Generic/Room/SecureLobbyClient.cs b/NetworkLibrary/P2P/Generic/Room/SecureLobbyClient.cs index 20d7a9c..2566ca5 100644 --- a/NetworkLibrary/P2P/Generic/Room/SecureLobbyClient.cs +++ b/NetworkLibrary/P2P/Generic/Room/SecureLobbyClient.cs @@ -52,6 +52,7 @@ public SecureLobbyClient(X509Certificate2 clientCert) public void StopAutoTimeSync()=>client.StopAutoTimeSync(); public double GetTime() => client.GetTime(); + public DateTime GetDateTime() => client.GetDateTime(); private bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { if (RemoteCertificateValidationCallback == null) diff --git a/NetworkLibrary/TCP/SSL/SslClient.cs b/NetworkLibrary/TCP/SSL/SslClient.cs index 56039b6..c18d709 100644 --- a/NetworkLibrary/TCP/SSL/SslClient.cs +++ b/NetworkLibrary/TCP/SSL/SslClient.cs @@ -91,6 +91,20 @@ public override Task ConnectAsyncAwaitable(string ip, int port) earg.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(ip), port); earg.Completed += (ignored, arg) => { HandleResult(arg); }; + Task.Delay(10000).ContinueWith(t => + { + if (IsConnecting) + { + try + { + clientSocket.Close(); + + } + catch { } + tcs.SetResult(false); + } + }); + if (!clientSocket.ConnectAsync(earg)) { HandleResult(earg); diff --git a/NetworkLibrary/Utils/AsyncDispatcher.cs b/NetworkLibrary/Utils/AsyncDispatcher.cs index 9a60c54..8d3a257 100644 --- a/NetworkLibrary/Utils/AsyncDispatcher.cs +++ b/NetworkLibrary/Utils/AsyncDispatcher.cs @@ -101,10 +101,10 @@ public async Task LoopPeriodicTask(Func asyncTask, int ms) try { if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0) return; - await Task.Delay(ms).ConfigureAwait(false); + await Task.Delay(ms).ConfigureAwait(false); if (Interlocked.CompareExchange(ref cancel, 0, 0) > 0) return; - await asyncTask.Invoke().ConfigureAwait(false); + await asyncTask.Invoke().ConfigureAwait(false); } catch (Exception e) { diff --git a/NetworkLibrary/Utils/Statistics.cs b/NetworkLibrary/Utils/Statistics.cs index bde8eeb..570a74e 100644 --- a/NetworkLibrary/Utils/Statistics.cs +++ b/NetworkLibrary/Utils/Statistics.cs @@ -34,6 +34,34 @@ public static IEnumerable FilterOutliers(List times) return filteredTimes; } + public static IEnumerable FilterOutliers(List times) + { + if (times == null || times.Count < 4) + { + throw new ArgumentException("The list must contain at least 4 elements."); + } + + // Sort the list + var sortedTimes = times.OrderBy(t => t).ToList(); + + // Calculate the quartiles + TimeSpan Q1 = GetPercentile(sortedTimes, 25); + TimeSpan Q3 = GetPercentile(sortedTimes, 75); + + // Calculate the interquartile range (IQR) + TimeSpan IQR = Q3 - Q1; + + // Define the acceptable range for non-outliers + TimeSpan lowerBound = Q1 - TimeSpan.FromTicks((long)(1.5 * IQR.Ticks)); + TimeSpan upperBound = Q3 + TimeSpan.FromTicks((long)(1.5 * IQR.Ticks)); + + // Filter out the outliers + var filteredTimes = sortedTimes.Where(t => t >= lowerBound && t <= upperBound); + + return filteredTimes; + } + + private static double GetPercentile(List sortedList, double percentile) { int N = sortedList.Count; @@ -45,5 +73,23 @@ private static double GetPercentile(List sortedList, double percentile) if (highIndex >= N) return sortedList[lowIndex]; return sortedList[lowIndex] + fraction * (sortedList[highIndex] - sortedList[lowIndex]); } + + private static TimeSpan GetPercentile(List sortedList, double percentile) + { + int N = sortedList.Count; + double rank = (percentile / 100.0) * (N - 1); + int lowIndex = (int)Math.Floor(rank); + int highIndex = (int)Math.Ceiling(rank); + double fraction = rank - lowIndex; + + if (highIndex >= N) return sortedList[lowIndex]; + + // Interpolate between the low and high index TimeSpans + TimeSpan lowValue = sortedList[lowIndex]; + TimeSpan highValue = sortedList[highIndex]; + double ticksDifference = (highValue - lowValue).Ticks; + + return lowValue + TimeSpan.FromTicks((long)(fraction * ticksDifference)); + } } } From 94015d3d063116f2d06332b4ce45aa5ec6cb608b Mon Sep 17 00:00:00 2001 From: dogancan ozturk Date: Tue, 10 Dec 2024 00:28:15 +0100 Subject: [PATCH 04/27] end session on exception --- Benchmarks/RelayBenchmark/Program.cs | 17 +-- Json/JsonNetwork.csproj | 2 +- MessagePack/MessagePackNetwork.csproj | 2 +- NetSerializer/NetSerializerNetwork.csproj | 2 +- .../Crypto/Algorithms/AesGcmAlgorithm.cs | 7 +- .../Unmanaged/DelimitedMessageWriter.cs | 1 - .../Fast/Network/SecureMessageSession.cs | 13 +- NetworkLibrary/NetworkLibrary.csproj | 2 +- NetworkLibrary/P2P/Generic/RelayServerBase.cs | 8 +- NetworkLibrary/TCP/Base/TcpSession.cs | 11 +- .../TCP/Generic/GenericSecureSession.cs | 2 +- NetworkLibrary/TCP/SSL/SslClient.cs | 1 + NetworkLibrary/TCP/SSL/SslServer.cs | 1 + NetworkLibrary/TCP/SSL/SslSession.cs | 131 +++--------------- Protobuff/P2P/RelayClient.cs | 2 +- Protobuff/Protobuff.csproj | 2 +- 16 files changed, 61 insertions(+), 143 deletions(-) diff --git a/Benchmarks/RelayBenchmark/Program.cs b/Benchmarks/RelayBenchmark/Program.cs index 38367d5..3694c59 100644 --- a/Benchmarks/RelayBenchmark/Program.cs +++ b/Benchmarks/RelayBenchmark/Program.cs @@ -185,6 +185,7 @@ private static void RelayTest() { //string ip = "79.52.134.220"; + //string ip = "172.25.3.45"; string ip = "127.0.0.1"; MiniLogger.AllLog += Console.WriteLine; @@ -202,7 +203,7 @@ private static void RelayTest() Console.ReadLine(); //Task.Run(async () => { while (true) { await Task.Delay(10000); server.GetTcpStatistics(out var generalStats, out _); Console.WriteLine(generalStats.ToString()); } }); var clients = new List(); - int numclients = 2; + int numclients = 100; var pending = new Task[numclients]; Task.Run(async () => { while (true) { await Task.Delay(1000); Console.WriteLine(Interlocked.Exchange(ref sumsum, 0).ToString("N0")); } }); @@ -236,7 +237,7 @@ private static void RelayTest() { if (client.SessionId == Guid.Empty) throw new Exception(); - client.StartPingService(); + // client.StartPingService(); // Console.WriteLine("--- -- - | "+client.sessionId+" count: " + client.Peers.Count); foreach (var peer in client.Peers) { @@ -247,8 +248,8 @@ private static void RelayTest() //var a = client.RequestTcpHolePunchAsync(peer.Key); //pndg.Add(a); - var aa = client.RequestHolePunchAsync(peer.Key, 10000, false); - pndg.Add(aa); + //var aa = client.RequestHolePunchAsync(peer.Key, 10000, false); + //pndg.Add(aa); //client.TestHP(peer.Key, 10000, false); // Console.WriteLine(peer.Key+" cnt=> "+ ++cc); } @@ -268,20 +269,20 @@ private static void RelayTest() var testMessage = new MessageEnvelope() { Header = "Test", - Payload = new byte[32] + Payload = new byte[32000] }; for (int i = 0; i < testMessage.PayloadCount; i++) { testMessage.Payload[i] = (byte)i; } - for (int i = 0; i < 100; i++) + for (int i = 0; i < 10; i++) { //return; foreach (var peer in client.Peers.Keys) { //await client.SendRequestAndWaitResponse(peer, testMessage,1000); - //client.SendAsyncMessage(peer, testMessage); - // client.SendUdpMessage(peer, testMessage); + client.SendAsyncMessage(peer, testMessage); + client.SendUdpMessage(peer, testMessage); // client.SendRudpMessage(peer, testMessage); // client.BroadcastMessage(testMessage); //client.BroadcastUdpMessage(testMessage); diff --git a/Json/JsonNetwork.csproj b/Json/JsonNetwork.csproj index 20023c2..f9b19cb 100644 --- a/Json/JsonNetwork.csproj +++ b/Json/JsonNetwork.csproj @@ -5,7 +5,7 @@ True Json.Network.Library Json Network Library - 2.0.0 + 2.0.1 ReferenceType Message passing and P2P network library using System.Text.Json Apache-2.0 diff --git a/MessagePack/MessagePackNetwork.csproj b/MessagePack/MessagePackNetwork.csproj index faa46f3..6cf724b 100644 --- a/MessagePack/MessagePackNetwork.csproj +++ b/MessagePack/MessagePackNetwork.csproj @@ -5,7 +5,7 @@ True MessagePack.Network.Library MessagePack Network Library - 2.0.0 + 2.0.1 ReferenceType Message passing and P2P network library using MessagePack Serialization Apache-2.0 diff --git a/NetSerializer/NetSerializerNetwork.csproj b/NetSerializer/NetSerializerNetwork.csproj index c908db1..a281793 100644 --- a/NetSerializer/NetSerializerNetwork.csproj +++ b/NetSerializer/NetSerializerNetwork.csproj @@ -5,7 +5,7 @@ True NetSerializer.Network.Library NetSerializer Network Library - 2.0.0 + 2.0.1 Message passing and P2P network library using .NetSerializer Apache-2.0 https://github.com/ReferenceType/StandardNetworkLibrary diff --git a/NetworkLibrary/Components/Crypto/Algorithms/AesGcmAlgorithm.cs b/NetworkLibrary/Components/Crypto/Algorithms/AesGcmAlgorithm.cs index 7377948..d20cecb 100644 --- a/NetworkLibrary/Components/Crypto/Algorithms/AesGcmAlgorithm.cs +++ b/NetworkLibrary/Components/Crypto/Algorithms/AesGcmAlgorithm.cs @@ -29,8 +29,13 @@ internal class AesGcmAlgorithm : IAesAlgorithm const int tagSize = 14; public AesGcmAlgorithm(byte[] Key, byte[] IV) { +#if NET8_0_OR_GREATER + aes = new AesGcm(Key,tagSize); + aes2 = new AesGcm(Key,tagSize); +#else aes = new AesGcm(Key); aes2 = new AesGcm(Key); +#endif iv = IV; } @@ -220,4 +225,4 @@ private byte[] ParseNonce(byte[] source, ref int sourceOffset, ref int sourceCou } #endif -} + } diff --git a/NetworkLibrary/Components/MessageProcessor/Unmanaged/DelimitedMessageWriter.cs b/NetworkLibrary/Components/MessageProcessor/Unmanaged/DelimitedMessageWriter.cs index 2356fe8..0faa5e2 100644 --- a/NetworkLibrary/Components/MessageProcessor/Unmanaged/DelimitedMessageWriter.cs +++ b/NetworkLibrary/Components/MessageProcessor/Unmanaged/DelimitedMessageWriter.cs @@ -4,7 +4,6 @@ namespace NetworkLibrary.Components.MessageProcessor.Unmanaged { internal sealed class DelimitedMessageWriter - : IMessageProcessor { private byte[] bufferInternal; diff --git a/NetworkLibrary/MessageProtocol/Fast/Network/SecureMessageSession.cs b/NetworkLibrary/MessageProtocol/Fast/Network/SecureMessageSession.cs index bfee4b1..51454d2 100644 --- a/NetworkLibrary/MessageProtocol/Fast/Network/SecureMessageSession.cs +++ b/NetworkLibrary/MessageProtocol/Fast/Network/SecureMessageSession.cs @@ -51,7 +51,7 @@ public void SendAsync(MessageEnvelope message) { SendAsyncInternal(message); } - catch { if (!IsSessionClosing()) throw; } + catch { if (!IsSessionClosing()) { EndSession(); throw; } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -97,7 +97,7 @@ public void SendAsync(MessageEnvelope envelope, T message) { SendAsyncInternal(envelope, message); } - catch { if (!IsSessionClosing()) throw; } + catch { if (!IsSessionClosing()) { EndSession(); throw; } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -132,8 +132,6 @@ private void SendAsyncInternal(MessageEnvelope envelope, T message) return; } - //mq.TryFlushQueue(ref sendBuffer, 0, out int amountWritten); - //WriteOnSessionStream(amountWritten); FlushAndSend(); } @@ -147,7 +145,7 @@ public void SendAsync(MessageEnvelope envelope, Action seria { SendAsyncInternal(envelope, serializationCallback); } - catch { if (!IsSessionClosing()) throw; } + catch { if (!IsSessionClosing()) { EndSession(); throw; } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -182,10 +180,9 @@ private void SendAsyncInternal(MessageEnvelope envelope, Actionhttps://github.com/ReferenceType/StandardNetworkLibrary RefrenceType Apache 2.0 - 3.0.0 + 3.1.0 2.0.1 2.0.1 For release notes check out: https://github.com/ReferenceType/StandardNetworkLibrary diff --git a/NetworkLibrary/P2P/Generic/RelayServerBase.cs b/NetworkLibrary/P2P/Generic/RelayServerBase.cs index 88afd36..11a3e0e 100644 --- a/NetworkLibrary/P2P/Generic/RelayServerBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayServerBase.cs @@ -31,6 +31,7 @@ namespace NetworkLibrary.P2P.Generic private ConcurrentDictionary RegisteredUdpEndpoints = new ConcurrentDictionary(); private ConcurrentDictionary> ClientUdpEndpoints = new ConcurrentDictionary>(); private ConcurrentDictionary UdpCryptos = new ConcurrentDictionary(); + private ConcurrentDictionary endpointMap = new ConcurrentDictionary(); internal ConcurrentDictionary> peerReachabilityMatrix = new ConcurrentDictionary>(); private TaskCompletionSource PushPeerList = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -166,7 +167,7 @@ internal void Register(Guid clientId, IPEndPoint remoteEndpoint, List(T message) { SendAsyncInternal(message); } - catch { if (!IsSessionClosing()) throw; } + catch { if (!IsSessionClosing()) { EndSession(); throw; } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/NetworkLibrary/TCP/SSL/SslClient.cs b/NetworkLibrary/TCP/SSL/SslClient.cs index 56039b6..82f8b60 100644 --- a/NetworkLibrary/TCP/SSL/SslClient.cs +++ b/NetworkLibrary/TCP/SSL/SslClient.cs @@ -53,6 +53,7 @@ public SslClient() private Socket GetSocket() { Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); + socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); return socket; } diff --git a/NetworkLibrary/TCP/SSL/SslServer.cs b/NetworkLibrary/TCP/SSL/SslServer.cs index c41e441..8591e18 100644 --- a/NetworkLibrary/TCP/SSL/SslServer.cs +++ b/NetworkLibrary/TCP/SSL/SslServer.cs @@ -77,6 +77,7 @@ public SslServer(int port) public override void StartServer() { serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); + serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); serverSocket.ReceiveBufferSize = ServerSockerReceiveBufferSize; serverSocket.Bind(new IPEndPoint(IPAddress.Any, ServerPort)); diff --git a/NetworkLibrary/TCP/SSL/SslSession.cs b/NetworkLibrary/TCP/SSL/SslSession.cs index fa0cc2a..9378401 100644 --- a/NetworkLibrary/TCP/SSL/SslSession.cs +++ b/NetworkLibrary/TCP/SSL/SslSession.cs @@ -26,9 +26,7 @@ public class SslSession : IAsyncSession protected Spinlock enqueueLock = new Spinlock(); protected SslStream sessionStream; protected byte[] receiveBuffer; -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// protected Memory receiveMemory; -//#endif + protected byte[] sendBuffer; protected Guid sessionId; protected IPEndPoint RemoteEP; @@ -64,12 +62,7 @@ public void StartSession() protected virtual void ConfigureBuffers() { - receiveBuffer = /*new byte[ReceiveBufferSize];*/ BufferPool.RentBuffer(ReceiveBufferSize); - -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER - -// receiveMemory = new Memory(receiveBuffer); -//#endif + receiveBuffer = BufferPool.RentBuffer(ReceiveBufferSize); if (UseQueue) sendBuffer = BufferPool.RentBuffer(SendBufferSize); @@ -97,6 +90,8 @@ public void SendAsync(byte[] buffer, int offset, int count) if (!IsSessionClosing()) MiniLogger.Log(MiniLogger.LogLevel.Error, "Unexpected error while sending async with ssl session" + e.Message+"Trace " +e.StackTrace); + EndSession(); + throw; } } private void SendAsync_(byte[] buffer, int offset, int count) @@ -150,6 +145,8 @@ public void SendAsync(byte[] buffer) if (!IsSessionClosing()) MiniLogger.Log(MiniLogger.LogLevel.Error, "Unexpected error while sending async with ssl session" + e.Message + "Trace " + e.StackTrace); + EndSession(); + throw; } } private void SendAsync_(byte[] buffer) @@ -209,11 +206,6 @@ protected void FlushAndSend() } protected void WriteOnSessionStream(int count) { -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// WriteModern(count); -// return; -//#endif - try { sessionStream.BeginWrite(sendBuffer, 0, count, SentInternal, null); @@ -225,68 +217,6 @@ protected void WriteOnSessionStream(int count) totalBytesSend += count; } -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER - -// private async void WriteModern(int count) -// { -// try -// { -// //somehow faster than while loop... -// Top: -// totalBytesSend += count; -// await sessionStream.WriteAsync(new ReadOnlyMemory(sendBuffer, 0, count)).ConfigureAwait(false); - -// if (IsSessionClosing()) -// { -// ReleaseSendResourcesIdempotent(); -// return; -// } -// if (messageQueue.TryFlushQueue(ref sendBuffer, 0, out int amountWritten)) -// { -// count = amountWritten; -// goto Top; - -// } - -// // here there was nothing to flush -// bool flush = false; - -// enqueueLock.Take(); -// // ask again safely -// if (messageQueue.IsEmpty()) -// { -// messageQueue.Flush(); - -// SendSemaphore.Release(); -// enqueueLock.Release(); -// if (IsSessionClosing()) -// ReleaseSendResourcesIdempotent(); -// return; -// } -// else -// { -// flush = true; - -// } -// enqueueLock.Release(); - -// // something got into queue just before i exit, we need to flush it -// if (flush) -// { -// if (messageQueue.TryFlushQueue(ref sendBuffer, 0, out int amountWritten_)) -// { -// count = amountWritten_; -// goto Top; -// } -// } -// } -// catch (Exception e) -// { -// HandleError("Error on sent callback ssl", e); -// } -// } -//#endif - private void SentInternal(IAsyncResult ar) { if (ar.CompletedSynchronously) @@ -365,10 +295,6 @@ private void Sent(IAsyncResult ar) protected virtual void Receive() { -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// ReceiveNew(); -// return; -//#endif if (IsSessionClosing()) { ReleaseReceiveResourcesIdempotent(); @@ -384,39 +310,7 @@ protected virtual void Receive() ReleaseReceiveResourcesIdempotent(); } } -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER - -// private async void ReceiveNew() -// { -// try -// { -// while (true) -// { -// if (IsSessionClosing()) -// { -// ReleaseReceiveResourcesIdempotent(); -// return; -// } -// var amountRead = await sessionStream.ReadAsync(receiveMemory).ConfigureAwait(false); -// if (amountRead > 0) -// { -// HandleReceived(receiveBuffer, 0, amountRead); -// } -// else -// { -// EndSession(); -// ReleaseReceiveResourcesIdempotent(); -// } -// totalBytesReceived += amountRead; -// } -// } -// catch (Exception ex) -// { -// HandleError("White receiving from SSL socket an error occurred", ex); -// ReleaseReceiveResourcesIdempotent(); -// } -// } -//#endif + protected virtual void Received(IAsyncResult ar) { if (IsSessionClosing()) @@ -440,7 +334,15 @@ protected virtual void Received(IAsyncResult ar) if (amountRead > 0) { - HandleReceived(receiveBuffer, 0, amountRead); + try + { + HandleReceived(receiveBuffer, 0, amountRead); + } + catch (Exception e) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, e.Message + "\n" + e.StackTrace); + EndSession(); + } } else { @@ -462,7 +364,6 @@ protected virtual void HandleReceived(byte[] buffer, int offset, int count) { totalMessageReceived++; OnBytesRecieved?.Invoke(sessionId, buffer, offset, count); - } #region Closure & Disposal diff --git a/Protobuff/P2P/RelayClient.cs b/Protobuff/P2P/RelayClient.cs index 80d499e..b5d4558 100644 --- a/Protobuff/P2P/RelayClient.cs +++ b/Protobuff/P2P/RelayClient.cs @@ -7,7 +7,7 @@ namespace Protobuff.P2P { public class RelayClient : RelayClientBase - { + { public RelayClient(X509Certificate2 clientCert, int udpPort = 0) : base(clientCert,udpPort) { } diff --git a/Protobuff/Protobuff.csproj b/Protobuff/Protobuff.csproj index aefda6f..318d495 100644 --- a/Protobuff/Protobuff.csproj +++ b/Protobuff/Protobuff.csproj @@ -11,7 +11,7 @@ Apache-2.0 ProtobufNetworkLibrary 2.01 - 3.0.0 + 3.0.1 2.0.1 True From e00b59250397c02fb854c5a93ba73d8d239a4a33 Mon Sep 17 00:00:00 2001 From: dogancan ozturk Date: Tue, 11 Mar 2025 07:26:07 +0100 Subject: [PATCH 05/27] keep alive --- .../HybridProtoNetworkBenchmark/Program.cs | 88 ++++++++++++++++- Benchmarks/RelayBenchmark/Program.cs | 17 ++-- .../SecureProtobuffBenchmark/Program.cs | 94 ++++++++++++++++++- Benchmarks/TcpBenchmark/Program.cs | 2 +- .../Fast/Network/SecureMessageServer.cs | 1 + NetworkLibrary/NetworkLibrary.csproj | 2 +- NetworkLibrary/P2P/Constants.cs | 1 + NetworkLibrary/P2P/Generic/RelayClientBase.cs | 82 ++++++++++------ NetworkLibrary/P2P/Generic/RelayServerBase.cs | 50 +++++++++- NetworkLibrary/TCP/SSL/SslClient.cs | 26 +++++ NetworkLibrary/TCP/SSL/SslServer.cs | 27 +++++- NetworkLibrary/UDP/AsyncUdpClient.cs | 4 + NetworkLibrary/UDP/AsyncUdpServer.cs | 4 +- NetworkLibrary/UDP/AsyncUdpServerLitecs.cs | 4 +- Protobuff/Protobuff.csproj | 2 +- 15 files changed, 352 insertions(+), 52 deletions(-) diff --git a/Benchmarks/HybridProtoNetworkBenchmark/Program.cs b/Benchmarks/HybridProtoNetworkBenchmark/Program.cs index bba7777..7a73ca2 100644 --- a/Benchmarks/HybridProtoNetworkBenchmark/Program.cs +++ b/Benchmarks/HybridProtoNetworkBenchmark/Program.cs @@ -1,6 +1,7 @@ using NetworkLibrary; using NetworkLibrary.Components.Statistics; using NetworkLibrary.Utils; +using ProtoBuf; using Protobuff; using System.Diagnostics; @@ -73,7 +74,7 @@ static void EchoDynamic(Guid arg1, MessageEnvelope arg2) } static void EchoStatic(Guid arg1, MessageEnvelope arg2) { - server.SendAsyncMessage(arg1, fixedMessage); + server.SendAsyncMessage(arg1, arg2); } private static void InitializeClients() @@ -129,18 +130,101 @@ private static void Benchmark() { Console.WriteLine("Press Enter To Benchmark"); Console.ReadLine(); + var large = new LargeDataClass(); sw2.Start(); Parallel.ForEach(clients, client => { for (int i = 0; i < numMessages; i++) { - client.SendAsyncMessage(clientMessage); + client.SendAsyncMessage(clientMessage,large); } }); } + [ProtoContract] + public class LargeDataClass + { + [ProtoMember(1)] + public int Id { get; set; } + + [ProtoMember(2)] + public string Name { get; set; } + + [ProtoMember(3)] + public double Value { get; set; } + + [ProtoMember(4)] + public DateTime Timestamp { get; set; } + + [ProtoMember(5)] + public NestedData Nested { get; set; } + + [ProtoMember(6)] + public List Tags { get; set; } + [ProtoMember(7)] + public Dictionary Metadata { get; set; } + + [ProtoMember(8)] + public bool IsActive { get; set; } + + [ProtoMember(9)] + public byte[] RawData { get; set; } + + [ProtoMember(10)] + public List NestedList { get; set; } + + public LargeDataClass() + { + // Initialize with some non-default values + Id = 1001; + Name = "Example Name"; + Value = 123.45; + Timestamp = DateTime.UtcNow; + Nested = new NestedData + { + NestedId = 42, + Description = "Nested description", + Data = new byte[] { 1, 2, 3, 4 } + }; + Tags = new List { "tag1", "tag2", "tag3" }; + Metadata = new Dictionary + { + { "key1", 1 }, + { "key2", 2 }, + { "key3", 3 } + }; + IsActive = true; + RawData = new byte[] { 10, 20, 30, 40 }; + NestedList = new List + { + new NestedData { NestedId = 101, Description = "First nested", Data = new byte[] { 5, 6, 7 } }, + new NestedData { NestedId = 102, Description = "Second nested", Data = new byte[] { 8, 9, 10 } } + }; + } + } + + [ProtoContract] + public class NestedData + { + [ProtoMember(1)] + public int NestedId { get; set; } + + [ProtoMember(2)] + public string Description { get; set; } + + [ProtoMember(3)] + public byte[] Data { get; set; } + + public NestedData() + { + // Initialize with non-default values + NestedId = 0; + Description = "Default description"; + Data = Array.Empty(); + } + } private static void ShowStatus() { while (Console.ReadLine() != "e") diff --git a/Benchmarks/RelayBenchmark/Program.cs b/Benchmarks/RelayBenchmark/Program.cs index 3694c59..9798e60 100644 --- a/Benchmarks/RelayBenchmark/Program.cs +++ b/Benchmarks/RelayBenchmark/Program.cs @@ -186,7 +186,10 @@ private static void RelayTest() //string ip = "79.52.134.220"; //string ip = "172.25.3.45"; - string ip = "127.0.0.1"; + //string ip = "35.159.121.192"; + // string ip = "79.33.180.225"; + string ip = "192.168.1.8"; + // string ip = "127.0.0.1"; MiniLogger.AllLog += Console.WriteLine; @@ -207,8 +210,8 @@ private static void RelayTest() var pending = new Task[numclients]; Task.Run(async () => { while (true) { await Task.Delay(1000); Console.WriteLine(Interlocked.Exchange(ref sumsum, 0).ToString("N0")); } }); - // Parallel.For(0, numclients, (i) => - for (int i = 0; i < numclients; i++) + Parallel.For(0, numclients, (i) => + //for (int i = 0; i < numclients; i++) { var client = new RelayClient(cert,0); @@ -227,7 +230,7 @@ private static void RelayTest() //Thread.Sleep(1000); } - // ); + ); Task.WaitAll(pending); Console.WriteLine("All Connected"); Thread.Sleep(2000); @@ -269,20 +272,20 @@ private static void RelayTest() var testMessage = new MessageEnvelope() { Header = "Test", - Payload = new byte[32000] + Payload = new byte[32] }; for (int i = 0; i < testMessage.PayloadCount; i++) { testMessage.Payload[i] = (byte)i; } - for (int i = 0; i < 10; i++) + for (int i = 0; i < 100; i++) { //return; foreach (var peer in client.Peers.Keys) { //await client.SendRequestAndWaitResponse(peer, testMessage,1000); client.SendAsyncMessage(peer, testMessage); - client.SendUdpMessage(peer, testMessage); + //client.SendUdpMessage(peer, testMessage); // client.SendRudpMessage(peer, testMessage); // client.BroadcastMessage(testMessage); //client.BroadcastUdpMessage(testMessage); diff --git a/Benchmarks/SecureProtobuffBenchmark/Program.cs b/Benchmarks/SecureProtobuffBenchmark/Program.cs index 90836b5..d6dd29f 100644 --- a/Benchmarks/SecureProtobuffBenchmark/Program.cs +++ b/Benchmarks/SecureProtobuffBenchmark/Program.cs @@ -1,6 +1,7 @@ using NetworkLibrary; using NetworkLibrary.Components.Statistics; using NetworkLibrary.Utils; +using ProtoBuf; using Protobuff; using System; using System.Collections.Generic; @@ -75,10 +76,95 @@ static void EchoDynamic(Guid arg1, MessageEnvelope arg2) } static void EchoStatic(Guid arg1, MessageEnvelope arg2) { - server.SendAsyncMessage(arg1, fixedMessage); + server.SendAsyncMessage(arg1, arg2); } + + +[ProtoContract] +public class LargeDataClass +{ + [ProtoMember(1)] + public int Id { get; set; } + + [ProtoMember(2)] + public string Name { get; set; } + + [ProtoMember(3)] + public double Value { get; set; } + + [ProtoMember(4)] + public DateTime Timestamp { get; set; } + + [ProtoMember(5)] + public NestedData Nested { get; set; } + + [ProtoMember(6)] + public List Tags { get; set; } + + [ProtoMember(7)] + public Dictionary Metadata { get; set; } + + [ProtoMember(8)] + public bool IsActive { get; set; } + + [ProtoMember(9)] + public byte[] RawData { get; set; } + + [ProtoMember(10)] + public List NestedList { get; set; } + + public LargeDataClass() + { + // Initialize with some non-default values + Id = 1001; + Name = "Example Name"; + Value = 123.45; + Timestamp = DateTime.UtcNow; + Nested = new NestedData + { + NestedId = 42, + Description = "Nested description", + Data = new byte[] { 1, 2, 3, 4 } + }; + Tags = new List { "tag1", "tag2", "tag3" }; + Metadata = new Dictionary + { + { "key1", 1 }, + { "key2", 2 }, + { "key3", 3 } + }; + IsActive = true; + RawData = new byte[] { 10, 20, 30, 40 }; + NestedList = new List + { + new NestedData { NestedId = 101, Description = "First nested", Data = new byte[] { 5, 6, 7 } }, + new NestedData { NestedId = 102, Description = "Second nested", Data = new byte[] { 8, 9, 10 } } + }; + } +} + +[ProtoContract] +public class NestedData +{ + [ProtoMember(1)] + public int NestedId { get; set; } + + [ProtoMember(2)] + public string Description { get; set; } + + [ProtoMember(3)] + public byte[] Data { get; set; } + + public NestedData() + { + // Initialize with non-default values + NestedId = 0; + Description = "Default description"; + Data = Array.Empty(); + } +} - private static void InitializeClients() +private static void InitializeClients() { clientMessage = new MessageEnvelope() @@ -129,8 +215,10 @@ private static void Prepare() InitializeClients(); } } + private static void Benchmark() { + var large = new LargeDataClass(); Console.WriteLine("Press Enter To Benchmark"); Console.ReadLine(); sw2.Start(); @@ -139,7 +227,7 @@ private static void Benchmark() { for (int i = 0; i < numMessages; i++) { - client.SendAsyncMessage(clientMessage); + client.SendAsyncMessage(clientMessage,large); } }); diff --git a/Benchmarks/TcpBenchmark/Program.cs b/Benchmarks/TcpBenchmark/Program.cs index be8e721..4cd87d4 100644 --- a/Benchmarks/TcpBenchmark/Program.cs +++ b/Benchmarks/TcpBenchmark/Program.cs @@ -14,7 +14,7 @@ namespace ConsoleTest internal class Program { - static int port = 20007; + static int port = 20008; static bool runAsServer; static bool isFixedMessage; static int fixedMessageSize; diff --git a/NetworkLibrary/MessageProtocol/Fast/Network/SecureMessageServer.cs b/NetworkLibrary/MessageProtocol/Fast/Network/SecureMessageServer.cs index 132c7c2..6ce15eb 100644 --- a/NetworkLibrary/MessageProtocol/Fast/Network/SecureMessageServer.cs +++ b/NetworkLibrary/MessageProtocol/Fast/Network/SecureMessageServer.cs @@ -135,6 +135,7 @@ public Task SendMessageAndWaitResponse(Guid clientId, MessageEn SendAsyncMessage(clientId, message); return task; } + public IPEndPoint GetIPEndPoint(Guid cliendId) { return GetSessionEndpoint(cliendId); diff --git a/NetworkLibrary/NetworkLibrary.csproj b/NetworkLibrary/NetworkLibrary.csproj index 011022d..2d64ece 100644 --- a/NetworkLibrary/NetworkLibrary.csproj +++ b/NetworkLibrary/NetworkLibrary.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0 + netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0 true Standard.Network.Library True diff --git a/NetworkLibrary/P2P/Constants.cs b/NetworkLibrary/P2P/Constants.cs index 3842a30..3c8347d 100644 --- a/NetworkLibrary/P2P/Constants.cs +++ b/NetworkLibrary/P2P/Constants.cs @@ -19,6 +19,7 @@ public class Constants public const string Ping = "Ping"; public const string Pong = "Pong"; + public const string KeepAlieve = ".K"; public const string RoomUpdate = "12"; diff --git a/NetworkLibrary/P2P/Generic/RelayClientBase.cs b/NetworkLibrary/P2P/Generic/RelayClientBase.cs index 8067098..b219987 100644 --- a/NetworkLibrary/P2P/Generic/RelayClientBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayClientBase.cs @@ -604,39 +604,48 @@ private async void SendPing(int intervalMs, bool sendToServer, bool sendTcpToPee MessageEnvelope msg = new MessageEnvelope(); msg.Header = Constants.Ping; - if (IsConnected) + try { - var time = DateTime.Now; - - if (sendToServer) + if (IsConnected) { - msg.From = sessionId; - msg.To = sessionId; + var time = DateTime.Now; + + if (sendToServer) + { + msg.From = sessionId; + msg.To = sessionId; - tcpMessageClient.SendAsyncMessage(msg); - pinger.NotifyTcpPingSent(sessionId, time); + tcpMessageClient.SendAsyncMessage(msg); + pinger.NotifyTcpPingSent(sessionId, time); - SendUdpMessage(sessionId, msg); - pinger.NotifyUdpPingSent(sessionId, time); - } + SendUdpMessage(sessionId, msg); + pinger.NotifyUdpPingSent(sessionId, time); + } - time = DateTime.Now; + time = DateTime.Now; - if (sendTcpToPeers) - BroadcastMessage(msg); - if (sendUdpToPeers) - BroadcastUdpMessage(msg); - foreach (var peer in Peers.Keys) - { if (sendTcpToPeers) - pinger.NotifyTcpPingSent(peer, time); + BroadcastMessage(msg); if (sendUdpToPeers) + BroadcastUdpMessage(msg); + foreach (var peer in Peers.Keys) { - //SendUdpMesssage(peer, msg); - pinger.NotifyUdpPingSent(peer, time); - } + if (sendTcpToPeers) + pinger.NotifyTcpPingSent(peer, time); + if (sendUdpToPeers) + { + //SendUdpMesssage(peer, msg); + pinger.NotifyUdpPingSent(peer, time); + } + } } + + } + catch + { + Disconnect(); + return; } } @@ -791,15 +800,23 @@ private void SendUdpMesssageInternal(Guid toId, MessageEnvelope message) SendLargeUdpMessage(toId,message); return; } - - if (!udpServer.TrySendAsync(endpoint, message, algo, out var excessStream)) + try { + if (!udpServer.TrySendAsync(endpoint, message, algo, out var excessStream)) + { - if (JumboUdpModules.TryGetValue(toId, out var mod)) - mod.Send(excessStream.GetBuffer(), 0, excessStream.Position32); - else - MiniLogger.Log(MiniLogger.LogLevel.Error, "Unable To find jumbo module with Id: " + toId + " in session " + sessionId); + if (JumboUdpModules.TryGetValue(toId, out var mod)) + mod.Send(excessStream.GetBuffer(), 0, excessStream.Position32); + else + MiniLogger.Log(MiniLogger.LogLevel.Error, "Unable To find jumbo module with Id: " + toId + " in session " + sessionId); + } + } + catch (Exception ex) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, "Unable To send udp message: " + ex.StackTrace); + Disconnect(); } + } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1316,7 +1333,6 @@ private void HandleUdpBytesReceived(IPEndPoint adress, byte[] bytes, int offset, if (peerCryptos.TryGetValue(adress, out var crypto)) { byte[] decryptBuffer = null; - try { MessageEnvelope msg; @@ -1404,6 +1420,9 @@ private void HandleUdpMessageReceived(MessageEnvelope message) mod3[2].HandleBytes(message.Payload, message.PayloadOffset, message.PayloadCount); } break; + case Constants.KeepAlieve: + tcpMessageClient.SendAsyncMessage(message); + break; default: HandleUdpMessage(message); break; @@ -1427,6 +1446,9 @@ private void HandleMessageReceived(MessageEnvelope message) UpdatePeerList(message); else if (message.Header == "CmdTcpHp") InitiateRemoteTcpHolepunch(message); + else if(message.Header == Constants.KeepAlieve) + tcpMessageClient.SendAsyncMessage(message); + else HandleMessage(message); } @@ -1446,7 +1468,7 @@ private void HandleMessageReceived(MessageEnvelope message) case Constants.Pong: HandlePong(message); break; - + default: HandleMessage(message); break; diff --git a/NetworkLibrary/P2P/Generic/RelayServerBase.cs b/NetworkLibrary/P2P/Generic/RelayServerBase.cs index 8bff59d..264e32a 100644 --- a/NetworkLibrary/P2P/Generic/RelayServerBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayServerBase.cs @@ -1,4 +1,5 @@ -using NetworkLibrary.Components; +using MessageProtocol; +using NetworkLibrary.Components; using NetworkLibrary.Components.Crypto; using NetworkLibrary.Components.Statistics; using NetworkLibrary.MessageProtocol; @@ -96,6 +97,48 @@ private void Initialise() udpServer.StartServer(); Task.Run(PeerListPushRoutine); + Task.Run(VerifyHangedClients); + } + public void FlushAllClients() + { + foreach (var session in Sessions.ToList()) + { + session.Value.EndSession(); + } + } + private async void VerifyHangedClients() + { + MessageEnvelope envelope = new MessageEnvelope(); + envelope.Header = Constants.KeepAlieve; + envelope.IsInternal = true; + while (!shutdown) + { + await Task.Delay(10000); + foreach (var session in Sessions.ToList()) + { + bool badSession = await VerifyTcp(envelope, session); + if (badSession) + { + session.Value.EndSession(); + } + } + } + + async Task VerifyTcp(MessageEnvelope envelope_, KeyValuePair session) + { + bool badSession = true; + for (var i = 0; i < 2; i++) + { + var response = await SendMessageAndWaitResponse(session.Key, envelope_, timeoutMs: 5000); + if (response != null && response.Header != MessageEnvelope.RequestTimeout) + { + badSession = false; + break; + } + } + + return badSession; + } } public void GetTcpStatistics(out TcpStatistics generalStats, out ConcurrentDictionary sessionStats) @@ -336,8 +379,6 @@ private void InitiateHolepunchBetweenPeers(MessageEnvelope message) } } - - private void HandleHolepunchcompletion(MessageEnvelope message) { peerReachabilityMatrix.TryAdd(message.From, new ConcurrentDictionary()); @@ -481,16 +522,17 @@ protected virtual void BroadcastUdp(byte[] buffer, int decrptedAmount) udpServer.SendBytesToClient(destEp, tempBuff, 0, reEncryptedBytesAmount); } } - } private void HandleUnregistreredMessage(IPEndPoint adress, byte[] bytes, int offset, int count) { + // local discovery udp bc if(count == 1 && bytes[offset] == 91) { udpServer.SendBytesToClient(adress, serverNameBytes,0,serverNameBytes.Length); return; } + //EndpointTransferMessage if (bytes[offset] == 92 && bytes[offset+1] == 93) { try diff --git a/NetworkLibrary/TCP/SSL/SslClient.cs b/NetworkLibrary/TCP/SSL/SslClient.cs index 70f550c..30c8bd3 100644 --- a/NetworkLibrary/TCP/SSL/SslClient.cs +++ b/NetworkLibrary/TCP/SSL/SslClient.cs @@ -159,6 +159,32 @@ public override void ConnectAsync(string IP, int port) private void Connected(string domainName, Socket clientSocket) { + clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + + int dscpValue = 40; // Critical services + byte tos = (byte)(dscpValue << 2); // Shift left by 2 bits + clientSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, tos); + +#if NET5_0_OR_GREATER + const int TcpKeepAliveTime = 15; // Start keepalive after 60 seconds + const int TcpKeepAliveInterval = 5; // Send probes every 10 seconds + const int TcpKeepAliveProbes = 2; // Retry 5 times before dropping + + clientSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, TcpKeepAliveTime); + clientSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, TcpKeepAliveInterval); + clientSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, TcpKeepAliveProbes); +#endif + //const int KeepAliveEnable = 1; // Enable keepalive + //const int KeepAliveTime = 60000; // Time (ms) to start keepalive (1 min) + //const int KeepAliveInterval = 10000; // Interval (ms) between keepalive probes (10 sec) + + //byte[] inOptionValues = new byte[12]; + //BitConverter.GetBytes(KeepAliveEnable).CopyTo(inOptionValues, 0); + //BitConverter.GetBytes(KeepAliveTime).CopyTo(inOptionValues, 4); + //BitConverter.GetBytes(KeepAliveInterval).CopyTo(inOptionValues, 8); + + //clientSocket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); + sslStream = new SslStream(new NetworkStream(clientSocket, true), false, ValidateCeriticate); sslStream.AuthenticateAsClient(domainName, new X509CertificateCollection(new[] { certificate }), System.Security.Authentication.SslProtocols.None, true); diff --git a/NetworkLibrary/TCP/SSL/SslServer.cs b/NetworkLibrary/TCP/SSL/SslServer.cs index 53a810e..ce555f6 100644 --- a/NetworkLibrary/TCP/SSL/SslServer.cs +++ b/NetworkLibrary/TCP/SSL/SslServer.cs @@ -51,7 +51,9 @@ public class SslServer : TcpServerBase private Socket serverSocket; private X509Certificate2 certificate; private TcpServerStatisticsPublisher statisticsPublisher; - + const int TcpKeepAliveTime = 15; // Start keepalive after 60 seconds + const int TcpKeepAliveInterval = 5; // Send probes every 10 seconds + const int TcpKeepAliveProbes = 2; // Retry 5 times before dropping public SslServer(int port, X509Certificate2 certificate) { ServerPort = port; @@ -79,6 +81,17 @@ public override void StartServer() { serverSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + + int dscpValue = 40; // Critical services + byte tos = (byte)(dscpValue << 2); // Shift left by 2 bits + serverSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, tos); + +#if NET5_0_OR_GREATER + + serverSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, TcpKeepAliveTime); + serverSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, TcpKeepAliveInterval); + serverSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, TcpKeepAliveProbes); +#endif serverSocket.ReceiveBufferSize = ServerSockerReceiveBufferSize; serverSocket.Bind(new IPEndPoint(IPAddress.Any, ServerPort)); @@ -117,6 +130,18 @@ private void Accepted(object sender, SocketAsyncEventArgs acceptedArg) return; } + acceptedArg.AcceptSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); + + int dscpValue = 40; // Critical services + byte tos = (byte)(dscpValue << 2); // Shift left by 2 bits + acceptedArg.AcceptSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, tos); + +#if NET5_0_OR_GREATER + + acceptedArg.AcceptSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, TcpKeepAliveTime); + acceptedArg.AcceptSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, TcpKeepAliveInterval); + acceptedArg.AcceptSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, TcpKeepAliveProbes); +#endif if (!ValidateConnection(acceptedArg.AcceptSocket)) { return; diff --git a/NetworkLibrary/UDP/AsyncUdpClient.cs b/NetworkLibrary/UDP/AsyncUdpClient.cs index 30a57f1..67d9305 100644 --- a/NetworkLibrary/UDP/AsyncUdpClient.cs +++ b/NetworkLibrary/UDP/AsyncUdpClient.cs @@ -60,6 +60,10 @@ public AsyncUdpClient() //clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); //clientSocket.SetSocketOption(SocketOptionLevel.Udp, SocketOptionName.PacketInformation, true); + int dscpValue = 40; // Critical services + byte tos = (byte)(dscpValue << 2); // Shift left by 2 bits + clientSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, tos); + clientSocket.ReceiveBufferSize = ReceiveBufferSize; clientSocket.SendBufferSize = SocketSendBufferSize; clientSocket.Blocking = true; diff --git a/NetworkLibrary/UDP/AsyncUdpServer.cs b/NetworkLibrary/UDP/AsyncUdpServer.cs index 1d8baf4..d1ba7fe 100644 --- a/NetworkLibrary/UDP/AsyncUdpServer.cs +++ b/NetworkLibrary/UDP/AsyncUdpServer.cs @@ -57,7 +57,9 @@ public AsyncUdpServer(int port = 20008) ServerSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true); // Not compatible with Unity.. //ServerSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true); - + int dscpValue = 40; // Critical services + byte tos = (byte)(dscpValue << 2); // Shift left by 2 bits + ServerSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, tos); ServerSocket.ReceiveBufferSize = SocketReceiveBufferSize; ServerSocket.SendBufferSize = SocketSendBufferSize; diff --git a/NetworkLibrary/UDP/AsyncUdpServerLitecs.cs b/NetworkLibrary/UDP/AsyncUdpServerLitecs.cs index 5da9a9a..d61d9d6 100644 --- a/NetworkLibrary/UDP/AsyncUdpServerLitecs.cs +++ b/NetworkLibrary/UDP/AsyncUdpServerLitecs.cs @@ -51,7 +51,9 @@ public AsyncUdpServerLite(int port) ServerSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true); // Not compatible with Unity.. //ServerSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true); - + int dscpValue = 40; // Critical services + byte tos = (byte)(dscpValue << 2); // Shift left by 2 bits + ServerSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, tos); ServerSocket.ReceiveBufferSize = SocketReceiveBufferSize; ServerSocket.SendBufferSize = SocketSendBufferSize; diff --git a/Protobuff/Protobuff.csproj b/Protobuff/Protobuff.csproj index 318d495..10a58a7 100644 --- a/Protobuff/Protobuff.csproj +++ b/Protobuff/Protobuff.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0 + netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0 True Protobuf.Network.Library Protobuf Network Library From df7657d70f4bbfa56de50eafe7c90845746827b8 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Tue, 18 Mar 2025 14:32:09 +0100 Subject: [PATCH 06/27] changed various serializer type API from internal to public --- Benchmarks/SslBenchmark/Program.cs | 2 +- Json/MessageProtocol/JsonMessageClient.cs | 2 +- Json/MessageProtocol/JsonMessageServer.cs | 2 +- .../SecureJsonMessageClient.cs | 2 +- .../SecureJsonMessageServer.cs | 2 +- Json/P2P/JsonRelayClient.cs | 2 +- Json/P2P/Room/JsonRoomClient.cs | 2 +- Json/Pure/JsonClient.cs | 2 +- Json/Pure/JsonServer.cs | 2 +- Json/Pure/SecureJsonClient.cs | 2 +- Json/Pure/SecureJsonServer.cs | 2 +- .../MessagePackMessageClient.cs | 2 +- .../MessagePackMessageServer.cs | 2 +- .../SecureMessagePackMessageClient.cs | 2 +- .../SecureMessagePackMessageServer.cs | 2 +- MessagePack/P2P/MessagePackRelayClient.cs | 2 +- MessagePack/P2P/Room/MessagePackRoomClient.cs | 2 +- MessagePack/Pure/MessagePackClient.cs | 2 +- MessagePack/Pure/MessagePackServer.cs | 2 +- MessagePack/Pure/SecureMessagePackClient.cs | 2 +- MessagePack/Pure/SecureMessagePackServer.cs | 2 +- .../NetSerialiserMessageClient.cs | 2 +- .../NetSerialiserMessageServer.cs | 2 +- .../SecureNetSerialiserMessageClient.cs | 2 +- .../SecureNetSerialiserMessageServer.cs | 2 +- NetSerializer/P2P/NetSerialiserRelayClient.cs | 2 +- .../P2P/Room/NetSerialiserRoomClient.cs | 2 +- NetSerializer/Pure/NetSerialiserClient.cs | 2 +- NetSerializer/Pure/NetSerialiserServer.cs | 2 +- .../Pure/SecureNetSerialiserClient.cs | 2 +- .../Pure/SecureNetSerialiserServer.cs | 2 +- NetworkLibrary/TCP/SSL/SslSession.cs | 140 ++---------------- 32 files changed, 44 insertions(+), 158 deletions(-) diff --git a/Benchmarks/SslBenchmark/Program.cs b/Benchmarks/SslBenchmark/Program.cs index a5ff34e..41688c4 100644 --- a/Benchmarks/SslBenchmark/Program.cs +++ b/Benchmarks/SslBenchmark/Program.cs @@ -114,7 +114,7 @@ private static void InitializeClients() int j = 0; foreach (var client1 in clients) { - client1.Connect("127.0.0.1", port); + client1.Connect("172.28.255.247", port); j++; } Console.WriteLine("All Clients Connected"); diff --git a/Json/MessageProtocol/JsonMessageClient.cs b/Json/MessageProtocol/JsonMessageClient.cs index 0951cea..3492bb9 100644 --- a/Json/MessageProtocol/JsonMessageClient.cs +++ b/Json/MessageProtocol/JsonMessageClient.cs @@ -3,7 +3,7 @@ namespace JsonNetwork.MessageProtocol { - internal class JsonMessageClient : GenericMessageClientWrapper + public class JsonMessageClient : GenericMessageClientWrapper { } } diff --git a/Json/MessageProtocol/JsonMessageServer.cs b/Json/MessageProtocol/JsonMessageServer.cs index 9aa24ad..f822042 100644 --- a/Json/MessageProtocol/JsonMessageServer.cs +++ b/Json/MessageProtocol/JsonMessageServer.cs @@ -3,7 +3,7 @@ namespace JsonNetwork.MessageProtocol { - internal class JsonMessageServer : GenericMessageServerWrapper + public class JsonMessageServer : GenericMessageServerWrapper { public JsonMessageServer(int port) : base(port) { diff --git a/Json/MessageProtocol/SecureJsonMessageClient.cs b/Json/MessageProtocol/SecureJsonMessageClient.cs index af23e80..b5365d6 100644 --- a/Json/MessageProtocol/SecureJsonMessageClient.cs +++ b/Json/MessageProtocol/SecureJsonMessageClient.cs @@ -4,7 +4,7 @@ namespace JsonNetwork.MessageProtocol { - internal class SecureJsonMessageClient : GenericSecureMessageClientWrapper + public class SecureJsonMessageClient : GenericSecureMessageClientWrapper { public SecureJsonMessageClient(X509Certificate2 certificate) : base(certificate) { diff --git a/Json/MessageProtocol/SecureJsonMessageServer.cs b/Json/MessageProtocol/SecureJsonMessageServer.cs index 53b5c78..8327833 100644 --- a/Json/MessageProtocol/SecureJsonMessageServer.cs +++ b/Json/MessageProtocol/SecureJsonMessageServer.cs @@ -4,7 +4,7 @@ namespace JsonNetwork.MessageProtocol { - internal class SecureJsonMessageServer : GenericSecureMessageServerWrapper + public class SecureJsonMessageServer : GenericSecureMessageServerWrapper { public SecureJsonMessageServer(int port, X509Certificate2 cerificate) : base(port, cerificate) { diff --git a/Json/P2P/JsonRelayClient.cs b/Json/P2P/JsonRelayClient.cs index 4a4fce5..02ad2a3 100644 --- a/Json/P2P/JsonRelayClient.cs +++ b/Json/P2P/JsonRelayClient.cs @@ -4,7 +4,7 @@ namespace JsonNetwork.P2P { - internal class JsonRelayClient : RelayClientBase + public class JsonRelayClient : RelayClientBase { public JsonRelayClient(X509Certificate2 clientCert) : base(clientCert) { diff --git a/Json/P2P/Room/JsonRoomClient.cs b/Json/P2P/Room/JsonRoomClient.cs index 667db72..869d654 100644 --- a/Json/P2P/Room/JsonRoomClient.cs +++ b/Json/P2P/Room/JsonRoomClient.cs @@ -4,7 +4,7 @@ namespace JsonNetwork.P2P.Room { - internal class JsonRoomClient : SecureLobbyClient + public class JsonRoomClient : SecureLobbyClient { public JsonRoomClient(X509Certificate2 clientCert) : base(clientCert) { diff --git a/Json/Pure/JsonClient.cs b/Json/Pure/JsonClient.cs index b00fe64..0edfb02 100644 --- a/Json/Pure/JsonClient.cs +++ b/Json/Pure/JsonClient.cs @@ -3,7 +3,7 @@ namespace JsonNetwork.Pure { - internal class JsonClient : GenericClient + public class JsonClient : GenericClient { } } diff --git a/Json/Pure/JsonServer.cs b/Json/Pure/JsonServer.cs index 2e6eba8..29db3b6 100644 --- a/Json/Pure/JsonServer.cs +++ b/Json/Pure/JsonServer.cs @@ -3,7 +3,7 @@ namespace JsonNetwork.Pure { - internal class JsonServer : GenericServer + public class JsonServer : GenericServer { public JsonServer(int port, bool writeLenghtPrefix = true) : base(port, writeLenghtPrefix) { diff --git a/Json/Pure/SecureJsonClient.cs b/Json/Pure/SecureJsonClient.cs index 9bcb6fb..9698341 100644 --- a/Json/Pure/SecureJsonClient.cs +++ b/Json/Pure/SecureJsonClient.cs @@ -4,7 +4,7 @@ namespace JsonNetwork.Pure { - internal class SecureJsonClient : GenericSecureClient + public class SecureJsonClient : GenericSecureClient { public SecureJsonClient(X509Certificate2 certificate, bool writeLenghtPrefix = true) : base(certificate, writeLenghtPrefix) { diff --git a/Json/Pure/SecureJsonServer.cs b/Json/Pure/SecureJsonServer.cs index 7e0fbd5..3577d67 100644 --- a/Json/Pure/SecureJsonServer.cs +++ b/Json/Pure/SecureJsonServer.cs @@ -4,7 +4,7 @@ namespace JsonNetwork.Pure { - internal class SecureJsonServer : GenericSecureServer + public class SecureJsonServer : GenericSecureServer { public SecureJsonServer(int port, X509Certificate2 certificate, bool writeLenghtPrefix = true) : base(port, certificate, writeLenghtPrefix) { diff --git a/MessagePack/MessageProtocol/MessagePackMessageClient.cs b/MessagePack/MessageProtocol/MessagePackMessageClient.cs index 1a62d69..ee90990 100644 --- a/MessagePack/MessageProtocol/MessagePackMessageClient.cs +++ b/MessagePack/MessageProtocol/MessagePackMessageClient.cs @@ -3,7 +3,7 @@ namespace MessagePackNetwork.MessageProtocol { - internal class MessagePackMessageClient:GenericMessageClientWrapper + public class MessagePackMessageClient:GenericMessageClientWrapper { } diff --git a/MessagePack/MessageProtocol/MessagePackMessageServer.cs b/MessagePack/MessageProtocol/MessagePackMessageServer.cs index 1f9ca4e..55bf2fb 100644 --- a/MessagePack/MessageProtocol/MessagePackMessageServer.cs +++ b/MessagePack/MessageProtocol/MessagePackMessageServer.cs @@ -8,7 +8,7 @@ namespace MessagePackNetwork.MessageProtocol { - internal class MessagePackMessageServer : GenericMessageServerWrapper + public class MessagePackMessageServer : GenericMessageServerWrapper { public MessagePackMessageServer(int port) : base(port) { diff --git a/MessagePack/MessageProtocol/SecureMessagePackMessageClient.cs b/MessagePack/MessageProtocol/SecureMessagePackMessageClient.cs index 05766d6..057bd1a 100644 --- a/MessagePack/MessageProtocol/SecureMessagePackMessageClient.cs +++ b/MessagePack/MessageProtocol/SecureMessagePackMessageClient.cs @@ -9,7 +9,7 @@ namespace MessagePackNetwork.MessageProtocol { - internal class SecureMessagePackMessageClient : GenericSecureMessageClientWrapper + public class SecureMessagePackMessageClient : GenericSecureMessageClientWrapper { public SecureMessagePackMessageClient(X509Certificate2 certificate) : base(certificate) { diff --git a/MessagePack/MessageProtocol/SecureMessagePackMessageServer.cs b/MessagePack/MessageProtocol/SecureMessagePackMessageServer.cs index 7f48b08..7ebac6e 100644 --- a/MessagePack/MessageProtocol/SecureMessagePackMessageServer.cs +++ b/MessagePack/MessageProtocol/SecureMessagePackMessageServer.cs @@ -10,7 +10,7 @@ namespace MessagePackNetwork.MessageProtocol { - internal class SecureMessagePackMessageServer : GenericSecureMessageServerWrapper + public class SecureMessagePackMessageServer : GenericSecureMessageServerWrapper { public SecureMessagePackMessageServer(int port, X509Certificate2 cerificate) : base(port, cerificate) { diff --git a/MessagePack/P2P/MessagePackRelayClient.cs b/MessagePack/P2P/MessagePackRelayClient.cs index a3f3305..63e1103 100644 --- a/MessagePack/P2P/MessagePackRelayClient.cs +++ b/MessagePack/P2P/MessagePackRelayClient.cs @@ -8,7 +8,7 @@ namespace MessagePackNetwork.P2P { - internal class MessagePackRelayClient : RelayClientBase + public class MessagePackRelayClient : RelayClientBase { public MessagePackRelayClient(X509Certificate2 clientCert) : base(clientCert) { diff --git a/MessagePack/P2P/Room/MessagePackRoomClient.cs b/MessagePack/P2P/Room/MessagePackRoomClient.cs index bae2b79..3c2b26d 100644 --- a/MessagePack/P2P/Room/MessagePackRoomClient.cs +++ b/MessagePack/P2P/Room/MessagePackRoomClient.cs @@ -7,7 +7,7 @@ namespace MessagePackNetwork.P2P.Lobby { - internal class MessagePackRoomClient : SecureLobbyClient + public class MessagePackRoomClient : SecureLobbyClient { public MessagePackRoomClient(X509Certificate2 clientCert) : base(clientCert) { diff --git a/MessagePack/Pure/MessagePackClient.cs b/MessagePack/Pure/MessagePackClient.cs index 32c999d..031c1de 100644 --- a/MessagePack/Pure/MessagePackClient.cs +++ b/MessagePack/Pure/MessagePackClient.cs @@ -8,7 +8,7 @@ namespace MessagePackNetwork.Pure { - internal class MessagePackClient:GenericClient + public class MessagePackClient:GenericClient { } } diff --git a/MessagePack/Pure/MessagePackServer.cs b/MessagePack/Pure/MessagePackServer.cs index 22ef10b..70a4a93 100644 --- a/MessagePack/Pure/MessagePackServer.cs +++ b/MessagePack/Pure/MessagePackServer.cs @@ -9,7 +9,7 @@ namespace MessagePackNetwork.Pure { - internal class MessagePackServer : GenericServer + public class MessagePackServer : GenericServer { public MessagePackServer(int port, bool writeLenghtPrefix = true) : base(port, writeLenghtPrefix) { diff --git a/MessagePack/Pure/SecureMessagePackClient.cs b/MessagePack/Pure/SecureMessagePackClient.cs index 13f8e6e..9c932bc 100644 --- a/MessagePack/Pure/SecureMessagePackClient.cs +++ b/MessagePack/Pure/SecureMessagePackClient.cs @@ -9,7 +9,7 @@ namespace MessagePackNetwork.Pure { - internal class SecureMessagePackClient : GenericSecureClient + public class SecureMessagePackClient : GenericSecureClient { public SecureMessagePackClient(X509Certificate2 certificate, bool writeLenghtPrefix = true) : base(certificate, writeLenghtPrefix) { diff --git a/MessagePack/Pure/SecureMessagePackServer.cs b/MessagePack/Pure/SecureMessagePackServer.cs index d75ed58..55cc737 100644 --- a/MessagePack/Pure/SecureMessagePackServer.cs +++ b/MessagePack/Pure/SecureMessagePackServer.cs @@ -9,7 +9,7 @@ namespace MessagePackNetwork.Pure { - internal class SecureMessagePackServer : GenericSecureServer + public class SecureMessagePackServer : GenericSecureServer { public SecureMessagePackServer(int port, X509Certificate2 certificate, bool writeLenghtPrefix = true) : base(port, certificate, writeLenghtPrefix) { diff --git a/NetSerializer/MessageProtocol/NetSerialiserMessageClient.cs b/NetSerializer/MessageProtocol/NetSerialiserMessageClient.cs index b5e48d8..2783015 100644 --- a/NetSerializer/MessageProtocol/NetSerialiserMessageClient.cs +++ b/NetSerializer/MessageProtocol/NetSerialiserMessageClient.cs @@ -3,7 +3,7 @@ namespace NetSerializerNetwork.MessageProtocol { - internal class NetSerialiserMessageClient : GenericMessageClientWrapper + public class NetSerialiserMessageClient : GenericMessageClientWrapper { } } diff --git a/NetSerializer/MessageProtocol/NetSerialiserMessageServer.cs b/NetSerializer/MessageProtocol/NetSerialiserMessageServer.cs index 677cca9..ff3851d 100644 --- a/NetSerializer/MessageProtocol/NetSerialiserMessageServer.cs +++ b/NetSerializer/MessageProtocol/NetSerialiserMessageServer.cs @@ -3,7 +3,7 @@ namespace NetSerializerNetwork.MessageProtocol { - internal class NetSerialiserMessageServer : GenericMessageServerWrapper + public class NetSerialiserMessageServer : GenericMessageServerWrapper { public NetSerialiserMessageServer(int port) : base(port) { diff --git a/NetSerializer/MessageProtocol/SecureNetSerialiserMessageClient.cs b/NetSerializer/MessageProtocol/SecureNetSerialiserMessageClient.cs index de6a774..d55380f 100644 --- a/NetSerializer/MessageProtocol/SecureNetSerialiserMessageClient.cs +++ b/NetSerializer/MessageProtocol/SecureNetSerialiserMessageClient.cs @@ -4,7 +4,7 @@ namespace NetSerializerNetwork.MessageProtocol { - internal class SecureNetSerialiserMessageClient : GenericSecureMessageClientWrapper + public class SecureNetSerialiserMessageClient : GenericSecureMessageClientWrapper { public SecureNetSerialiserMessageClient(X509Certificate2 certificate) : base(certificate) { diff --git a/NetSerializer/MessageProtocol/SecureNetSerialiserMessageServer.cs b/NetSerializer/MessageProtocol/SecureNetSerialiserMessageServer.cs index 35b8f1d..71b671f 100644 --- a/NetSerializer/MessageProtocol/SecureNetSerialiserMessageServer.cs +++ b/NetSerializer/MessageProtocol/SecureNetSerialiserMessageServer.cs @@ -4,7 +4,7 @@ namespace NetSerializerNetwork.MessageProtocol { - internal class SecureNetSerialiserMessageServer : GenericSecureMessageServerWrapper + public class SecureNetSerialiserMessageServer : GenericSecureMessageServerWrapper { public SecureNetSerialiserMessageServer(int port, X509Certificate2 cerificate) : base(port, cerificate) { diff --git a/NetSerializer/P2P/NetSerialiserRelayClient.cs b/NetSerializer/P2P/NetSerialiserRelayClient.cs index 55417c3..80c86d6 100644 --- a/NetSerializer/P2P/NetSerialiserRelayClient.cs +++ b/NetSerializer/P2P/NetSerialiserRelayClient.cs @@ -4,7 +4,7 @@ namespace NetSerializerNetwork.P2P { - internal class NetSerialiserRelayClient : RelayClientBase + public class NetSerialiserRelayClient : RelayClientBase { public NetSerialiserRelayClient(X509Certificate2 clientCert) : base(clientCert) { diff --git a/NetSerializer/P2P/Room/NetSerialiserRoomClient.cs b/NetSerializer/P2P/Room/NetSerialiserRoomClient.cs index 8b68241..1823876 100644 --- a/NetSerializer/P2P/Room/NetSerialiserRoomClient.cs +++ b/NetSerializer/P2P/Room/NetSerialiserRoomClient.cs @@ -4,7 +4,7 @@ namespace NetSerializerNetwork.P2P.Room { - internal class NetSerialiserRoomClient : SecureLobbyClient + public class NetSerialiserRoomClient : SecureLobbyClient { public NetSerialiserRoomClient(X509Certificate2 clientCert) : base(clientCert) { diff --git a/NetSerializer/Pure/NetSerialiserClient.cs b/NetSerializer/Pure/NetSerialiserClient.cs index 025e2e0..0eb85bc 100644 --- a/NetSerializer/Pure/NetSerialiserClient.cs +++ b/NetSerializer/Pure/NetSerialiserClient.cs @@ -3,7 +3,7 @@ namespace NetSerializerNetwork.Pure { - internal class NetSerialiserClient : GenericClient + public class NetSerialiserClient : GenericClient { } } diff --git a/NetSerializer/Pure/NetSerialiserServer.cs b/NetSerializer/Pure/NetSerialiserServer.cs index 72fd940..90a215f 100644 --- a/NetSerializer/Pure/NetSerialiserServer.cs +++ b/NetSerializer/Pure/NetSerialiserServer.cs @@ -3,7 +3,7 @@ namespace NetSerializerNetwork.Pure { - internal class NetSerialiserServer : GenericServer + public class NetSerialiserServer : GenericServer { public NetSerialiserServer(int port, bool writeLenghtPrefix = true) : base(port, writeLenghtPrefix) { diff --git a/NetSerializer/Pure/SecureNetSerialiserClient.cs b/NetSerializer/Pure/SecureNetSerialiserClient.cs index b78f95e..9648500 100644 --- a/NetSerializer/Pure/SecureNetSerialiserClient.cs +++ b/NetSerializer/Pure/SecureNetSerialiserClient.cs @@ -4,7 +4,7 @@ namespace NetSerializerNetwork.Pure { - internal class SecureNetSerialiserClient : GenericSecureClient + public class SecureNetSerialiserClient : GenericSecureClient { public SecureNetSerialiserClient(X509Certificate2 certificate, bool writeLenghtPrefix = true) : base(certificate, writeLenghtPrefix) { diff --git a/NetSerializer/Pure/SecureNetSerialiserServer.cs b/NetSerializer/Pure/SecureNetSerialiserServer.cs index 1303dd6..c765c59 100644 --- a/NetSerializer/Pure/SecureNetSerialiserServer.cs +++ b/NetSerializer/Pure/SecureNetSerialiserServer.cs @@ -4,7 +4,7 @@ namespace NetSerializerNetwork.Pure { - internal class SecureNetSerialiserServer : GenericSecureServer + public class SecureNetSerialiserServer : GenericSecureServer { public SecureNetSerialiserServer(int port, X509Certificate2 certificate, bool writeLenghtPrefix = true) : base(port, certificate, writeLenghtPrefix) { diff --git a/NetworkLibrary/TCP/SSL/SslSession.cs b/NetworkLibrary/TCP/SSL/SslSession.cs index 02dae28..b434c95 100644 --- a/NetworkLibrary/TCP/SSL/SslSession.cs +++ b/NetworkLibrary/TCP/SSL/SslSession.cs @@ -28,9 +28,7 @@ public class SslSession : IAsyncSession protected Spinlock enqueueLock = new Spinlock(); protected SslStream sessionStream; protected byte[] receiveBuffer; -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// protected Memory receiveMemory; -//#endif + protected byte[] sendBuffer; protected Guid sessionId; protected IPEndPoint RemoteEP; @@ -66,14 +64,9 @@ public void StartSession() protected virtual void ConfigureBuffers() { - receiveBuffer = /*new byte[ReceiveBufferSize];*/ BufferPool.RentBuffer(ReceiveBufferSize); - -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER - -// receiveMemory = new Memory(receiveBuffer); -//#endif - - if (UseQueue) sendBuffer = BufferPool.RentBuffer(SendBufferSize); + receiveBuffer = BufferPool.RentBuffer(ReceiveBufferSize); + if (UseQueue) + sendBuffer = BufferPool.RentBuffer(SendBufferSize); } @@ -92,7 +85,7 @@ public void SendAsync(List> batch) return; try { - SendAsync_( batch); + SendAsyncInternal( batch); } catch (Exception e) { @@ -101,7 +94,7 @@ public void SendAsync(List> batch) "Unexpected error while sending async with ssl session" + e.Message + "Trace " + e.StackTrace); } } - private void SendAsync_(List> batch) + private void SendAsyncInternal(List> batch) { enqueueLock.Take(); if (IsSessionClosing()) @@ -152,7 +145,7 @@ public void SendAsync(byte[] buffer, int offset, int count) return; try { - SendAsync_(buffer, offset, count); + SendAsyncInternal(buffer, offset, count); } catch(Exception e) { @@ -161,7 +154,7 @@ public void SendAsync(byte[] buffer, int offset, int count) "Unexpected error while sending async with ssl session" + e.Message+"Trace " +e.StackTrace); } } - private void SendAsync_(byte[] buffer, int offset, int count) + private void SendAsyncInternal(byte[] buffer, int offset, int count) { enqueueLock.Take(); if (IsSessionClosing()) @@ -205,7 +198,7 @@ public void SendAsync(byte[] buffer) return; try { - SendAsync_(buffer); + SendAsyncInternal(buffer); } catch (Exception e) { @@ -214,7 +207,7 @@ public void SendAsync(byte[] buffer) "Unexpected error while sending async with ssl session" + e.Message + "Trace " + e.StackTrace); } } - private void SendAsync_(byte[] buffer) + private void SendAsyncInternal(byte[] buffer) { enqueueLock.Take(); if (IsSessionClosing()) @@ -253,8 +246,6 @@ private void SendAsync_(byte[] buffer) // this can only be called inside send lock critical section protected void FlushAndSend() { - //ThreadPool.UnsafeQueueUserWorkItem((s) => - //{ try { messageQueue.TryFlushQueue(ref sendBuffer, 0, out int amountWritten); @@ -265,17 +256,10 @@ protected void FlushAndSend() if (!IsSessionClosing()) throw; } - - // }, null); - } + protected void WriteOnSessionStream(int count) { -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// WriteModern(count); -// return; -//#endif - try { sessionStream.BeginWrite(sendBuffer, 0, count, SentInternal, null); @@ -287,68 +271,6 @@ protected void WriteOnSessionStream(int count) totalBytesSend += count; } -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER - -// private async void WriteModern(int count) -// { -// try -// { -// //somehow faster than while loop... -// Top: -// totalBytesSend += count; -// await sessionStream.WriteAsync(new ReadOnlyMemory(sendBuffer, 0, count)).ConfigureAwait(false); - -// if (IsSessionClosing()) -// { -// ReleaseSendResourcesIdempotent(); -// return; -// } -// if (messageQueue.TryFlushQueue(ref sendBuffer, 0, out int amountWritten)) -// { -// count = amountWritten; -// goto Top; - -// } - -// // here there was nothing to flush -// bool flush = false; - -// enqueueLock.Take(); -// // ask again safely -// if (messageQueue.IsEmpty()) -// { -// messageQueue.Flush(); - -// SendSemaphore.Release(); -// enqueueLock.Release(); -// if (IsSessionClosing()) -// ReleaseSendResourcesIdempotent(); -// return; -// } -// else -// { -// flush = true; - -// } -// enqueueLock.Release(); - -// // something got into queue just before i exit, we need to flush it -// if (flush) -// { -// if (messageQueue.TryFlushQueue(ref sendBuffer, 0, out int amountWritten_)) -// { -// count = amountWritten_; -// goto Top; -// } -// } -// } -// catch (Exception e) -// { -// HandleError("Error on sent callback ssl", e); -// } -// } -//#endif - private void SentInternal(IAsyncResult ar) { if (ar.CompletedSynchronously) @@ -427,10 +349,6 @@ private void Sent(IAsyncResult ar) protected virtual void Receive() { -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// ReceiveNew(); -// return; -//#endif if (IsSessionClosing()) { ReleaseReceiveResourcesIdempotent(); @@ -446,39 +364,7 @@ protected virtual void Receive() ReleaseReceiveResourcesIdempotent(); } } -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER - -// private async void ReceiveNew() -// { -// try -// { -// while (true) -// { -// if (IsSessionClosing()) -// { -// ReleaseReceiveResourcesIdempotent(); -// return; -// } -// var amountRead = await sessionStream.ReadAsync(receiveMemory).ConfigureAwait(false); -// if (amountRead > 0) -// { -// HandleReceived(receiveBuffer, 0, amountRead); -// } -// else -// { -// EndSession(); -// ReleaseReceiveResourcesIdempotent(); -// } -// totalBytesReceived += amountRead; -// } -// } -// catch (Exception ex) -// { -// HandleError("White receiving from SSL socket an error occurred", ex); -// ReleaseReceiveResourcesIdempotent(); -// } -// } -//#endif + protected virtual void Received(IAsyncResult ar) { if (IsSessionClosing()) @@ -563,7 +449,7 @@ public void EndSession() } - int sendResReleased = 0; + int sendResReleased = 0; protected void ReleaseSendResourcesIdempotent() { if (Interlocked.CompareExchange(ref sendResReleased, 1, 0) == 0) From 4bc51902a77c383e5f2970b9ba6d767f0596e5fd Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Wed, 19 Mar 2025 17:02:57 +0100 Subject: [PATCH 07/27] started distributed p2p development --- .../Components/IAuthenticator.cs | 16 ++ .../Components/IClientConnection.cs | 12 ++ .../Components/IClientDbConnector.cs | 10 ++ .../Components/MessageConnection.cs | 20 +++ .../Components/SessionManager.cs | 58 +++++++ .../DistributedP2P/DistributedLobbyServer.cs | 157 ++++++++++++++++++ .../DistributedP2P/ServerSession.cs | 27 +++ NetworkLibrary/TCP/SSL/SslClient.cs | 11 +- NetworkLibrary/TCP/SSL/SslServer.cs | 8 +- NetworkLibrary/TCP/SSL/SslSession.cs | 5 - 10 files changed, 305 insertions(+), 19 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Components/IAuthenticator.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/IClientConnection.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/IClientDbConnector.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/MessageConnection.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/SessionManager.cs create mode 100644 NetworkLibrary/DistributedP2P/DistributedLobbyServer.cs create mode 100644 NetworkLibrary/DistributedP2P/ServerSession.cs diff --git a/NetworkLibrary/DistributedP2P/Components/IAuthenticator.cs b/NetworkLibrary/DistributedP2P/Components/IAuthenticator.cs new file mode 100644 index 0000000..2bf1b2b --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/IAuthenticator.cs @@ -0,0 +1,16 @@ +using NetworkLibrary.MessageProtocol; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Components +{ + public interface IAuthenticationResult + { + bool Success { get;} + } + public interface IAuthenticator + { + IAuthenticationResult Authenticate(IClientConnection messageConnection, Guid guid); + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/IClientConnection.cs b/NetworkLibrary/DistributedP2P/Components/IClientConnection.cs new file mode 100644 index 0000000..ff755ee --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/IClientConnection.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Components +{ + // Send Receive messages. + // know about disconnect. + public interface IClientConnection + { + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/IClientDbConnector.cs b/NetworkLibrary/DistributedP2P/Components/IClientDbConnector.cs new file mode 100644 index 0000000..c0c819c --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/IClientDbConnector.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Components +{ + public interface IClientDbConnector + { + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/MessageConnection.cs b/NetworkLibrary/DistributedP2P/Components/MessageConnection.cs new file mode 100644 index 0000000..ed90d23 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/MessageConnection.cs @@ -0,0 +1,20 @@ +using MessageProtocol; +using System; +using System.Collections.Generic; +using System.Text; +using NetworkLibrary.MessageProtocol; + + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class MessageConnection: IClientConnection where S : ISerializer, new() + { + SecureMessageServer server; + public MessageConnection(SecureMessageServer serverConnection ) + { + this.server = serverConnection; + } + + + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/SessionManager.cs b/NetworkLibrary/DistributedP2P/Components/SessionManager.cs new file mode 100644 index 0000000..865bf95 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/SessionManager.cs @@ -0,0 +1,58 @@ +using NetworkLibrary.MessageProtocol; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Components +{ + // Creation, and managing destruction of sessions. + //Created after Authentication. + // Routing messages between sessions. + // Where do we put rooms? probably here + internal class SessionManager where S : ISerializer, new() + { + internal ConcurrentDictionary serverSessions = new ConcurrentDictionary(); + + internal void HandleMessage(Guid guid, MessageEnvelope envelope) + { + + } + + public void CreateSession(IClientConnection messageConnection1, IAuthenticationResult result, Guid guid, IPEndPoint sessionEp) + { + serverSessions.TryAdd(guid, new ServerSession()); + } + + internal void RouteP2PMessageTcp(Guid guid, byte[] bytes, int offset, int count) + { + + } + + internal void RouteP2PMessageUdp(IPEndPoint adress, byte[] bytes, int offset, int count) + { + + } + + internal void HandleDisconnect(Guid guid) + { + + } + + internal void HandleTcpPipeDisconnect(Guid guid) + { + + } + + internal void TcpPipeCreated(Guid guid) + { + + } + + internal void CreateSession(MessageConnection messageConnection, Guid guid, IPEndPoint sessionEp) where S : ISerializer, new() + { + throw new NotImplementedException(); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/DistributedLobbyServer.cs new file mode 100644 index 0000000..e254ea1 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/DistributedLobbyServer.cs @@ -0,0 +1,157 @@ +using NetworkLibrary.Components.Crypto.Certificate; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.P2P.Components.StateManagement; +using NetworkLibrary.TCP.Base; +using NetworkLibrary.TCP.SSL.Base; +using NetworkLibrary.UDP; +using NetworkLibrary.MessageProtocol; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace NetworkLibrary.DistributedP2P +{ + + public class Dependencies + { + public IAuthenticator Authenticator; + public IClientDbConnector DbConnector; + } + public class ServerParameters + { + public X509Certificate2 certificate; + public int SSlPort; + public int TcpPort; + public int UdpPort; + } + public class DistributedLobbyServerBase where S : ISerializer, new() + { + public readonly int SSlPort; + public readonly int TcpPort; + public readonly int UdpPort; + + private X509Certificate2 serverCertificate; + + SecureMessageServer sslServer; + AsyncTcpServer tcpServer; + AsyncUdpServer udpServer; + + IAuthenticator authenticator; + IClientDbConnector dbConnector; + + SessionManager sessionManager; + public DistributedLobbyServerBase(Dependencies dependencies, ServerParameters parameters) + { + this.authenticator = dependencies.Authenticator; + this.dbConnector = dependencies.DbConnector; + SSlPort = parameters.SSlPort; + TcpPort = parameters.TcpPort; + UdpPort = parameters.UdpPort; + + serverCertificate = parameters.certificate ?? CertificateGenerator.GenerateSelfSignedCertificate(); + } + + + public void StartServer() + { + sslServer = new SecureMessageServer(SSlPort, serverCertificate); + tcpServer = new AsyncTcpServer(TcpPort); + udpServer = new AsyncUdpServer(UdpPort); + + sslServer.OnClientRequestedConnection += ValidateSslConnection; + sslServer.OnClientAccepted += SslClientAccepted; + sslServer.OnClientDisconnected += SslClientDisconnected; + + tcpServer.OnClientAccepting += ValidateTcpConnection; + tcpServer.OnClientAccepted += TcpClientAccepted; + tcpServer.OnClientDisconnected += TcpclientDisconnected; + + sslServer.OnMessageReceived += SslMessageReceived; + tcpServer.OnBytesReceived += TcpBytesReceived; + udpServer.OnBytesRecieved += UdpBytesReceived; + + udpServer.StartServer(); + tcpServer.StartServer(); + sslServer.StartServer(); + } + + + + private bool ValidateTcpConnection(Socket acceptedSocket) + { + return true; + } + + private void TcpClientAccepted(Guid guid) + { + sessionManager.TcpPipeCreated(guid); + } + private void TcpclientDisconnected(Guid guid) + { + sessionManager.HandleTcpPipeDisconnect(guid); + } + + private void SslClientDisconnected(Guid guid) + { + sessionManager.HandleDisconnect(guid); + } + + private bool ValidateSslConnection(Socket acceptedSocket) + { + // we will do Ddos protection here; + return true; + } + + + private void SslClientAccepted(Guid guid) + { + // this must be async, later. + MessageConnection messageConnection = new MessageConnection(sslServer); + IAuthenticationResult result = authenticator.Authenticate(messageConnection, guid); + + var sessionEp = sslServer.GetSessionEndpoint(guid); + sessionManager.CreateSession(messageConnection, result, guid, sessionEp); + } + + #region Receive + // so here message can be p2p message, clients business. + // internal messages + private void SslMessageReceived(Guid guid, MessageEnvelope envelope) + { + if (envelope.IsInternal) + { + HandleInternalMessage(guid, envelope); + } + else + { + sessionManager.HandleMessage(guid, envelope); + } + } + + private void HandleInternalMessage(Guid guid, MessageEnvelope envelope) + { + //server messages. + } + + private void TcpBytesReceived(Guid guid, byte[] bytes, int offset, int count) + { + sessionManager.RouteP2PMessageTcp(guid, bytes, offset, count); + } + + private void UdpBytesReceived(IPEndPoint adress, byte[] bytes, int offset, int count) + { + sessionManager.RouteP2PMessageUdp(adress, bytes, offset, count); + } + #endregion + + public void ShutDownServer() + { + sslServer.ShutdownServer(); + tcpServer.ShutdownServer(); + udpServer.Dispose(); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/ServerSession.cs b/NetworkLibrary/DistributedP2P/ServerSession.cs new file mode 100644 index 0000000..160f0c6 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/ServerSession.cs @@ -0,0 +1,27 @@ +using NetworkLibrary.DistributedP2P.Components; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P +{ + enum Sessionstate + { + Uninitialized, + Authenticating, + ObtainingClientInfo, + EstablishingUdp, + Running + } + + // should hold the data about the client. Keys etc everything. + + internal class ServerSession + { + public Sessionstate state; + + public ServerSession() + { + } + } +} diff --git a/NetworkLibrary/TCP/SSL/SslClient.cs b/NetworkLibrary/TCP/SSL/SslClient.cs index 30c8bd3..22a3e13 100644 --- a/NetworkLibrary/TCP/SSL/SslClient.cs +++ b/NetworkLibrary/TCP/SSL/SslClient.cs @@ -174,16 +174,7 @@ private void Connected(string domainName, Socket clientSocket) clientSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, TcpKeepAliveInterval); clientSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, TcpKeepAliveProbes); #endif - //const int KeepAliveEnable = 1; // Enable keepalive - //const int KeepAliveTime = 60000; // Time (ms) to start keepalive (1 min) - //const int KeepAliveInterval = 10000; // Interval (ms) between keepalive probes (10 sec) - - //byte[] inOptionValues = new byte[12]; - //BitConverter.GetBytes(KeepAliveEnable).CopyTo(inOptionValues, 0); - //BitConverter.GetBytes(KeepAliveTime).CopyTo(inOptionValues, 4); - //BitConverter.GetBytes(KeepAliveInterval).CopyTo(inOptionValues, 8); - - //clientSocket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); + sslStream = new SslStream(new NetworkStream(clientSocket, true), false, ValidateCeriticate); sslStream.AuthenticateAsClient(domainName, diff --git a/NetworkLibrary/TCP/SSL/SslServer.cs b/NetworkLibrary/TCP/SSL/SslServer.cs index ce555f6..898db79 100644 --- a/NetworkLibrary/TCP/SSL/SslServer.cs +++ b/NetworkLibrary/TCP/SSL/SslServer.cs @@ -44,16 +44,16 @@ public class SslServer : TcpServerBase public int SessionCount => Sessions.Count; - private protected ConcurrentDictionary Sessions = new ConcurrentDictionary(); + internal ConcurrentDictionary Sessions = new ConcurrentDictionary(); internal ConcurrentDictionary Stats { get; } = new ConcurrentDictionary(); private Socket serverSocket; private X509Certificate2 certificate; private TcpServerStatisticsPublisher statisticsPublisher; - const int TcpKeepAliveTime = 15; // Start keepalive after 60 seconds - const int TcpKeepAliveInterval = 5; // Send probes every 10 seconds - const int TcpKeepAliveProbes = 2; // Retry 5 times before dropping + const int TcpKeepAliveTime = 30; // Start keepalive after 30 seconds + const int TcpKeepAliveInterval = 10; // Send probes every 10 seconds + const int TcpKeepAliveProbes = 2; // Retry 3 times before dropping public SslServer(int port, X509Certificate2 certificate) { ServerPort = port; diff --git a/NetworkLibrary/TCP/SSL/SslSession.cs b/NetworkLibrary/TCP/SSL/SslSession.cs index a0b91ef..72c2b80 100644 --- a/NetworkLibrary/TCP/SSL/SslSession.cs +++ b/NetworkLibrary/TCP/SSL/SslSession.cs @@ -69,11 +69,6 @@ protected virtual void ConfigureBuffers() sendBuffer = BufferPool.RentBuffer(SendBufferSize); receiveBuffer = BufferPool.RentBuffer(ReceiveBufferSize); -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER - -// receiveMemory = new Memory(receiveBuffer); -//#endif - if (UseQueue) sendBuffer = BufferPool.RentBuffer(SendBufferSize); } From 52cf4da9abe95ed23d8d571e343504524499abf1 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Wed, 26 Mar 2025 16:51:50 +0100 Subject: [PATCH 08/27] pogress on distributed p2p, authenticated connection --- .../Crypto/DiffieHellman/DiffieHellman.cs | 63 ++++++ .../Crypto/DiffieHellman/EllipticCurveDH.cs | 43 ++++ .../Crypto/DigitalSignature/PrivateKeySign.cs | 27 +++ .../Crypto/DigitalSignature/PublicKeySign.cs | 102 +++++++++ .../Components/Crypto/KeyDerivation/HKDF.cs | 65 ++++++ .../DistributedP2P/Client/ChannelInfo.cs | 14 ++ .../Client/DistributedLobbyClient.cs | 144 ++++++++++++ .../Client/IClientAuthenticationProvider.cs | 7 + .../Client/IClientAuthenticationToken.cs | 9 + .../Client/IClientConnection.cs | 11 + .../Client/IClientDbConnection.cs | 11 + .../DistributedP2P/Client/ITcpChannel.cs | 14 ++ .../DistributedP2P/Client/SecureTcpChannel.cs | 73 ++++++ .../StateManagement/ClientConnectionState.cs | 85 +++++++ .../Client/StateManagement/ClientPipeState.cs | 25 +++ .../Components/ConversationStateBase.cs | 81 +++++++ .../Components/IAuthenticator.cs | 16 -- .../Components/IClientConnection.cs | 12 - .../Components/IClientDbConnector.cs | 10 - .../Components/IConversationState.cs | 19 ++ .../Components/ITimeProvider.cs | 9 + .../Components/InternalConstants.cs | 16 ++ .../Components/MessageConnection.cs | 20 -- .../Components/SessionManager.cs | 58 ----- .../DistributedP2P/Components/StateManager.cs | 48 ++++ .../{ => Server}/DistributedLobbyServer.cs | 121 ++++++---- .../DistributedP2P/Server/IAuthenticator.cs | 20 ++ .../Server/IDistributedConnection.cs | 14 ++ .../Server/IServerDbConnector.cs | 19 ++ .../DistributedP2P/Server/PipeAssociator.cs | 209 ++++++++++++++++++ .../DistributedP2P/Server/PipeState.cs | 32 +++ .../{ => Server}/ServerSession.cs | 16 +- .../DistributedP2P/Server/SessionManager.cs | 128 +++++++++++ .../StateManagement/ServerConnectionState.cs | 169 ++++++++++++++ .../Server/StateManagement/ServerPipeState.cs | 20 ++ NetworkLibrary/NetworkLibrary.csproj | 2 +- .../HolePunch/KnownTypeSerializer.cs | 34 +++ 37 files changed, 1598 insertions(+), 168 deletions(-) create mode 100644 NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs create mode 100644 NetworkLibrary/Components/Crypto/DiffieHellman/EllipticCurveDH.cs create mode 100644 NetworkLibrary/Components/Crypto/DigitalSignature/PrivateKeySign.cs create mode 100644 NetworkLibrary/Components/Crypto/DigitalSignature/PublicKeySign.cs create mode 100644 NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/IClientAuthenticationProvider.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/IClientAuthenticationToken.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/IClientConnection.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/IClientDbConnection.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs delete mode 100644 NetworkLibrary/DistributedP2P/Components/IAuthenticator.cs delete mode 100644 NetworkLibrary/DistributedP2P/Components/IClientConnection.cs delete mode 100644 NetworkLibrary/DistributedP2P/Components/IClientDbConnector.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/IConversationState.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/ITimeProvider.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/InternalConstants.cs delete mode 100644 NetworkLibrary/DistributedP2P/Components/MessageConnection.cs delete mode 100644 NetworkLibrary/DistributedP2P/Components/SessionManager.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/StateManager.cs rename NetworkLibrary/DistributedP2P/{ => Server}/DistributedLobbyServer.cs (52%) create mode 100644 NetworkLibrary/DistributedP2P/Server/IAuthenticator.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/IServerDbConnector.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/PipeAssociator.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/PipeState.cs rename NetworkLibrary/DistributedP2P/{ => Server}/ServerSession.cs (60%) create mode 100644 NetworkLibrary/DistributedP2P/Server/SessionManager.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs diff --git a/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs b/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs new file mode 100644 index 0000000..d083107 --- /dev/null +++ b/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Security.Cryptography; +using System.Text; + +namespace NetworkLibrary.Components.Crypto.DiffieHellman +{ + public class DiffieHellman + { + // Well-known prime for DH(the well-known 2048-bit MODP group from RFC 3526) + private const string PrimeHex = "0FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BB" + + "EA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E" + + "9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361" + + "C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA1" + + "8217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956" + + "AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF"; + + private static readonly BigInteger _prime = BigInteger.Parse(PrimeHex, System.Globalization.NumberStyles.HexNumber); + + private static readonly BigInteger Generator = new BigInteger(2); + + private readonly BigInteger _privateKey; + private readonly BigInteger _publicKey; + public DiffieHellman() + { + _privateKey = GenerateRandomPrivateKey(); + _publicKey = BigInteger.ModPow(Generator, _privateKey, _prime); + } + + public byte[] GetPublicKey() + { + return _publicKey.ToByteArray(); + } + + public byte[] CalculateSharedSecret(byte[] otherPublicKeyBytes) + { + BigInteger otherPublicKey = new BigInteger(otherPublicKeyBytes); + BigInteger sharedSecret = BigInteger.ModPow(otherPublicKey, _privateKey, _prime); + return sharedSecret.ToByteArray(); + } + + private BigInteger GenerateRandomPrivateKey() + { + // Recommended key size for security (at least 256 bits) + int keySize = 256 / 8; + byte[] randomBytes = new byte[keySize + 1]; // Extra byte to ensure positive BigInteger + + using (var rng = RandomNumberGenerator.Create()) + { + do + { + rng.GetBytes(randomBytes); + // Ensure private key is in [1, p-1] + var key = new BigInteger(randomBytes); + if (key > 1 && key < _prime - 1) + return key; + } while (true); + } + } + + } +} diff --git a/NetworkLibrary/Components/Crypto/DiffieHellman/EllipticCurveDH.cs b/NetworkLibrary/Components/Crypto/DiffieHellman/EllipticCurveDH.cs new file mode 100644 index 0000000..a3ab233 --- /dev/null +++ b/NetworkLibrary/Components/Crypto/DiffieHellman/EllipticCurveDH.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace NetworkLibrary.Components.Crypto.DiffieHellman +{ +#if NET6_0_OR_GREATER + public class ECDH : IDisposable + { + private readonly ECDiffieHellman _ecdh; + + public ECDH() + { + _ecdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256); + } + + public ECDH(ECCurve curve) + { + _ecdh = ECDiffieHellman.Create(curve); + } + + public byte[] GetPublicKey() + { + return _ecdh.PublicKey.ExportSubjectPublicKeyInfo(); + } + + public byte[] CalculateSharedSecret(byte[] otherPublicKeyBytes) + { + using (ECDiffieHellman otherParty = ECDiffieHellman.Create()) + { + otherParty.ImportSubjectPublicKeyInfo(otherPublicKeyBytes, out _); + return _ecdh.DeriveKeyMaterial(otherParty.PublicKey); + } + } + + public void Dispose() + { + _ecdh.Dispose(); + } + } +#endif +} diff --git a/NetworkLibrary/Components/Crypto/DigitalSignature/PrivateKeySign.cs b/NetworkLibrary/Components/Crypto/DigitalSignature/PrivateKeySign.cs new file mode 100644 index 0000000..b52188c --- /dev/null +++ b/NetworkLibrary/Components/Crypto/DigitalSignature/PrivateKeySign.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace NetworkLibrary.Components.Crypto.DigitalSignature +{ + public class PrivateKeySign + { + HMACSHA256 sha256; + public PrivateKeySign(byte[] key) + { + sha256 = new HMACSHA256(key); + } + + public byte[] Sign(byte[] data) + { + return sha256.ComputeHash(data); + } + + public byte[] Sign(byte[] buffer, int offset, int count) + { + return sha256.ComputeHash(buffer, offset, count); + } + + } +} diff --git a/NetworkLibrary/Components/Crypto/DigitalSignature/PublicKeySign.cs b/NetworkLibrary/Components/Crypto/DigitalSignature/PublicKeySign.cs new file mode 100644 index 0000000..d417422 --- /dev/null +++ b/NetworkLibrary/Components/Crypto/DigitalSignature/PublicKeySign.cs @@ -0,0 +1,102 @@ +using NetworkLibrary.Utils; +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace NetworkLibrary.Components.Crypto.DigitalSignature +{ + internal class PublicKeySign + { + + private static readonly ECCurve curve = ECCurve.NamedCurves.nistP256; + + public static (byte[] privateKey, byte[] publicKey) GenerateKeys() + { + using (var ecdsa = ECDsa.Create(curve)) + { + var parameters = ecdsa.ExportParameters(true); + return ( + privateKey: parameters.D, + publicKey: CombineBytes(parameters.Q.X, parameters.Q.Y) + ); + } + } + + public static byte[] SignData(byte[] data,int offset,int count, byte[] privateKey) + { + using (var ecdsa = ECDsa.Create(curve)) + { + ecdsa.ImportParameters(new ECParameters + { + Curve = curve, + D = privateKey, + Q = DerivePublicKey(privateKey) + }); + return ecdsa.SignData(data,offset,count, HashAlgorithmName.SHA256); + } + } + + public static byte[] SignData(byte[] data, int offset, int count, ECParameters privateKeyParams) + { + using (var ecdsa = ECDsa.Create(curve)) + { + ecdsa.ImportParameters(privateKeyParams); + return ecdsa.SignData(data, offset, count, HashAlgorithmName.SHA256); + } + + } + + + public static bool VerifyData(byte[] data,int offset,int count, byte[] signature, byte[] publicKey) + { + using (var ecdsa = ECDsa.Create(curve)) + { + if (publicKey.Length != 64) + throw new ArgumentException("Public key must be 64 bytes (X+Y concatenated)"); + + ecdsa.ImportParameters(new ECParameters + { + Curve = curve, + Q = new ECPoint + { + X = ByteCopy.ToArray(publicKey, 0, 32),//publicKey[0..32], + Y = ByteCopy.ToArray(publicKey, 32, 32) //publicKey[32..64] + } + }); + return ecdsa.VerifyData(data,offset,count, signature, HashAlgorithmName.SHA256); + } + } + + public static bool VerifyData(byte[] data, int offset, int count, byte[] signature, ECParameters publicKeyParams) + { + using (var ecdsa = ECDsa.Create(curve)) + { + ecdsa.ImportParameters(publicKeyParams); + return ecdsa.VerifyData(data, offset, count, signature, HashAlgorithmName.SHA256); + } + } + + private static ECPoint DerivePublicKey(byte[] privateKey) + { + using (var ecdsa = ECDsa.Create(curve)) + { + ecdsa.ImportParameters(new ECParameters + { + Curve = curve, + D = privateKey + }); + return ecdsa.ExportParameters(false).Q; + } + } + + private static byte[] CombineBytes(byte[] first, byte[] second) + { + var result = new byte[first.Length + second.Length]; + Buffer.BlockCopy(first, 0, result, 0, first.Length); + Buffer.BlockCopy(second, 0, result, first.Length, second.Length); + return result; + } + + } +} diff --git a/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs b/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs new file mode 100644 index 0000000..59814ba --- /dev/null +++ b/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace NetworkLibrary.Components.Crypto.KeyDerivation +{ + public class HKDFLite + { + // HKDF implementation (RFC 5869) + public static byte[] DeriveKey(byte[] ikm, byte[] salt = null, byte[] info = null, int outputLength = 32) + { + if (salt == null) + { + salt = Encoding.UTF8.GetBytes("AES-GCM-Salt"); + } + + if (info == null) + { + info = Encoding.UTF8.GetBytes("AES-GCM-Info"); + } + + byte[] prk = HkdfExtract(salt, ikm); + return HkdfExpand(prk, info, outputLength); + } + + private static byte[] HkdfExtract(byte[] salt, byte[] ikm) + { + using (var hmac = new HMACSHA256(salt)) + { + return hmac.ComputeHash(ikm); + } + } + + private static byte[] HkdfExpand(byte[] prk, byte[] info, int outputLength) + { + using (var hmac = new HMACSHA256(prk)) + { + byte[] result = new byte[outputLength]; + byte[] t = new byte[0]; + byte counter = 1; + int offset = 0; + + while (offset < outputLength) + { + // Concatenate T(i-1) + info + counter + byte[] input = new byte[t.Length + info.Length + 1]; + Array.Copy(t, 0, input, 0, t.Length); + Array.Copy(info, 0, input, t.Length, info.Length); + input[t.Length + info.Length] = counter++; + + // Compute T(i) = HMAC-Hash(PRK, T(i-1) | info | i) + t = hmac.ComputeHash(input); + + // Copy to result + int toCopy = Math.Min(t.Length, outputLength - offset); + Array.Copy(t, 0, result, offset, toCopy); + offset += toCopy; + } + + return result; + } + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs new file mode 100644 index 0000000..df46ec6 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs @@ -0,0 +1,14 @@ +using NetworkLibrary.Components.Crypto; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Client +{ + public class ChannelInfo + { + public AesMode AesMode { get; } + + public string ChannelName { get; } + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs new file mode 100644 index 0000000..b8e55e6 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -0,0 +1,144 @@ +using MessageProtocol; +using System; +using System.Collections.Generic; +using System.Text; +using NetworkLibrary.MessageProtocol; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using System.Net.Sockets; +using NetworkLibrary.TCP.AES; +using NetworkLibrary.Components.Crypto; +using NetworkLibrary.Components; +using NetworkLibrary.TCP.ByteMessage; +using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.Components.Crypto.KeyDerivation; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Client.StateManagement; + +namespace NetworkLibrary.DistributedP2P.Client +{ + public class DistributedLobbyClient:IClientConnection where S : ISerializer, new() + { + IClientDbConnection clientDbConnector; + IClientAuthenticationProvider clientAuthProvider; + SecureMessageClient sslClient; + StateManager stateManager = new StateManager(); + public DistributedLobbyClient(IClientDbConnection clientDbConnector, X509Certificate2 certificate = null) + { + this.clientDbConnector = clientDbConnector; + sslClient = new SecureMessageClient(certificate); + } + + public async Task ConnectAsync(string ip, int port) + { + IClientAuthenticationToken authToken = clientAuthProvider.Authenticate(); + + bool res = await sslClient.ConnectAsync(ip, port); + if (res) + { + Guid conversationId = Guid.NewGuid(); + var conState = new ClientConnectionState(conversationId, this, clientDbConnector, authToken); + stateManager.RegisterState(conState); + + await conState.WaitCompletion(); + + if (conState.IsSuccesful) + { + return true; + } + + return false; + + } + else return false; + } + + public void SenAsyncMessage(MessageEnvelope message) + { + sslClient.SendAsyncMessage(message); + } + + public async Task SendMessageAndWaitResponse(MessageEnvelope message) + { + return await sslClient.SendMessageAndWaitResponse(message); + } + + public async Task OpenTcpChannel(Guid destinationPeer, ChannelInfo Info) + { + // so here somehow we will get a socet. + // its either through holepunch or through the server + + Socket socket = await OpenTcpPipeWithPeer(destinationPeer); + + // Socket is either a server or a client. + + + if (socket != null) + { + if (Info.AesMode != AesMode.None) + { + // we obtain here shared Key with peer; + byte[] sharedSecret = await PerformDHWithPeer(destinationPeer); + + + var alg = new ConcurrentAesAlgorithm(sharedSecret, Info.AesMode); + return new SecureTcpChannel(new AesTcpClient(socket, alg), Info); + + } + else + { + return new SecureTcpChannel(new AesTcpClient(socket, new ConcurrentAesAlgorithm(new byte[16], Info.AesMode)), Info); + } + + } + else + { + return null; + } + } + + private async Task PerformDHWithPeer(Guid destinationPeer) + { + // lets do this first + // so how the other party associates this with the channel. + // maybe we need channel creation state machine. + + DiffieHellman df = new DiffieHellman(); + byte[] publicKey = df.GetPublicKey(); + + MessageEnvelope envelope = new MessageEnvelope(); + envelope.Header = "DH"; + envelope.To = destinationPeer; + envelope.Payload = publicKey; + + + var response = await SendMessageAndWaitResponse(envelope); + if (response.Header != MessageEnvelope.RequestTimeout) + { + response.LockBytes(); + byte[] dstPublic = response.Payload; + + var secret = df.CalculateSharedSecret(dstPublic); + var symetricKey = HKDFLite.DeriveKey(secret, outputLength: 16); + return symetricKey; + } + return null; + } + + private Task OpenTcpPipeWithPeer(Guid destinationPeer) + { + // Shenanigans with server + // First try holepunch + // Then open a pipe with the server + + return null; + } + + public void Disconnect() + { + sslClient.Disconnect(); + } + + + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationProvider.cs b/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationProvider.cs new file mode 100644 index 0000000..2263acd --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationProvider.cs @@ -0,0 +1,7 @@ +namespace NetworkLibrary.DistributedP2P.Client +{ + internal interface IClientAuthenticationProvider + { + IClientAuthenticationToken Authenticate(); + } +} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationToken.cs b/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationToken.cs new file mode 100644 index 0000000..fa821dc --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationToken.cs @@ -0,0 +1,9 @@ +namespace NetworkLibrary.DistributedP2P.Client +{ + internal interface IClientAuthenticationToken + { + string Token { get; } + string AuthenticationMethod { get; } + string AdditionalData { get; } + } +} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Client/IClientConnection.cs b/NetworkLibrary/DistributedP2P/Client/IClientConnection.cs new file mode 100644 index 0000000..a52f092 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/IClientConnection.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Client +{ + internal interface IClientConnection + { + void SenAsyncMessage(MessageEnvelope message); + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/IClientDbConnection.cs b/NetworkLibrary/DistributedP2P/Client/IClientDbConnection.cs new file mode 100644 index 0000000..239c2a6 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/IClientDbConnection.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Client +{ + public interface IClientDbConnection + { + byte[] GetClientPublicData(); + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs b/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs new file mode 100644 index 0000000..35e2696 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs @@ -0,0 +1,14 @@ +using System; + +namespace NetworkLibrary.DistributedP2P.Client +{ + + public interface ITcpChannel + { + ChannelInfo Info { get; } + + event Action BytesReceived; + event Action Disconnected; + void SendAsync(byte[] buffer, int offset, int count); + } +} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs b/NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs new file mode 100644 index 0000000..0bd9d1b --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NetworkLibrary.TCP.AES; + +namespace NetworkLibrary.DistributedP2P.Client +{ + public class SecureTcpChannel : ITcpChannel + { + AesTcpClient client; + AesTcpServer server; + private Guid clientId; + bool clientMode = false; + + public ChannelInfo Info { get; private set; } + + public event Action BytesReceived; + public event Action Disconnected; + + public SecureTcpChannel(AesTcpClient client, ChannelInfo info) + { + this.client = client; + this.Info = info; + + clientMode = true; + client.OnBytesReceived += ClientBytesReceived; + client.OnDisconnected += ClientDisconnected; + } + + public SecureTcpChannel(AesTcpServer server, ChannelInfo info) + { + this.server = server; + this.Info = info; + + clientId = server.Sessions.First().Key; + server.OnBytesReceived += ServerBytesReceived; + server.OnClientDisconnected += ServerClientDisconnected; + } + + + private void ServerBytesReceived(Guid guid, byte[] bytes, int offset, int count) + { + ClientBytesReceived(bytes, offset, count); + } + + private void ClientBytesReceived(byte[] bytes, int offset, int count) + { + BytesReceived?.Invoke(bytes, offset, count); + } + + + public void SendAsync(byte[] buffer, int offset, int count) + { + if (clientMode) + client.SendAsync(buffer, offset, count); + else + server.SendBytesToClient(clientId, buffer, offset, count); + } + + + private void ServerClientDisconnected(Guid guid) + { + ClientDisconnected(); + } + + + private void ClientDisconnected() + { + Disconnected?.Invoke(); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs new file mode 100644 index 0000000..eced4e8 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs @@ -0,0 +1,85 @@ +using NetworkLibrary.DistributedP2P.Components; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Client.StateManagement +{ + //Signed Challenge Tokens + internal class ClientConnectionState : ConversationStateBase + { + private readonly IClientConnection connection; + private readonly IClientDbConnection clientDbConnector; + private readonly IClientAuthenticationToken authToken; + + + private TaskCompletionSource Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + public ClientConnectionState(Guid stateId, IClientConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken):base(stateId) + { + this.connection = connection; + this.clientDbConnector = clientDbConnector; + this.authToken = authToken; + } + + public override void HandleMessage(MessageEnvelope message) + { + switch (message.Header) + { + case InternalConstants.ConnectionStart: + Start(); + break; + case InternalConstants.ConnectionGetClientPublicData: + SendClientPublicData(message); + break; + case InternalConstants.ConnectionAckGood: + HandleConnectionSucces(message); + break; + case InternalConstants.Error: + HandleConnectionFail(message); + break; + } + } + + private void Start() + { + MessageEnvelope msg = CreateEnvelope(); + msg.Header = InternalConstants.ConnectionReq; + msg.KeyValuePairs = new Dictionary(); + + msg.KeyValuePairs.Add("AuthToken", authToken.Token); + msg.KeyValuePairs.Add("AuthMethod", authToken.AuthenticationMethod); + msg.KeyValuePairs.Add("AdditionalData", authToken.AdditionalData); + + connection.SenAsyncMessage(msg); + // now server will authenticate after this + // may ask additional data to link, if we are first timer + // then succes or fail + } + + private void SendClientPublicData(MessageEnvelope message) + { + MessageEnvelope response = CreateEnvelope(); + response.Header = InternalConstants.ConnectionAckClientPublicData; + response.Payload = clientDbConnector.GetClientPublicData(); + + connection.SenAsyncMessage(response); + } + + + private void HandleConnectionSucces(MessageEnvelope message) + { + Completed(succes: true); + } + + private void HandleConnectionFail(MessageEnvelope message) + { + if (message.KeyValuePairs.ContainsKey("Error")) + ErrorMessage = message.KeyValuePairs["Error"]; + + Completed(succes: false); + } + + + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs new file mode 100644 index 0000000..e65af32 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -0,0 +1,25 @@ +using NetworkLibrary.DistributedP2P.Components; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Client.StateManagement +{ + internal class ClientPipeState : ConversationStateBase + { + + + public ClientPipeState(Guid stateId):base(stateId) + { + + } + + public override void HandleMessage(MessageEnvelope message) + { + + } + + + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs new file mode 100644 index 0000000..cd13c2e --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal abstract class ConversationStateBase:IConversationState + { + public Guid StateId { get; private set; } + + public bool IsSuccesful { get; private set; } + + public event Action OnComplete; + + public string ErrorMessage { get; protected set; } + + protected readonly object cancellationMutex = new object(); + private TaskCompletionSource Completion; + private int isComplete = 0; + + public ConversationStateBase(Guid stateId) + { + Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + public abstract void HandleMessage(MessageEnvelope message); + + public Task WaitCompletion() + { + return Completion.Task; + } + + public void Cancel() + { + lock (cancellationMutex) + { + Completed(false); + } + + } + protected MessageEnvelope CreateErrorMsg(string err) + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.Error; + msg.KeyValuePairs = new Dictionary + { + { "Error", err } + }; + return msg; + } + protected MessageEnvelope CreateEnvelope() + { + MessageEnvelope msg = new MessageEnvelope(); + msg.MessageId = StateId; + msg.IsInternal = true; + return msg; + } + + protected bool IsCompleted() + { + return Interlocked.CompareExchange(ref isComplete, 0, 0) == 1; + } + + + protected void Completed(bool succes) + { + if (Interlocked.CompareExchange(ref isComplete, 1, 0) == 0) + { + + IsSuccesful = false; + OnComplete?.Invoke(this); + Completion.SetResult(this); + OnComplete = null; + } + } + + + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/IAuthenticator.cs b/NetworkLibrary/DistributedP2P/Components/IAuthenticator.cs deleted file mode 100644 index 2bf1b2b..0000000 --- a/NetworkLibrary/DistributedP2P/Components/IAuthenticator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using NetworkLibrary.MessageProtocol; -using System; -using System.Collections.Generic; -using System.Text; - -namespace NetworkLibrary.DistributedP2P.Components -{ - public interface IAuthenticationResult - { - bool Success { get;} - } - public interface IAuthenticator - { - IAuthenticationResult Authenticate(IClientConnection messageConnection, Guid guid); - } -} diff --git a/NetworkLibrary/DistributedP2P/Components/IClientConnection.cs b/NetworkLibrary/DistributedP2P/Components/IClientConnection.cs deleted file mode 100644 index ff755ee..0000000 --- a/NetworkLibrary/DistributedP2P/Components/IClientConnection.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace NetworkLibrary.DistributedP2P.Components -{ - // Send Receive messages. - // know about disconnect. - public interface IClientConnection - { - } -} diff --git a/NetworkLibrary/DistributedP2P/Components/IClientDbConnector.cs b/NetworkLibrary/DistributedP2P/Components/IClientDbConnector.cs deleted file mode 100644 index c0c819c..0000000 --- a/NetworkLibrary/DistributedP2P/Components/IClientDbConnector.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace NetworkLibrary.DistributedP2P.Components -{ - public interface IClientDbConnector - { - } -} diff --git a/NetworkLibrary/DistributedP2P/Components/IConversationState.cs b/NetworkLibrary/DistributedP2P/Components/IConversationState.cs new file mode 100644 index 0000000..28fe927 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/IConversationState.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Components +{ + public interface IConversationState + { + Guid StateId { get; } + bool IsSuccesful { get; } + + void HandleMessage(MessageEnvelope message); + void Cancel(); + Task WaitCompletion(); + + event Action OnComplete; + + + } +} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Components/ITimeProvider.cs b/NetworkLibrary/DistributedP2P/Components/ITimeProvider.cs new file mode 100644 index 0000000..852c311 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/ITimeProvider.cs @@ -0,0 +1,9 @@ +using System; + +namespace NetworkLibrary.DistributedP2P.Components +{ + public interface ITimeProvider + { + DateTime GetTime(); + } +} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs new file mode 100644 index 0000000..2ece266 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class InternalConstants + { + public const string ConnectionStart = "-"; + public const string ConnectionReq = "0"; + public const string ConnectionAckGood = "1"; + public const string Error = "2"; + public const string ConnectionGetClientPublicData = "3"; + public const string ConnectionAckClientPublicData = "4"; + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/MessageConnection.cs b/NetworkLibrary/DistributedP2P/Components/MessageConnection.cs deleted file mode 100644 index ed90d23..0000000 --- a/NetworkLibrary/DistributedP2P/Components/MessageConnection.cs +++ /dev/null @@ -1,20 +0,0 @@ -using MessageProtocol; -using System; -using System.Collections.Generic; -using System.Text; -using NetworkLibrary.MessageProtocol; - - -namespace NetworkLibrary.DistributedP2P.Components -{ - internal class MessageConnection: IClientConnection where S : ISerializer, new() - { - SecureMessageServer server; - public MessageConnection(SecureMessageServer serverConnection ) - { - this.server = serverConnection; - } - - - } -} diff --git a/NetworkLibrary/DistributedP2P/Components/SessionManager.cs b/NetworkLibrary/DistributedP2P/Components/SessionManager.cs deleted file mode 100644 index 865bf95..0000000 --- a/NetworkLibrary/DistributedP2P/Components/SessionManager.cs +++ /dev/null @@ -1,58 +0,0 @@ -using NetworkLibrary.MessageProtocol; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Net; -using System.Text; - -namespace NetworkLibrary.DistributedP2P.Components -{ - // Creation, and managing destruction of sessions. - //Created after Authentication. - // Routing messages between sessions. - // Where do we put rooms? probably here - internal class SessionManager where S : ISerializer, new() - { - internal ConcurrentDictionary serverSessions = new ConcurrentDictionary(); - - internal void HandleMessage(Guid guid, MessageEnvelope envelope) - { - - } - - public void CreateSession(IClientConnection messageConnection1, IAuthenticationResult result, Guid guid, IPEndPoint sessionEp) - { - serverSessions.TryAdd(guid, new ServerSession()); - } - - internal void RouteP2PMessageTcp(Guid guid, byte[] bytes, int offset, int count) - { - - } - - internal void RouteP2PMessageUdp(IPEndPoint adress, byte[] bytes, int offset, int count) - { - - } - - internal void HandleDisconnect(Guid guid) - { - - } - - internal void HandleTcpPipeDisconnect(Guid guid) - { - - } - - internal void TcpPipeCreated(Guid guid) - { - - } - - internal void CreateSession(MessageConnection messageConnection, Guid guid, IPEndPoint sessionEp) where S : ISerializer, new() - { - throw new NotImplementedException(); - } - } -} diff --git a/NetworkLibrary/DistributedP2P/Components/StateManager.cs b/NetworkLibrary/DistributedP2P/Components/StateManager.cs new file mode 100644 index 0000000..5a5be25 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/StateManager.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Concurrent; +using static NetworkLibrary.P2P.Components.PingData; + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class StateManager + { + ConcurrentDictionary states = new ConcurrentDictionary(); + public void RegisterState(IConversationState state) + { + state.OnComplete += HandleComplete; + states.TryAdd(state.StateId, state); + + } + + private void HandleComplete(IConversationState state) + { + UnregisterState(state.StateId); + state.OnComplete -= HandleComplete; + } + + public void UnregisterState(Guid stateId) + { + states.TryRemove(stateId, out _); + } + + public bool HandleMessage(MessageEnvelope message) + { + Guid stateId = message.MessageId; + + if (stateId == Guid.Empty) + { + return false; + } + + if (states.TryGetValue(stateId, out var state)) + { + state.HandleMessage(message); + return true; + } + + return false; + } + + + } +} diff --git a/NetworkLibrary/DistributedP2P/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs similarity index 52% rename from NetworkLibrary/DistributedP2P/DistributedLobbyServer.cs rename to NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index e254ea1..d4c9511 100644 --- a/NetworkLibrary/DistributedP2P/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -11,14 +11,20 @@ using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Data.Common; +using NetworkLibrary.P2P; +using System.Diagnostics; -namespace NetworkLibrary.DistributedP2P +using System.Threading.Tasks; +using NetworkLibrary.DistributedP2P.Server.StateManagement; + +namespace NetworkLibrary.DistributedP2P.Server { public class Dependencies { public IAuthenticator Authenticator; - public IClientDbConnector DbConnector; + public IServerDbConnector DbConnector; } public class ServerParameters { @@ -27,7 +33,7 @@ public class ServerParameters public int TcpPort; public int UdpPort; } - public class DistributedLobbyServerBase where S : ISerializer, new() + public class DistributedLobbyServerBase : IDistributedConnection where S : ISerializer, new() { public readonly int SSlPort; public readonly int TcpPort; @@ -40,13 +46,18 @@ public class ServerParameters AsyncUdpServer udpServer; IAuthenticator authenticator; - IClientDbConnector dbConnector; + IServerDbConnector dbConnector; SessionManager sessionManager; + Components.StateManager stateManager = new Components.StateManager(); + PipeAssociator piper; + Stopwatch serverClock = new Stopwatch(); + + private byte[] serverKey = new byte[16]; public DistributedLobbyServerBase(Dependencies dependencies, ServerParameters parameters) { - this.authenticator = dependencies.Authenticator; - this.dbConnector = dependencies.DbConnector; + authenticator = dependencies.Authenticator; + dbConnector = dependencies.DbConnector; SSlPort = parameters.SSlPort; TcpPort = parameters.TcpPort; UdpPort = parameters.UdpPort; @@ -57,6 +68,8 @@ public DistributedLobbyServerBase(Dependencies dependencies, ServerParameters pa public void StartServer() { + serverClock.Start(); + sslServer = new SecureMessageServer(SSlPort, serverCertificate); tcpServer = new AsyncTcpServer(TcpPort); udpServer = new AsyncUdpServer(UdpPort); @@ -66,62 +79,53 @@ public void StartServer() sslServer.OnClientDisconnected += SslClientDisconnected; tcpServer.OnClientAccepting += ValidateTcpConnection; - tcpServer.OnClientAccepted += TcpClientAccepted; - tcpServer.OnClientDisconnected += TcpclientDisconnected; - sslServer.OnMessageReceived += SslMessageReceived; - tcpServer.OnBytesReceived += TcpBytesReceived; - udpServer.OnBytesRecieved += UdpBytesReceived; udpServer.StartServer(); tcpServer.StartServer(); sslServer.StartServer(); - } - + sessionManager = new SessionManager(this, tcpServer, udpServer, serverKey); - private bool ValidateTcpConnection(Socket acceptedSocket) - { - return true; } - private void TcpClientAccepted(Guid guid) - { - sessionManager.TcpPipeCreated(guid); - } - private void TcpclientDisconnected(Guid guid) + private bool ValidateSslConnection(Socket acceptedSocket) { - sessionManager.HandleTcpPipeDisconnect(guid); + // we will do Ddos protection here; + return true; } - private void SslClientDisconnected(Guid guid) + private bool ValidateTcpConnection(Socket acceptedSocket) { - sessionManager.HandleDisconnect(guid); + return true; } - private bool ValidateSslConnection(Socket acceptedSocket) + private void SslClientAccepted(Guid ephemeralClientId) { - // we will do Ddos protection here; - return true; + Guid stateId = Guid.NewGuid(); + var state = new ServerConnectionState(stateId, ephemeralClientId, this, authenticator, dbConnector); + stateManager.RegisterState(state); + + state.Start(); + + state.OnComplete += ConnectionStateComplete; } - - private void SslClientAccepted(Guid guid) + private void ConnectionStateComplete(IConversationState state_) { - // this must be async, later. - MessageConnection messageConnection = new MessageConnection(sslServer); - IAuthenticationResult result = authenticator.Authenticate(messageConnection, guid); - - var sessionEp = sslServer.GetSessionEndpoint(guid); - sessionManager.CreateSession(messageConnection, result, guid, sessionEp); + var state = (ServerConnectionState)state_; + if (state.IsSuccesful) + { + var sessionEp = sslServer.GetSessionEndpoint(state.EphemeralClientId); + sessionManager.CreateSession(state.clientDbInfo, state.EphemeralClientId, sessionEp); + } } - #region Receive // so here message can be p2p message, clients business. // internal messages private void SslMessageReceived(Guid guid, MessageEnvelope envelope) { - if (envelope.IsInternal) + if (envelope.IsInternal) { HandleInternalMessage(guid, envelope); } @@ -131,27 +135,56 @@ private void SslMessageReceived(Guid guid, MessageEnvelope envelope) } } - private void HandleInternalMessage(Guid guid, MessageEnvelope envelope) + private void HandleInternalMessage(Guid clientId, MessageEnvelope message) { //server messages. + // time sync + // holepunching + // ping + + if (sessionManager.HandleMessage(clientId, message)) + return; + + switch (message.Header) + { + case Constants.TimeSync: + + byte[] time = new byte[8]; + message.Payload = time; + PrimitiveEncoder.WriteFixedDouble(time, 0, serverClock.Elapsed.TotalMilliseconds); + message.TimeStamp = DateTime.UtcNow; + SendAsyncMessage(clientId, message); + + break; + } } - private void TcpBytesReceived(Guid guid, byte[] bytes, int offset, int count) + public void SendAsyncMessage(Guid clientId, MessageEnvelope message) { - sessionManager.RouteP2PMessageTcp(guid, bytes, offset, count); + sslServer.SendAsyncMessage(clientId, message); } - private void UdpBytesReceived(IPEndPoint adress, byte[] bytes, int offset, int count) + public Task SendMessageAndWaitResponse(Guid destination, MessageEnvelope envelope) { - sessionManager.RouteP2PMessageUdp(adress, bytes, offset, count); + return sslServer.SendMessageAndWaitResponse(destination, envelope); } - #endregion - public void ShutDownServer() + private void SslClientDisconnected(Guid guid) + { + sessionManager.DestroySession(guid); + } + + public void ShutDownServer() { sslServer.ShutdownServer(); tcpServer.ShutdownServer(); udpServer.Dispose(); } + + public DateTime GetTime() + { + // will be distributed time + return DateTime.UtcNow; + } } } diff --git a/NetworkLibrary/DistributedP2P/Server/IAuthenticator.cs b/NetworkLibrary/DistributedP2P/Server/IAuthenticator.cs new file mode 100644 index 0000000..640a507 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/IAuthenticator.cs @@ -0,0 +1,20 @@ +using NetworkLibrary.MessageProtocol; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Server +{ + public interface IAuthenticationResult + { + bool IsValid { get; } + string UserId { get; } + string Error { get; } + IReadOnlyDictionary Claims { get; } + } + public interface IAuthenticator + { + Task Authenticate(string AuthenticationToken, string AuthenticationMethod, string Cookies); + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs b/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs new file mode 100644 index 0000000..b470294 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using NetworkLibrary.DistributedP2P.Components; + +namespace NetworkLibrary.DistributedP2P.Server +{ + internal interface IDistributedConnection : ITimeProvider + { + void SendAsyncMessage(Guid a, MessageEnvelope msgs); + Task SendMessageAndWaitResponse(Guid a, MessageEnvelope msg); + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/IServerDbConnector.cs b/NetworkLibrary/DistributedP2P/Server/IServerDbConnector.cs new file mode 100644 index 0000000..d8cccca --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/IServerDbConnector.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Server +{ + public interface IClientDbInfo + { + bool IsValid { get; } + string Error { get; } + Guid ClientId { get; } + } + public interface IServerDbConnector + { + Task GetClientData(IAuthenticationResult result); + Task RegisterClient(IAuthenticationResult tokenResult, byte[] payload); + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/PipeAssociator.cs b/NetworkLibrary/DistributedP2P/Server/PipeAssociator.cs new file mode 100644 index 0000000..2b00ad9 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/PipeAssociator.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Text; +using NetworkLibrary.Components; +using NetworkLibrary.Components.Crypto.DigitalSignature; +using NetworkLibrary.MessageProtocol; +using NetworkLibrary.TCP.Base; +using NetworkLibrary.UDP; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.Utils; +using System.IO; +using NetworkLibrary.DistributedP2P.Components; +namespace NetworkLibrary.DistributedP2P.Server +{ + internal class PipeAssociator + { + enum PipeFlag : byte + { + PipeAssociation = 1, + RoomPipeAssociation = 2, + PipeRoute = 3, + RoomRoute = 4 + } + internal AsyncTcpServer TcpServer; + internal AsyncUdpServer UdpServer; + + internal ConcurrentDictionary pipeMapTcp = new ConcurrentDictionary(); + internal ConcurrentDictionary pipeMapUdp = new ConcurrentDictionary(); + + ConcurrentDictionary> activeTcpPipeStates = new ConcurrentDictionary>(); + ConcurrentDictionary> activeUdpPipeStates = new ConcurrentDictionary>(); + + byte[] cryptoKey; + PrivateKeySign signer; + private ITimeProvider timeProvider; + + public PipeAssociator(AsyncTcpServer tcpServer, AsyncUdpServer udpServer, byte[] pipeKey, ITimeProvider timeProvider) + { + TcpServer = tcpServer; + UdpServer = udpServer; + this.timeProvider = timeProvider; + + TcpServer.OnBytesReceived += HandleTcpBytes; + UdpServer.OnBytesRecieved += HandleUdpBytes; + signer = new PrivateKeySign(pipeKey); + + } + + + private void HandleTcpBytes(Guid guid, byte[] bytes, int offset, int count) + { + if (!RouteTcp(guid, bytes, offset, count)) + { + ManagePipeToken(guid, bytes, offset, count); + } + } + + private void HandleUdpBytes(IPEndPoint endpoint, byte[] bytes, int offset, int count) + { + if (!RouteUdp(endpoint, bytes, offset, count)) + { + ManagePipeToken(endpoint, bytes, offset, count); + } + } + + + private bool RouteTcp(Guid guid, byte[] bytes, int offset, int count) + { + // we should look for room stuff here + if (pipeMapTcp.TryGetValue(guid, out var to)) + { + TcpServer.SendBytesToClient(to, bytes, offset, count); + return true; + } + return false; + } + + private bool RouteUdp(IPEndPoint endpoint, byte[] bytes, int offset, int count) + { + if (pipeMapUdp.TryGetValue(endpoint, out var to)) + { + UdpServer.SendBytesToClient(to, bytes, offset, count); + return true; + } + return false; + } + + //tcp + private void ManagePipeToken(Guid guid, byte[] bytes, int offset, int count) + { + // state object etc + var token = ExtractPipeToken(bytes, offset, count); + if (token != null) + { + activeTcpPipeStates.TryGetValue(token.Token, out var state); + state.RegisterClient(guid); + if (state.IsComplete()) + { + TcpPipeCreated(state.Clients[0], state.Clients[1]); + } + } + else + { + TcpServer.CloseSession(guid); + } + } + + //udp + private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int count) + { + var token = ExtractPipeToken(bytes, offset, count); + if (token != null) + { + activeUdpPipeStates.TryGetValue(token.Token, out var state); + state.RegisterClient(clientEp); + if (state.IsComplete()) + { + UdpPipeCreated(state.Clients[0], state.Clients[1]); + } + } + else + { + UdpServer.RemoveClient(clientEp); + } + } + + private PipeToken ExtractPipeToken(byte[] bytes, int offset, int count) + { + if (count < 24 + 32) + throw new InvalidDataException(); + + int offPrev = offset; + var token = PrimitiveEncoder.ReadGuid(bytes, ref offset);//16 + var expiration = DateTime.FromBinary(PrimitiveEncoder.ReadFixedInt64(bytes, ref offset));//8 + + var calculatedSignature = signer.Sign(bytes, offPrev, 24); + + if (SignatureMatch(calculatedSignature, bytes, offset)) + { + return new PipeToken() { Token = token, Expiration = expiration }; + } + else + { + return null; + } + } + + private bool SignatureMatch(byte[] localSignature, byte[] bytes, int offset) + { + for (int i = 0; i < localSignature.Length; i++)//32 + { + if (localSignature[i] != bytes[offset + i]) + return false; + } + return true; + } + + internal void TcpPipeCreated(Guid from, Guid to) + { + pipeMapTcp.TryAdd(from, to); + pipeMapTcp.TryAdd(to, from); + } + + internal void HandleTcpPipeDisconnect(Guid from) + { + if (pipeMapTcp.TryRemove(from, out Guid to)) + { + pipeMapTcp.TryRemove(to, out _); + } + } + + internal void UdpPipeCreated(IPEndPoint from, IPEndPoint to) + { + pipeMapUdp.TryAdd(from, to); + pipeMapUdp.TryAdd(to, from); + } + + internal void HandleUdpPipeDisconnect(IPEndPoint from) + { + if (pipeMapUdp.TryRemove(from, out var to)) + { + pipeMapUdp.TryRemove(to, out _); + } + } + + internal void GetPipeData(PooledMemoryStream stream, bool tcp) + { + int originalPos = stream.Position32; + PipeToken pipeData = new PipeToken(); + + pipeData.Token = Guid.NewGuid(); + pipeData.Expiration = timeProvider.GetTime().AddSeconds(20); + + PrimitiveEncoder.WriteGuid(stream, pipeData.Token);//16 + long Time = pipeData.Expiration.ToBinary(); + PrimitiveEncoder.WriteFixedInt64(stream, Time); //8 + + byte[] signature = signer.Sign(stream.GetBuffer(), originalPos, 24); + stream.Write(signature, 0, signature.Length); + + if (tcp) + activeTcpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); + else + activeUdpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/PipeState.cs b/NetworkLibrary/DistributedP2P/Server/PipeState.cs new file mode 100644 index 0000000..2d3713e --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/PipeState.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Server +{ + internal class PipeState + { + + internal List Clients = new List(); + internal PipeToken pipeData; + private object mtex = new object(); + + public PipeState(PipeToken pipeData) + { + this.pipeData = pipeData; + } + + internal bool IsComplete() + { + lock (mtex) + return Clients.Count > 1; + } + + internal void RegisterClient(T guid) + { + lock (mtex) + Clients.Add(guid); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/ServerSession.cs b/NetworkLibrary/DistributedP2P/Server/ServerSession.cs similarity index 60% rename from NetworkLibrary/DistributedP2P/ServerSession.cs rename to NetworkLibrary/DistributedP2P/Server/ServerSession.cs index 160f0c6..35a1a44 100644 --- a/NetworkLibrary/DistributedP2P/ServerSession.cs +++ b/NetworkLibrary/DistributedP2P/Server/ServerSession.cs @@ -1,11 +1,10 @@ -using NetworkLibrary.DistributedP2P.Components; -using System; +using System; using System.Collections.Generic; using System.Text; -namespace NetworkLibrary.DistributedP2P +namespace NetworkLibrary.DistributedP2P.Server { - enum Sessionstate + enum Sessionstate { Uninitialized, Authenticating, @@ -15,13 +14,16 @@ enum Sessionstate } // should hold the data about the client. Keys etc everything. - + internal class ServerSession { public Sessionstate state; - - public ServerSession() + public IClientDbInfo ClientInfo { get; } + public ServerSession(IClientDbInfo clientInfo) { + ClientInfo = clientInfo; } + + } } diff --git a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs new file mode 100644 index 0000000..81179fb --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs @@ -0,0 +1,128 @@ +using NetworkLibrary.Components; +using NetworkLibrary.Components.Crypto.DigitalSignature; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.MessageProtocol; +using NetworkLibrary.MessageProtocol.Serialization; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.TCP.Base; +using NetworkLibrary.UDP; +using NetworkLibrary.Utils; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Server +{ + class Flags + { + public const byte Route = 1; + } + + + class PipeToken + { + public Guid Token; + public DateTime Expiration; + } + + // Creation, and managing destruction of sessions. + //Created after Authentication. + // Routing messages between sessions. + // Where do we put rooms? probably here + internal class SessionManager where S : ISerializer, new() + { + internal ConcurrentDictionary serverSessions = new ConcurrentDictionary(); + internal ConcurrentDictionary endPointMap = new ConcurrentDictionary(); + internal ConcurrentDictionary pipeMapTcp = new ConcurrentDictionary(); + internal ConcurrentDictionary pipeMapUdp = new ConcurrentDictionary(); + + PipeAssociator piper; + RandomNumberGenerator random; + + IDistributedConnection serverConnection; + + public SessionManager(IDistributedConnection serverConnection, AsyncTcpServer tcpServer, AsyncUdpServer udpServer, byte[] pipeKey) + { + random = RandomNumberGenerator.Create(); + piper = new PipeAssociator(tcpServer, udpServer, pipeKey, serverConnection); + this.serverConnection = serverConnection; + } + + + internal bool HandleMessage(Guid from, MessageEnvelope envelope) + { + switch (envelope.Header) + { + case "Pipe": + ManagePiping(from, envelope.To, envelope); + break; + } + return false; + } + + private async void ManagePiping(Guid A, Guid B, MessageEnvelope msg) + { + + PooledMemoryStream tokenData = await FindOptimumTcpServer(); + + MessageEnvelope envelope = new MessageEnvelope(); + envelope.Header = "PipeToken"; + envelope.MessageId = msg.MessageId; + envelope.SetPayload(tokenData.GetBuffer(), 0, tokenData.Position32); + + Task ackA = serverConnection.SendMessageAndWaitResponse(A, envelope); + Task ackB = serverConnection.SendMessageAndWaitResponse(B, envelope); + + await Task.WhenAll(ackA, ackB); + + MessageEnvelope finalAck = new MessageEnvelope(); + finalAck.MessageId = msg.MessageId; + finalAck.Header = "Fail"; + + if (isGood(ackA.Result) && isGood(ackB.Result)) + { + finalAck.Header = "Success"; + serverConnection.SendAsyncMessage(A, finalAck); + serverConnection.SendAsyncMessage(B, finalAck); + } + else + { + serverConnection.SendAsyncMessage(A, finalAck); + serverConnection.SendAsyncMessage(B, finalAck); + } + + SharerdMemoryStreamPool.ReturnStreamStatic(tokenData); + } + + private bool isGood(MessageEnvelope ackA) + { + if (ackA.Header != MessageEnvelope.RequestTimeout) + return true; + return false; + } + + private Task FindOptimumTcpServer() + { + //do only local for now + PooledMemoryStream stream = SharerdMemoryStreamPool.RentStreamStatic(); + piper.GetPipeData(stream, tcp: true); + return Task.FromResult(stream); + } + + public void CreateSession(IClientDbInfo clientInfo, Guid ephemeralClientId, IPEndPoint sessionEp) + { + serverSessions.TryAdd(ephemeralClientId, new ServerSession(clientInfo)); + //Send a feedback + } + + internal void DestroySession(Guid guid) + { + + } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs new file mode 100644 index 0000000..4669b45 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs @@ -0,0 +1,169 @@ +using NetworkLibrary.DistributedP2P.Components; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Server.StateManagement +{ + internal class ServerConnectionState : ConversationStateBase + { + public IClientDbInfo clientDbInfo { get; private set; } + + public readonly Guid EphemeralClientId; + + private readonly IDistributedConnection connection; + private readonly IAuthenticator authenticator; + private readonly IServerDbConnector dbConnector; + private IAuthenticationResult tokenResult; + + public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection connection, IAuthenticator authenticator, IServerDbConnector dbConnector):base(stateId) + { + this.EphemeralClientId = clientId; + this.connection = connection; + this.authenticator = authenticator; + this.dbConnector = dbConnector; + } + + internal void Start() + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.ConnectionStart; + connection.SendAsyncMessage(EphemeralClientId, msg); + } + + public override void HandleMessage(MessageEnvelope message) + { + if (IsCompleted()) + return; + switch (message.Header) + { + case InternalConstants.ConnectionReq: + HandleInitialConnectionRequest(message); + break; + + case InternalConstants.ConnectionAckClientPublicData: + HandleClientPublicData(message); + break; + } + } + + + + private void HandleInitialConnectionRequest(MessageEnvelope message) + { + message.KeyValuePairs.TryGetValue("AuthToken", out string token); + message.KeyValuePairs.TryGetValue("AuthMethod", out string method); + message.KeyValuePairs.TryGetValue("AdditionalData", out string additionalData); + + if (token == null) + { + ReplyError("No token provided"); + return; + } + + if (method == null) + { + ReplyError("No authentication method provided"); + return; + } + + authenticator.Authenticate(token, method, additionalData).ContinueWith(HandleAuthentication); + } + + private void HandleAuthentication(Task task) + { + if (IsCompleted()) + return; + + IAuthenticationResult result = task.Result; + + if (result.IsValid) + { + FindClientDBLink(result); + } + else + { + ReplyError(result.Error); + } + } + + private void FindClientDBLink(IAuthenticationResult tokenResult) + { + dbConnector.GetClientData(tokenResult).ContinueWith(t => HandleClientData(t.Result, tokenResult)); + } + + private void HandleClientData(IClientDbInfo dbResult, IAuthenticationResult tokenResult) + { + if (IsCompleted()) + return; + + if (dbResult.IsValid) + { + clientDbInfo = dbResult; + ReplyGood(); + } + else + { + this.tokenResult = tokenResult; + RegisterClient(); + } + } + + private void RegisterClient() + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.ConnectionGetClientPublicData; + connection.SendAsyncMessage(EphemeralClientId, msg); + } + + private void HandleClientPublicData(MessageEnvelope message) + { + message.LockBytes(); + dbConnector.RegisterClient(tokenResult, message.Payload).ContinueWith(HandleDbRegistration); + } + + private void HandleDbRegistration(Task task) + { + if (IsCompleted()) + return; + + IClientDbInfo dbResult = task.Result; + if (dbResult.IsValid) + { + clientDbInfo = dbResult; + ReplyGood(); + } + else + { + ReplyError(dbResult.Error); + } + } + + private void ReplyGood() + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.ConnectionAckGood; + + lock (cancellationMutex) + { + if (IsCompleted()) + return; + + connection.SendAsyncMessage(EphemeralClientId, msg); + Completed(true); + } + + } + + private void ReplyError(string err) + { + connection.SendAsyncMessage(EphemeralClientId, CreateErrorMsg(err)); + Completed(false); + } + + + + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs new file mode 100644 index 0000000..154c938 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs @@ -0,0 +1,20 @@ +using NetworkLibrary.DistributedP2P.Components; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Server.StateManagement +{ + internal class ServerPipeState : ConversationStateBase + { + public ServerPipeState(Guid stateId) : base(stateId) + { + } + + public override void HandleMessage(MessageEnvelope message) + { + throw new NotImplementedException(); + } + } +} diff --git a/NetworkLibrary/NetworkLibrary.csproj b/NetworkLibrary/NetworkLibrary.csproj index 2d64ece..011022d 100644 --- a/NetworkLibrary/NetworkLibrary.csproj +++ b/NetworkLibrary/NetworkLibrary.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0 + netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0 true Standard.Network.Library True diff --git a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs index 8d91d93..d07a53f 100644 --- a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs +++ b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs @@ -1,4 +1,5 @@ using NetworkLibrary.Components; +using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Generic; using NetworkLibrary.Utils; using System; @@ -8,6 +9,39 @@ namespace NetworkLibrary.P2P.Components.HolePunch { public class KnownTypeSerializer { + + + #region PipeData + + //internal static void SerializeSignedPipeData(PooledMemoryStream stream, SignedPipeData signed) + //{ + // SerializePipeData(stream, signed.PipeData); + // stream.Write(signed.Signature, 0, signed.Signature.Length); + //} + + + + internal static void SerializePipeData(PooledMemoryStream stream, PipeToken pipeData) + { + SerializeEndpointData(stream, pipeData.serverEndpoint); + PrimitiveEncoder.WriteGuid(stream, pipeData.Token); + PrimitiveEncoder.WriteDatetime(stream, pipeData.Expiration); + } + + internal static PipeToken DeserializePipeData(byte[] buffer,ref int offset) + { + PipeToken pipeData = new PipeToken(); + + pipeData.serverEndpoint = DeserializeEndpointData(buffer, ref offset); + pipeData.Token = PrimitiveEncoder.ReadGuid(buffer, ref offset); + pipeData.Expiration = PrimitiveEncoder.ReadDatetime(buffer, ref offset); + + return pipeData; + } + + + #endregion + #region Endpoint Data public static void SerializeEndpointData(PooledMemoryStream stream, EndpointData data) { From e6e122013caf688267d968bd3d41e5f54949a147 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Fri, 28 Mar 2025 13:46:05 +0100 Subject: [PATCH 09/27] pipe establishment on distributed p2p --- .../Client/DistributedLobbyClient.cs | 70 ++-- .../Client/StateManagement/ClientPipeState.cs | 191 ++++++++- .../Components/InternalConstants.cs | 15 +- .../DistributedP2P/Components/TimerService.cs | 33 ++ .../Server/DistributedLobbyServer.cs | 27 +- .../DistributedP2P/Server/PipeAssociator.cs | 209 ---------- .../DistributedP2P/Server/PipeManager.cs | 370 ++++++++++++++++++ .../DistributedP2P/Server/PipeState.cs | 7 +- .../DistributedP2P/Server/SessionManager.cs | 68 +--- .../Server/StateManagement/ServerPipeState.cs | 116 +++++- .../HolePunch/KnownTypeSerializer.cs | 46 ++- NetworkLibrary/TCP/Base/TcpSession.cs | 47 +-- 12 files changed, 815 insertions(+), 384 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Components/TimerService.cs delete mode 100644 NetworkLibrary/DistributedP2P/Server/PipeAssociator.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/PipeManager.cs diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index b8e55e6..76e1d53 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -14,6 +14,7 @@ using NetworkLibrary.Components.Crypto.KeyDerivation; using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Client.StateManagement; +using NetworkLibrary.TCP.SSL.Base; namespace NetworkLibrary.DistributedP2P.Client { @@ -27,8 +28,11 @@ public DistributedLobbyClient(IClientDbConnection clientDbConnector, X509Certifi { this.clientDbConnector = clientDbConnector; sslClient = new SecureMessageClient(certificate); + sslClient.OnMessageReceived += HandleServerMsg; } + + public async Task ConnectAsync(string ip, int port) { IClientAuthenticationToken authToken = clientAuthProvider.Authenticate(); @@ -65,36 +69,22 @@ public async Task SendMessageAndWaitResponse(MessageEnvelope me public async Task OpenTcpChannel(Guid destinationPeer, ChannelInfo Info) { - // so here somehow we will get a socet. - // its either through holepunch or through the server - - Socket socket = await OpenTcpPipeWithPeer(destinationPeer); - - // Socket is either a server or a client. + var pipeState = new ClientPipeState(Guid.NewGuid(), this); + stateManager.RegisterState(pipeState); + pipeState.Start(destinationPeer); + await pipeState.WaitCompletion(); - if (socket != null) + if (pipeState.IsSuccesful) { - if (Info.AesMode != AesMode.None) + var symetricKey = await PerformDHWithPeer(destinationPeer); + if (symetricKey != null) { - // we obtain here shared Key with peer; - byte[] sharedSecret = await PerformDHWithPeer(destinationPeer); - - - var alg = new ConcurrentAesAlgorithm(sharedSecret, Info.AesMode); - return new SecureTcpChannel(new AesTcpClient(socket, alg), Info); - + var channel = new SecureTcpChannel(Info, pipeState.ConnectedSocket, symetricKey); + return channel; } - else - { - return new SecureTcpChannel(new AesTcpClient(socket, new ConcurrentAesAlgorithm(new byte[16], Info.AesMode)), Info); - } - - } - else - { - return null; } + return null; } private async Task PerformDHWithPeer(Guid destinationPeer) @@ -125,13 +115,35 @@ private async Task PerformDHWithPeer(Guid destinationPeer) return null; } - private Task OpenTcpPipeWithPeer(Guid destinationPeer) + + + private void HandleServerMsg(MessageEnvelope envelope) { - // Shenanigans with server - // First try holepunch - // Then open a pipe with the server + if (envelope.IsInternal) + { + if (stateManager.HandleMessage(envelope)) + return; - return null; + switch (envelope.Header) + { + case InternalConstants.PipeTokenDelivery: + + var pipeState = new ClientPipeState(envelope.MessageId, this); + pipeState.OnComplete += HandlePipeCreated; + stateManager.RegisterState(pipeState); + pipeState.HandleMessage(envelope); + break; + + } + } + } + + private void HandlePipeCreated(IConversationState state) + { + if(state.IsSuccesful) + { + // notify that a connection is opened, like socket accept + } } public void Disconnect() diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index e65af32..5b7142f 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -1,6 +1,9 @@ using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.P2P.Components.HolePunch; using System; using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; using System.Text; using System.Threading.Tasks; @@ -8,18 +11,200 @@ namespace NetworkLibrary.DistributedP2P.Client.StateManagement { internal class ClientPipeState : ConversationStateBase { + private readonly IClientConnection connection; + private Guid destinationPeer; + public Socket ConnectedSocket { get; private set; } + public EndpointData SuccesfullEndpoint { get; private set; } - public ClientPipeState(Guid stateId):base(stateId) + public ClientPipeState(Guid stateId, IClientConnection connection) : base(stateId) { - + this.connection = connection; + } + + public void Start(Guid destinationPeer) + { + this.destinationPeer = destinationPeer; + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PipeRequest; + msg.To = destinationPeer; + + connection.SenAsyncMessage(msg); } public override void HandleMessage(MessageEnvelope message) { + switch(message.Header) + { + case InternalConstants.PipeTokenDelivery: + HandlePipeToken(message); + break; + + case InternalConstants.ConnectionAckGood: + HandleGoodAck(message); + break; + + case InternalConstants.ConnectionAckBad: + HandleBadAck(message); + break; + } + } + + private async void HandlePipeToken(MessageEnvelope message) + { + int off = message.PayloadOffset; + var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); + + foreach (EndpointData endpoint in pipeData.PipeEndpoints) + { + Socket connected = await TryConnectWithTimeout(endpoint); + if(connected != null) + { + bool success = await TokenExchange(connected,pipeData.Token); + if(success) + { + OnConnectionSuccessful(endpoint,connected); + return; + } + else + { + try { connected.Close(); connected.Dispose(); } catch { } + } + } + // connect send token + //wait a data to come + //then send ack + } + + OnConnectionFail(); + return; + } + + + + private async Task TryConnectWithTimeout(EndpointData endpoint, int timeout = 500) + { + var clientSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); + var connectTask = ConnectAsync(clientSocket, endpoint.ToIpEndpoint()); + var timeoutTask = Task.Delay(timeout); + + var completedTask = await Task.WhenAny(connectTask, timeoutTask); + + if (completedTask == timeoutTask) + { + try { clientSocket.Close(); clientSocket.Dispose(); } catch { } + return null; + } + + bool res = connectTask.Result; + if(res) + { + return clientSocket; + } + else + { + try { clientSocket.Close(); clientSocket.Dispose(); } catch { } + return null; + } + + } + + private async Task ConnectAsync(Socket socket, IPEndPoint endPoint) + { + var tcs = new TaskCompletionSource(); + + var sa = new SocketAsyncEventArgs(); + sa.RemoteEndPoint = endPoint; + sa.Completed += (s, e) => + { + if (e.SocketError == SocketError.Success) + { + tcs.TrySetResult(true); + } + else + { + tcs.TrySetResult(false); + } + sa.Dispose(); + }; + + if (!socket.ConnectAsync(sa)) + { + return sa.SocketError == SocketError.Success; + } + + return await tcs.Task; + } + + private async Task TokenExchange(Socket connectedSocket, byte[] token, int timeoutMs = 500) + { + try + { + connectedSocket.SendTimeout = timeoutMs; + connectedSocket.ReceiveTimeout = timeoutMs; + + int bytesSent = await connectedSocket.SendAsync(new ArraySegment(token), SocketFlags.None); + if (bytesSent != token.Length) + { + return false; + } + + var responseBuffer = new byte[1]; + var receiveTask = connectedSocket.ReceiveAsync(new ArraySegment(responseBuffer), SocketFlags.None); + var timeoutTask = Task.Delay(timeoutMs); + + var completedTask = await Task.WhenAny(receiveTask, timeoutTask); + + if (completedTask == timeoutTask) + { + return false; + } + + int bytesReceived = receiveTask.Result; + + connectedSocket.SendTimeout = -1; + connectedSocket.ReceiveTimeout = -1; + + return bytesReceived == 1; + } + catch + { + return false; + } + } + + private void OnConnectionSuccessful(EndpointData endpoint, Socket socket) + { + if (IsCompleted()) + return; + + this.ConnectedSocket = socket; + this.SuccesfullEndpoint = endpoint; + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.ConnectionAckGood; + connection.SenAsyncMessage(msg); + } + + private void OnConnectionFail() + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.ConnectionAckBad; + connection.SenAsyncMessage(msg); + + Completed(false); + } + + private void HandleGoodAck(MessageEnvelope message) + { + Completed(true); + } + + private void HandleBadAck(MessageEnvelope message) + { + Completed(false); } - } } diff --git a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs index 2ece266..3520b91 100644 --- a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs +++ b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs @@ -6,11 +6,14 @@ namespace NetworkLibrary.DistributedP2P.Components { internal class InternalConstants { - public const string ConnectionStart = "-"; - public const string ConnectionReq = "0"; - public const string ConnectionAckGood = "1"; - public const string Error = "2"; - public const string ConnectionGetClientPublicData = "3"; - public const string ConnectionAckClientPublicData = "4"; + public const string ConnectionStart = "0"; + public const string ConnectionReq = "1"; + public const string ConnectionAckGood = "2"; + public const string ConnectionAckBad = "3"; + public const string Error = "4"; + public const string ConnectionGetClientPublicData = "5"; + public const string ConnectionAckClientPublicData = "6"; + public const string PipeRequest = "7"; + public const string PipeTokenDelivery = "7"; } } diff --git a/NetworkLibrary/DistributedP2P/Components/TimerService.cs b/NetworkLibrary/DistributedP2P/Components/TimerService.cs new file mode 100644 index 0000000..ae8bd1f --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/TimerService.cs @@ -0,0 +1,33 @@ +using NetworkLibrary.DistributedP2P.Server; +using System; +using System.Collections.Concurrent; +using System.Threading; +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class TimerService + { + private static readonly ConcurrentDictionary timers = new ConcurrentDictionary(); + + public static void RegisterTimer(Guid timerId,int delay, Action OnTime) + { + var timer = new Timer(s => + { + OnTime?.Invoke(); + CancelTimeout(timerId); + + }, null, delay, Timeout.Infinite); + + timers.TryAdd(timerId, timer); + } + + public static void CancelTimeout(Guid guid) + { + if (timers.TryRemove(guid, out var timer)) + { + timer.Dispose(); + } + + } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index d4c9511..eba4f9d 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -17,6 +17,7 @@ using System.Threading.Tasks; using NetworkLibrary.DistributedP2P.Server.StateManagement; +using System.Security.Cryptography; namespace NetworkLibrary.DistributedP2P.Server { @@ -50,9 +51,11 @@ public class ServerParameters SessionManager sessionManager; Components.StateManager stateManager = new Components.StateManager(); - PipeAssociator piper; + PipeManager piper; Stopwatch serverClock = new Stopwatch(); + PipeManager pipeManager; + private byte[] serverKey = new byte[16]; public DistributedLobbyServerBase(Dependencies dependencies, ServerParameters parameters) { @@ -74,6 +77,11 @@ public void StartServer() tcpServer = new AsyncTcpServer(TcpPort); udpServer = new AsyncUdpServer(UdpPort); + var random = RandomNumberGenerator.Create(); + var key = new byte[32]; + random.GetNonZeroBytes(key); + pipeManager = new PipeManager(tcpServer,udpServer,key); + sslServer.OnClientRequestedConnection += ValidateSslConnection; sslServer.OnClientAccepted += SslClientAccepted; sslServer.OnClientDisconnected += SslClientDisconnected; @@ -85,7 +93,7 @@ public void StartServer() tcpServer.StartServer(); sslServer.StartServer(); - sessionManager = new SessionManager(this, tcpServer, udpServer, serverKey); + sessionManager = new SessionManager(this); } @@ -142,11 +150,24 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) // holepunching // ping + message.From = clientId; + + if (stateManager.HandleMessage(message)) + return; + if (sessionManager.HandleMessage(clientId, message)) return; switch (message.Header) { + case InternalConstants.PipeRequest: + + var pipeState = new ServerPipeState(message.MessageId, this, pipeManager); + stateManager.RegisterState(pipeState); + pipeState.HandleMessage(message); + + break; + case Constants.TimeSync: byte[] time = new byte[8]; @@ -155,7 +176,7 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) message.TimeStamp = DateTime.UtcNow; SendAsyncMessage(clientId, message); - break; + break; } } diff --git a/NetworkLibrary/DistributedP2P/Server/PipeAssociator.cs b/NetworkLibrary/DistributedP2P/Server/PipeAssociator.cs deleted file mode 100644 index 2b00ad9..0000000 --- a/NetworkLibrary/DistributedP2P/Server/PipeAssociator.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Net; -using System.Text; -using NetworkLibrary.Components; -using NetworkLibrary.Components.Crypto.DigitalSignature; -using NetworkLibrary.MessageProtocol; -using NetworkLibrary.TCP.Base; -using NetworkLibrary.UDP; -using NetworkLibrary.P2P.Components.HolePunch; -using NetworkLibrary.Utils; -using System.IO; -using NetworkLibrary.DistributedP2P.Components; -namespace NetworkLibrary.DistributedP2P.Server -{ - internal class PipeAssociator - { - enum PipeFlag : byte - { - PipeAssociation = 1, - RoomPipeAssociation = 2, - PipeRoute = 3, - RoomRoute = 4 - } - internal AsyncTcpServer TcpServer; - internal AsyncUdpServer UdpServer; - - internal ConcurrentDictionary pipeMapTcp = new ConcurrentDictionary(); - internal ConcurrentDictionary pipeMapUdp = new ConcurrentDictionary(); - - ConcurrentDictionary> activeTcpPipeStates = new ConcurrentDictionary>(); - ConcurrentDictionary> activeUdpPipeStates = new ConcurrentDictionary>(); - - byte[] cryptoKey; - PrivateKeySign signer; - private ITimeProvider timeProvider; - - public PipeAssociator(AsyncTcpServer tcpServer, AsyncUdpServer udpServer, byte[] pipeKey, ITimeProvider timeProvider) - { - TcpServer = tcpServer; - UdpServer = udpServer; - this.timeProvider = timeProvider; - - TcpServer.OnBytesReceived += HandleTcpBytes; - UdpServer.OnBytesRecieved += HandleUdpBytes; - signer = new PrivateKeySign(pipeKey); - - } - - - private void HandleTcpBytes(Guid guid, byte[] bytes, int offset, int count) - { - if (!RouteTcp(guid, bytes, offset, count)) - { - ManagePipeToken(guid, bytes, offset, count); - } - } - - private void HandleUdpBytes(IPEndPoint endpoint, byte[] bytes, int offset, int count) - { - if (!RouteUdp(endpoint, bytes, offset, count)) - { - ManagePipeToken(endpoint, bytes, offset, count); - } - } - - - private bool RouteTcp(Guid guid, byte[] bytes, int offset, int count) - { - // we should look for room stuff here - if (pipeMapTcp.TryGetValue(guid, out var to)) - { - TcpServer.SendBytesToClient(to, bytes, offset, count); - return true; - } - return false; - } - - private bool RouteUdp(IPEndPoint endpoint, byte[] bytes, int offset, int count) - { - if (pipeMapUdp.TryGetValue(endpoint, out var to)) - { - UdpServer.SendBytesToClient(to, bytes, offset, count); - return true; - } - return false; - } - - //tcp - private void ManagePipeToken(Guid guid, byte[] bytes, int offset, int count) - { - // state object etc - var token = ExtractPipeToken(bytes, offset, count); - if (token != null) - { - activeTcpPipeStates.TryGetValue(token.Token, out var state); - state.RegisterClient(guid); - if (state.IsComplete()) - { - TcpPipeCreated(state.Clients[0], state.Clients[1]); - } - } - else - { - TcpServer.CloseSession(guid); - } - } - - //udp - private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int count) - { - var token = ExtractPipeToken(bytes, offset, count); - if (token != null) - { - activeUdpPipeStates.TryGetValue(token.Token, out var state); - state.RegisterClient(clientEp); - if (state.IsComplete()) - { - UdpPipeCreated(state.Clients[0], state.Clients[1]); - } - } - else - { - UdpServer.RemoveClient(clientEp); - } - } - - private PipeToken ExtractPipeToken(byte[] bytes, int offset, int count) - { - if (count < 24 + 32) - throw new InvalidDataException(); - - int offPrev = offset; - var token = PrimitiveEncoder.ReadGuid(bytes, ref offset);//16 - var expiration = DateTime.FromBinary(PrimitiveEncoder.ReadFixedInt64(bytes, ref offset));//8 - - var calculatedSignature = signer.Sign(bytes, offPrev, 24); - - if (SignatureMatch(calculatedSignature, bytes, offset)) - { - return new PipeToken() { Token = token, Expiration = expiration }; - } - else - { - return null; - } - } - - private bool SignatureMatch(byte[] localSignature, byte[] bytes, int offset) - { - for (int i = 0; i < localSignature.Length; i++)//32 - { - if (localSignature[i] != bytes[offset + i]) - return false; - } - return true; - } - - internal void TcpPipeCreated(Guid from, Guid to) - { - pipeMapTcp.TryAdd(from, to); - pipeMapTcp.TryAdd(to, from); - } - - internal void HandleTcpPipeDisconnect(Guid from) - { - if (pipeMapTcp.TryRemove(from, out Guid to)) - { - pipeMapTcp.TryRemove(to, out _); - } - } - - internal void UdpPipeCreated(IPEndPoint from, IPEndPoint to) - { - pipeMapUdp.TryAdd(from, to); - pipeMapUdp.TryAdd(to, from); - } - - internal void HandleUdpPipeDisconnect(IPEndPoint from) - { - if (pipeMapUdp.TryRemove(from, out var to)) - { - pipeMapUdp.TryRemove(to, out _); - } - } - - internal void GetPipeData(PooledMemoryStream stream, bool tcp) - { - int originalPos = stream.Position32; - PipeToken pipeData = new PipeToken(); - - pipeData.Token = Guid.NewGuid(); - pipeData.Expiration = timeProvider.GetTime().AddSeconds(20); - - PrimitiveEncoder.WriteGuid(stream, pipeData.Token);//16 - long Time = pipeData.Expiration.ToBinary(); - PrimitiveEncoder.WriteFixedInt64(stream, Time); //8 - - byte[] signature = signer.Sign(stream.GetBuffer(), originalPos, 24); - stream.Write(signature, 0, signature.Length); - - if (tcp) - activeTcpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); - else - activeUdpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); - } - } -} diff --git a/NetworkLibrary/DistributedP2P/Server/PipeManager.cs b/NetworkLibrary/DistributedP2P/Server/PipeManager.cs new file mode 100644 index 0000000..dae3759 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/PipeManager.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Text; +using NetworkLibrary.Components; +using NetworkLibrary.Components.Crypto.DigitalSignature; +using NetworkLibrary.MessageProtocol; +using NetworkLibrary.TCP.Base; +using NetworkLibrary.UDP; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.Utils; +using System.IO; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Server.StateManagement; +using System.Threading.Tasks; +using System.Threading; +using System.Security.Cryptography; +namespace NetworkLibrary.DistributedP2P.Server +{ + internal class PipeToken + { + public Guid Token; + public DateTime Expiration; + + internal bool IsExpired() + { + DateTime now = DateTime.UtcNow; + if (now > Expiration) + return true; + + return false; + } + } + internal class TcpTokenStorage + { + internal byte[] Token = new byte[PipeData.TokenLength]; + int tokenOffset = 0; + + internal int StoreTokenFragment(byte[] tokenFragment, int offset, int count) + { + if (count > PipeData.TokenLength - tokenOffset) + { + return -1; + } + else + { + Buffer.BlockCopy(tokenFragment, offset, Token, tokenOffset, count); + tokenOffset += count; + if (tokenOffset == PipeData.TokenLength) + { + return 1; + } + return 0; + } + } + } + + internal class PipeManager + { + int tokenLifetimeMs = 20000; + + internal AsyncTcpServer TcpServer; + internal AsyncUdpServer UdpServer; + + internal ConcurrentDictionary pipeMapTcp = new ConcurrentDictionary(); + internal ConcurrentDictionary pipeMapUdp = new ConcurrentDictionary(); + + private ConcurrentDictionary> activeTcpPipeStates = new ConcurrentDictionary>(); + private ConcurrentDictionary> activeUdpPipeStates = new ConcurrentDictionary>(); + + private readonly ConcurrentDictionary tokenStorage = new ConcurrentDictionary(); + + byte[] cryptoKey; + PrivateKeySign signer; + readonly object tokenMtex = new object(); + + public PipeManager(AsyncTcpServer tcpServer, AsyncUdpServer udpServer, byte[] pipeKey) + { + cryptoKey = pipeKey; + + TcpServer = tcpServer; + UdpServer = udpServer; + + TcpServer.OnClientAccepted += TcpClientAccepted; + TcpServer.OnClientDisconnected += HandleTcpPipeDisconnect; + + TcpServer.OnBytesReceived += HandleTcpBytes; + UdpServer.OnBytesRecieved += HandleUdpBytes; + signer = new PrivateKeySign(pipeKey); + + } + + + private void TcpClientAccepted(Guid guid) + { + tokenStorage.TryAdd(guid, new TcpTokenStorage()); + TimerService.RegisterTimer(guid, tokenLifetimeMs, ()=>HandleTcpClientTimeout(guid)); + + } + + private void HandleTcpClientTimeout(Guid guid) + { + if (!pipeMapTcp.ContainsKey(guid)) + { + tokenStorage.TryRemove(guid, out _); + TcpServer.CloseSession(guid); + // ddos here + } + } + + private void CancelTimeout(Guid guid) + { + TimerService.CancelTimeout(guid); + tokenStorage.TryRemove(guid, out _); + } + + + + private void HandleTcpBytes(Guid clientId, byte[] bytes, int offset, int count) + { + if (!RouteTcp(clientId, bytes, offset, count)) + { + ManagePipeToken(clientId, bytes, offset, count); + } + } + + private void HandleUdpBytes(IPEndPoint endpoint, byte[] bytes, int offset, int count) + { + if (!RouteUdp(endpoint, bytes, offset, count)) + { + ManagePipeToken(endpoint, bytes, offset, count); + } + } + + + private bool RouteTcp(Guid guid, byte[] bytes, int offset, int count) + { + // we should look for room stuff here + if (pipeMapTcp.TryGetValue(guid, out var to)) + { + TcpServer.SendBytesToClient(to, bytes, offset, count); + return true; + } + return false; + } + + private bool RouteUdp(IPEndPoint endpoint, byte[] bytes, int offset, int count) + { + if (pipeMapUdp.TryGetValue(endpoint, out var to)) + { + UdpServer.SendBytesToClient(to, bytes, offset, count); + return true; + } + return false; + } + + + //tcp + private void ManagePipeToken(Guid guid, byte[] bytes, int offset, int count) + { + // state object etc + lock (tokenMtex) + { + tokenStorage.TryGetValue(guid, out var storage); + + int result = storage.StoreTokenFragment(bytes, offset, count); + + if (result == 0)//incomplete + return; + + if (result == -1)// nonsense + { + RemoveTcpClient(guid); + return; + } + + if (result == 1)// tokenComplete + { + PipeToken token = DeserializePipeToken(storage.Token, 0); + + if (activeTcpPipeStates.TryGetValue(token.Token, out var pipeState)) + { + if (VerifyToken(storage.Token, token.Expiration)) + { + pipeState.RegisterClient(guid); + + if (pipeState.IsComplete()) + { + activeTcpPipeStates.TryRemove(token.Token, out _); + TcpPipeCreated(pipeState.Clients[0], pipeState.Clients[1]); + } + + TcpServer.SendBytesToClient(guid, new byte[1] { 0x01 }); + } + else + { + RemoveTcpClient(guid); + } + } + else + { + RemoveTcpClient(guid); + } + } + else + { + RemoveTcpClient(guid); + } + } + } + + //udp + private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int count) + { + lock (tokenMtex) + { + byte[] tokenBytes = ByteCopy.ToArray(bytes, offset, count); + + if(tokenBytes.Length != PipeData.TokenLength) + { + return; + } + + PipeToken token = DeserializePipeToken(tokenBytes, 0); + + + if (activeUdpPipeStates.TryGetValue(token.Token, out var pipeState)) + { + if (VerifyToken(tokenBytes, token.Expiration)) + { + pipeState.RegisterClient(clientEp); + + if (pipeState.IsComplete()) + { + activeUdpPipeStates.TryRemove(token.Token, out _); + UdpPipeCreated(pipeState.Clients[0], pipeState.Clients[1]); + } + + UdpServer.SendBytesToClient(clientEp, new byte[1] { 0x01 }, 0, 1); + } + else + { + // ddos here. + UdpServer.RemoveClient(clientEp); + } + } + else + { + UdpServer.RemoveClient(clientEp); + } + } + + } + + private bool VerifyToken(byte[] Token, DateTime expiration) + { + var calculatedSignature = signer.Sign(Token, 0, 24); + if (SignatureMatch(calculatedSignature, Token)) + { + if (DateTime.UtcNow < expiration) + return true; + return false; + } + else + { + return false; + } + } + + private bool SignatureMatch(byte[] localSignature, byte[] incomingSignature) + { + for (int i = 0; i < 32; i++) + { + if (localSignature[i] != incomingSignature[24 + i]) + return false; + } + return true; + } + + + private PipeToken DeserializePipeToken(byte[] bytes, int offset) + { + var token = PrimitiveEncoder.ReadGuid(bytes, ref offset);//16 + var expiration = DateTime.FromBinary(PrimitiveEncoder.ReadFixedInt64(bytes, ref offset));//8 + + return new PipeToken() { Token = token, Expiration = expiration }; + } + + private void RemoveTcpClient(Guid guid) + { + TcpServer.CloseSession(guid); + } + + internal void TcpPipeCreated(Guid from, Guid to) + { + pipeMapTcp.TryAdd(from, to); + pipeMapTcp.TryAdd(to, from); + + CancelTimeout(from); + CancelTimeout(to); + } + + internal void UdpPipeCreated(IPEndPoint from, IPEndPoint to) + { + pipeMapUdp.TryAdd(from, to); + pipeMapUdp.TryAdd(to, from); + } + + internal void HandleUdpPipeDisconnect(IPEndPoint from) + { + if (pipeMapUdp.TryRemove(from, out var to)) + { + pipeMapUdp.TryRemove(to, out _); + } + } + + internal void HandleTcpPipeDisconnect(Guid from) + { + if (pipeMapTcp.TryRemove(from, out Guid to)) + { + TcpServer.CloseSession(to); + pipeMapTcp.TryRemove(to, out _); + } + + tokenStorage.TryRemove(from, out _); + } + + internal byte[] GetPipeToken( bool tcp) + { + byte[] data = new byte[PipeData.TokenLength]; + int offset = 0; + PipeToken pipeData = new PipeToken(); + + pipeData.Token = Guid.NewGuid(); + pipeData.Expiration = DateTime.UtcNow.AddMilliseconds(tokenLifetimeMs); + + PrimitiveEncoder.WriteGuid(data,ref offset, pipeData.Token);//16 + + long Time = pipeData.Expiration.ToBinary(); + PrimitiveEncoder.WriteFixedInt64(data, ref offset, Time); //8 + + byte[] signature = signer.Sign(data); + Buffer.BlockCopy(signature, 0, data, offset, 32);//32 + + if (tcp) + RegisterTcpToken(pipeData); + else + RegisterUdpToken(pipeData); + + return data; + } + + private void RegisterTcpToken(PipeToken pipeData) + { + activeTcpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); + TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeTcpPipeStates.TryRemove(pipeData.Token, out _)); + } + + private void RegisterUdpToken(PipeToken pipeData) + { + activeUdpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); + TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeUdpPipeStates.TryRemove(pipeData.Token, out _)); + + } + + } + + +} diff --git a/NetworkLibrary/DistributedP2P/Server/PipeState.cs b/NetworkLibrary/DistributedP2P/Server/PipeState.cs index 2d3713e..cbe0d36 100644 --- a/NetworkLibrary/DistributedP2P/Server/PipeState.cs +++ b/NetworkLibrary/DistributedP2P/Server/PipeState.cs @@ -1,4 +1,5 @@ -using System; +using NetworkLibrary.DistributedP2P.Server.StateManagement; +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; @@ -11,7 +12,7 @@ internal class PipeState internal List Clients = new List(); internal PipeToken pipeData; private object mtex = new object(); - + public PipeState(PipeToken pipeData) { this.pipeData = pipeData; @@ -28,5 +29,7 @@ internal void RegisterClient(T guid) lock (mtex) Clients.Add(guid); } + + } } diff --git a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs index 81179fb..3240a98 100644 --- a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs +++ b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs @@ -23,11 +23,7 @@ class Flags } - class PipeToken - { - public Guid Token; - public DateTime Expiration; - } + // Creation, and managing destruction of sessions. //Created after Authentication. @@ -36,19 +32,12 @@ class PipeToken internal class SessionManager where S : ISerializer, new() { internal ConcurrentDictionary serverSessions = new ConcurrentDictionary(); - internal ConcurrentDictionary endPointMap = new ConcurrentDictionary(); - internal ConcurrentDictionary pipeMapTcp = new ConcurrentDictionary(); - internal ConcurrentDictionary pipeMapUdp = new ConcurrentDictionary(); - PipeAssociator piper; - RandomNumberGenerator random; IDistributedConnection serverConnection; - public SessionManager(IDistributedConnection serverConnection, AsyncTcpServer tcpServer, AsyncUdpServer udpServer, byte[] pipeKey) + public SessionManager(IDistributedConnection serverConnection) { - random = RandomNumberGenerator.Create(); - piper = new PipeAssociator(tcpServer, udpServer, pipeKey, serverConnection); this.serverConnection = serverConnection; } @@ -57,61 +46,12 @@ internal bool HandleMessage(Guid from, MessageEnvelope envelope) { switch (envelope.Header) { - case "Pipe": - ManagePiping(from, envelope.To, envelope); - break; - } - return false; - } - - private async void ManagePiping(Guid A, Guid B, MessageEnvelope msg) - { - - PooledMemoryStream tokenData = await FindOptimumTcpServer(); - - MessageEnvelope envelope = new MessageEnvelope(); - envelope.Header = "PipeToken"; - envelope.MessageId = msg.MessageId; - envelope.SetPayload(tokenData.GetBuffer(), 0, tokenData.Position32); - - Task ackA = serverConnection.SendMessageAndWaitResponse(A, envelope); - Task ackB = serverConnection.SendMessageAndWaitResponse(B, envelope); - - await Task.WhenAll(ackA, ackB); - - MessageEnvelope finalAck = new MessageEnvelope(); - finalAck.MessageId = msg.MessageId; - finalAck.Header = "Fail"; - - if (isGood(ackA.Result) && isGood(ackB.Result)) - { - finalAck.Header = "Success"; - serverConnection.SendAsyncMessage(A, finalAck); - serverConnection.SendAsyncMessage(B, finalAck); + } - else - { - serverConnection.SendAsyncMessage(A, finalAck); - serverConnection.SendAsyncMessage(B, finalAck); - } - - SharerdMemoryStreamPool.ReturnStreamStatic(tokenData); - } - - private bool isGood(MessageEnvelope ackA) - { - if (ackA.Header != MessageEnvelope.RequestTimeout) - return true; return false; } - private Task FindOptimumTcpServer() - { - //do only local for now - PooledMemoryStream stream = SharerdMemoryStreamPool.RentStreamStatic(); - piper.GetPipeData(stream, tcp: true); - return Task.FromResult(stream); - } + public void CreateSession(IClientDbInfo clientInfo, Guid ephemeralClientId, IPEndPoint sessionEp) { diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs index 154c938..b4942cb 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs @@ -1,20 +1,130 @@ -using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.Components; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.P2P.Generic; +using NetworkLibrary.Utils; using System; using System.Collections.Generic; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace NetworkLibrary.DistributedP2P.Server.StateManagement { + class PipeData + { + public const int TokenLength = 32 + 24;//32 bytes signature, 24 bytes token + public byte[] Token { get; set; } + // localhost, localip, publicip + public List PipeEndpoints { get; set; } = new List(); + } + internal class ServerPipeState : ConversationStateBase { - public ServerPipeState(Guid stateId) : base(stateId) + private PipeManager piper; + private Guid from, to; + private int ackCount = 0; + private readonly IDistributedConnection connection; + + public ServerPipeState(Guid stateId, IDistributedConnection connection, PipeManager piper) : base(stateId) { + this.connection = connection; + this.piper = piper; } + /* + * C1 wants pope req with C2 + * Server finds a suitible Relay to pipe on + * Server obtains token from relay + * Server sends conn info and token to C1 and C2 + * + * C2 then needs to verify C1 optionally, with this you know that token came from trusted server and client is who he says he is. + * + */ + public override void HandleMessage(MessageEnvelope message) { - throw new NotImplementedException(); + switch (message.Header) + { + case InternalConstants.PipeRequest: + HandlePipeRequest(message); + break; + + case InternalConstants.ConnectionAckGood: + HandleGoodAck(message); + break; + + case InternalConstants.ConnectionAckBad: + HandleBadAck(message); + break; + + } + } + + private void HandlePipeRequest(MessageEnvelope message) + { + this.from = message.From; + this.to = message.To; + ObtainPipeToken().ContinueWith(HandlePipeToken); + } + + private Task ObtainPipeToken() + { + //do only local for now + byte[] token = piper.GetPipeToken(tcp: true); + PipeData data = new PipeData(); + data.Token = token; + data.PipeEndpoints = new List(); + + return Task.FromResult(data); } + + + private void HandlePipeToken(Task task) + { + var data = task.Result; + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PipeTokenDelivery; + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.Position32 = 0; + + KnownTypeSerializer.SerializePipeData(stream, data); + msg.SetPayload(stream.GetBuffer(),0,stream.Position32); + + connection.SendAsyncMessage(from, msg); + connection.SendAsyncMessage(to, msg); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + + + private void HandleBadAck(MessageEnvelope message) + { + lock (cancellationMutex) + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.ConnectionAckBad; + connection.SendAsyncMessage(from, msg); + connection.SendAsyncMessage(to, msg); + Completed(false); + } + } + + private void HandleGoodAck(MessageEnvelope message) + { + if (Interlocked.Increment(ref ackCount) == 2) + { + lock (cancellationMutex) + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.ConnectionAckGood; + connection.SendAsyncMessage(from, msg); + connection.SendAsyncMessage(to, msg); + Completed(true); + } + } + } + + } } diff --git a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs index d07a53f..9c0d4f1 100644 --- a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs +++ b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs @@ -1,9 +1,12 @@ using NetworkLibrary.Components; using NetworkLibrary.DistributedP2P.Server; +using NetworkLibrary.DistributedP2P.Server.StateManagement; using NetworkLibrary.P2P.Generic; using NetworkLibrary.Utils; using System; using System.Collections.Generic; +using System.Reflection; +using System.Threading; namespace NetworkLibrary.P2P.Components.HolePunch { @@ -13,33 +16,38 @@ public class KnownTypeSerializer #region PipeData - //internal static void SerializeSignedPipeData(PooledMemoryStream stream, SignedPipeData signed) - //{ - // SerializePipeData(stream, signed.PipeData); - // stream.Write(signed.Signature, 0, signed.Signature.Length); - //} - - - - internal static void SerializePipeData(PooledMemoryStream stream, PipeToken pipeData) + internal static void SerializePipeData(PooledMemoryStream stream, PipeData pipeData) { - SerializeEndpointData(stream, pipeData.serverEndpoint); - PrimitiveEncoder.WriteGuid(stream, pipeData.Token); - PrimitiveEncoder.WriteDatetime(stream, pipeData.Expiration); + stream.Write(pipeData.Token, 0, PipeData.TokenLength); + + if (pipeData.PipeEndpoints != null && pipeData.PipeEndpoints.Count > 0) + { + PrimitiveEncoder.WriteInt32(stream, pipeData.PipeEndpoints.Count); + foreach (EndpointData ep in pipeData.PipeEndpoints) + { + SerializeEndpointData(stream, ep); + } + } + else + throw new InvalidOperationException("PipeData.PipeEndpoints is null or empty"); } - internal static PipeToken DeserializePipeData(byte[] buffer,ref int offset) + internal static PipeData DeserializePipeData(byte[] buffer, ref int offset) { - PipeToken pipeData = new PipeToken(); + PipeData pipeData = new PipeData(); + pipeData.Token = ByteCopy.ToArray(buffer, offset, PipeData.TokenLength); + offset += PipeData.TokenLength; + + int count = PrimitiveEncoder.ReadInt32(buffer, ref offset); + for (int i = 0; i < count; i++) + { + pipeData.PipeEndpoints.Add(DeserializeEndpointData(buffer, ref offset)); + } - pipeData.serverEndpoint = DeserializeEndpointData(buffer, ref offset); - pipeData.Token = PrimitiveEncoder.ReadGuid(buffer, ref offset); - pipeData.Expiration = PrimitiveEncoder.ReadDatetime(buffer, ref offset); - return pipeData; } - + #endregion #region Endpoint Data diff --git a/NetworkLibrary/TCP/Base/TcpSession.cs b/NetworkLibrary/TCP/Base/TcpSession.cs index 465043d..7879135 100644 --- a/NetworkLibrary/TCP/Base/TcpSession.cs +++ b/NetworkLibrary/TCP/Base/TcpSession.cs @@ -125,10 +125,7 @@ protected virtual IMessageQueue CreateMessageQueue() #region Recieve protected virtual void Receive() { -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// ReceiveModern(); -// return; -//#endif + if (IsSessionClosing()) { ReleaseReceiveResourcesIdempotent(); @@ -149,49 +146,7 @@ protected virtual void Receive() } -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER - -// private async void ReceiveModern() -// { -// try -// { -// while (true) -// { -// if (IsSessionClosing()) -// { -// ReleaseReceiveResourcesIdempotent(); -// return; -// } - -// int amountReceived = await sessionSocket.ReceiveAsync(receiveMemory, -// SocketFlags.None).ConfigureAwait(false); -// if (IsSessionClosing()) -// { -// ReleaseReceiveResourcesIdempotent(); -// return; -// } -// if (amountReceived == 0) -// { -// Disconnect(); -// ReleaseReceiveResourcesIdempotent(); -// return; -// } -// totalBytesReceived += amountReceived; -// HandleReceived(recieveBuffer, 0, amountReceived); -// } -// } -// catch (Exception e) -// { -// if (!IsSessionClosing()) -// { -// MiniLogger.Log(MiniLogger.LogLevel.Error, -// "While receiving on tcp session error occured;" +e.Message); -// EndSession(); -// } -// } -// } -//#endif private void BytesRecieved(object sender, SocketAsyncEventArgs e) { From b81d7a47e40a8c97b9eae8b4781efb73e376c6bd Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Tue, 1 Apr 2025 16:45:08 +0200 Subject: [PATCH 10/27] dist.p2p time sync, peerlish publish, tests --- .../Channels/ByteMessageChannel.cs | 44 +++ .../Client/DistributedLobbyClient.cs | 215 +++++++++-- .../Client/IClientAuthenticationProvider.cs | 2 +- .../Client/IClientAuthenticationToken.cs | 2 +- .../Client/IClientConnection.cs | 11 - .../DistributedP2P/Client/ITcpChannel.cs | 2 + .../DistributedP2P/Client/SecureTcpChannel.cs | 5 + .../StateManagement/ClientConnectionState.cs | 18 +- .../Client/StateManagement/ClientPipeState.cs | 141 +++++-- .../Components/ConversationStateBase.cs | 7 +- .../Components/InternalConstants.cs | 7 +- .../DistributedP2P/Components/TimeSync.cs | 249 +++++++++++++ .../Server/DistributedLobbyServer.cs | 87 +++-- .../Server/IDistributedConnection.cs | 2 + .../DistributedP2P/Server/ServerSession.cs | 94 ++++- .../DistributedP2P/Server/SessionManager.cs | 103 +++++- .../StateManagement/ServerConnectionState.cs | 17 +- .../Server/StateManagement/ServerPipeState.cs | 24 +- .../{Server => SimpleRelay}/PipeManager.cs | 50 ++- .../{Server => SimpleRelay}/PipeState.cs | 6 +- .../HolePunch/KnownTypeSerializer.cs | 82 +++++ .../P2P/Components/HolePunch/Messages.cs | 7 + NetworkLibrary/P2P/Generic/RelayClientBase.cs | 2 +- NetworkLibrary/TCP/AES/AesTcpClient.cs | 22 +- NetworkLibrary/TCP/Base/AsyncTcpClient.cs | 34 +- Protobuff/Protobuff.csproj | 2 +- .../DistributedP2P/DistP2PServerclientTest.cs | 345 ++++++++++++++++++ 27 files changed, 1365 insertions(+), 215 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs delete mode 100644 NetworkLibrary/DistributedP2P/Client/IClientConnection.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/TimeSync.cs rename NetworkLibrary/DistributedP2P/{Server => SimpleRelay}/PipeManager.cs (91%) rename NetworkLibrary/DistributedP2P/{Server => SimpleRelay}/PipeState.cs (91%) create mode 100644 Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs diff --git a/NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs new file mode 100644 index 0000000..73edd34 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs @@ -0,0 +1,44 @@ +using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.TCP.ByteMessage; +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Channels +{ + public class ByteMessageChannel : ITcpChannel + { + public ChannelInfo Info { get; private set; } + + public event Action BytesReceived; + public event Action Disconnected; + + private ByteMessageTcpClient client; + private readonly Socket connectedSocket; + + public ByteMessageChannel(ChannelInfo info, Socket connectedSocket) + { + Info = info; + this.connectedSocket = connectedSocket; + client = new ByteMessageTcpClient(); + client.GatherConfig = ScatterGatherConfig.UseBuffer; + + client.OnDisconnected += () => Disconnected?.Invoke(); + client.OnBytesReceived += (b, o, c) => BytesReceived?.Invoke(b, o, c); + } + + public void Start() + { + client.SetConnectedSocket(connectedSocket, ScatterGatherConfig.UseBuffer); + } + + + public void SendAsync(byte[] buffer, int offset, int count) + { + client.SendAsync(buffer, offset, count); + } + + + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 76e1d53..229699c 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -1,40 +1,65 @@ -using MessageProtocol; +using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.Components.Crypto.KeyDerivation; +using NetworkLibrary.DistributedP2P.Channels; +using NetworkLibrary.DistributedP2P.Client.StateManagement; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Server; +using NetworkLibrary.MessageProtocol; +using NetworkLibrary.P2P.Components.HolePunch; using System; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Text; -using NetworkLibrary.MessageProtocol; +using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; +using System.Threading; using System.Threading.Tasks; -using System.Net.Sockets; -using NetworkLibrary.TCP.AES; -using NetworkLibrary.Components.Crypto; -using NetworkLibrary.Components; -using NetworkLibrary.TCP.ByteMessage; -using NetworkLibrary.Components.Crypto.DiffieHellman; -using NetworkLibrary.Components.Crypto.KeyDerivation; -using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.DistributedP2P.Client.StateManagement; -using NetworkLibrary.TCP.SSL.Base; namespace NetworkLibrary.DistributedP2P.Client { - public class DistributedLobbyClient:IClientConnection where S : ISerializer, new() + public class DistributedLobbyClient : IDistributedConnection where S : ISerializer, new() { IClientDbConnection clientDbConnector; IClientAuthenticationProvider clientAuthProvider; SecureMessageClient sslClient; - StateManager stateManager = new StateManager(); - public DistributedLobbyClient(IClientDbConnection clientDbConnector, X509Certificate2 certificate = null) + StateManager stateManager = new StateManager(); + TimeSync timeSync; + + private ConcurrentDictionary onlinePeers = new ConcurrentDictionary(); + + public event Action PeerConnected; + public event Action PeerOnline; + public event Action PeerOffline; + + public event Action MessageReceived; + public event Action Disconnected; + + public Guid SessionId { get; private set; } + + private int connected = 0; + public bool IsConnected + { + get => Interlocked.CompareExchange(ref connected, 0, 0) == 1; + private set => Interlocked.Exchange(ref connected, value ? 1 : 0); + } + + public DistributedLobbyClient(IClientDbConnection clientDbConnector, + IClientAuthenticationProvider clientAuthProvider, + X509Certificate2 certificate = null) { this.clientDbConnector = clientDbConnector; + this.clientAuthProvider = clientAuthProvider; sslClient = new SecureMessageClient(certificate); sslClient.OnMessageReceived += HandleServerMsg; + sslClient.OnDisconnected += HandleDisconnected; } - + public async Task ConnectAsync(string ip, int port) { + if (IsConnected) + return true; + IClientAuthenticationToken authToken = clientAuthProvider.Authenticate(); bool res = await sslClient.ConnectAsync(ip, port); @@ -43,11 +68,17 @@ public async Task ConnectAsync(string ip, int port) Guid conversationId = Guid.NewGuid(); var conState = new ClientConnectionState(conversationId, this, clientDbConnector, authToken); stateManager.RegisterState(conState); + conState.Start(); await conState.WaitCompletion(); if (conState.IsSuccesful) { + SessionId = conState.SessionId; + IsConnected = true; + timeSync = new TimeSync(this); + await timeSync.SyncTime(); + timeSync.StartAutoTimeSync(5000); return true; } @@ -57,41 +88,87 @@ public async Task ConnectAsync(string ip, int port) else return false; } - public void SenAsyncMessage(MessageEnvelope message) + public void SendAsyncMessage(MessageEnvelope message) { sslClient.SendAsyncMessage(message); } - public async Task SendMessageAndWaitResponse(MessageEnvelope message) + public Task SendMessageAndWaitResponse(MessageEnvelope message) + { + return sslClient.SendMessageAndWaitResponse(message); + } + public void SendAsyncMessage(Guid a, MessageEnvelope msg) + { + msg.To = a; + sslClient.SendAsyncMessage(msg); + } + + public Task SendMessageAndWaitResponse(Guid a, MessageEnvelope msg) { - return await sslClient.SendMessageAndWaitResponse(message); + msg.To = a; + return sslClient.SendMessageAndWaitResponse(msg); } + public async Task OpenTcpSocket(Guid destinationPeer, ChannelInfo Info) + { + var pipeState = new ClientPipeState(Guid.NewGuid(), this); + stateManager.RegisterState(pipeState); + pipeState.Start(destinationPeer); + + await pipeState.WaitCompletion(); + + if (pipeState.IsSuccesful) + { + Console.WriteLine("PipeSuccesfull"); + return pipeState.ConnectedSocket; + } + return null; + } + public async Task OpenTcpChannel(Guid destinationPeer, ChannelInfo Info) { - var pipeState = new ClientPipeState(Guid.NewGuid(), this); - stateManager.RegisterState(pipeState); - pipeState.Start(destinationPeer); + var pipeState = new ClientPipeState(Guid.NewGuid(), this); + stateManager.RegisterState(pipeState); + pipeState.Start(destinationPeer); await pipeState.WaitCompletion(); if (pipeState.IsSuccesful) { + var info = new ChannelInfo(); + var channel = new ByteMessageChannel(info, pipeState.ConnectedSocket); + return channel; + + + Console.WriteLine("PipeSuccesfull"); + return null; var symetricKey = await PerformDHWithPeer(destinationPeer); if (symetricKey != null) { - var channel = new SecureTcpChannel(Info, pipeState.ConnectedSocket, symetricKey); - return channel; + //var channel = new SecureTcpChannel(Info, pipeState.ConnectedSocket, symetricKey); + return null; } } return null; } + private void HandlePipeCreated(IConversationState state) + { + if (state.IsSuccesful) + { + var pipeState = (ClientPipeState)state; + + var info = new ChannelInfo(); + var channel = new ByteMessageChannel(info, pipeState.ConnectedSocket); + PeerConnected?.Invoke(channel); + + Console.WriteLine("DestPeer Conn Succesfull"); + // notify that a connection is opened, like socket accept + } + } + private async Task PerformDHWithPeer(Guid destinationPeer) { - // lets do this first - // so how the other party associates this with the channel. - // maybe we need channel creation state machine. DiffieHellman df = new DiffieHellman(); byte[] publicKey = df.GetPublicKey(); @@ -101,7 +178,6 @@ private async Task PerformDHWithPeer(Guid destinationPeer) envelope.To = destinationPeer; envelope.Payload = publicKey; - var response = await SendMessageAndWaitResponse(envelope); if (response.Header != MessageEnvelope.RequestTimeout) { @@ -115,35 +191,85 @@ private async Task PerformDHWithPeer(Guid destinationPeer) return null; } - - private void HandleServerMsg(MessageEnvelope envelope) { + if (envelope.IsInternal) { if (stateManager.HandleMessage(envelope)) return; - switch (envelope.Header) + switch (envelope.Header) { - case InternalConstants.PipeTokenDelivery: + case InternalConstants.PipeTokenDeliveryTcp: var pipeState = new ClientPipeState(envelope.MessageId, this); pipeState.OnComplete += HandlePipeCreated; stateManager.RegisterState(pipeState); pipeState.HandleMessage(envelope); break; - + + case InternalConstants.PublishPeerList: + + var buffer = envelope.Payload; + int offset = envelope.PayloadOffset; + + PeerStatusList statusList = KnownTypeSerializer.DeserializePeerStatusList(buffer, ref offset); + PublishPeerStatusEvents(statusList); + + break; + } } + else + { + MessageReceived?.Invoke(envelope); + } + + } - private void HandlePipeCreated(IConversationState state) + private void PublishPeerStatusEvents(PeerStatusList statusList) + { + foreach (var offlineKV in statusList.WentOffline) + { + if (onlinePeers.TryRemove(offlineKV.Value.EphemeralId, out _)) + PeerOffline?.Invoke(offlineKV.Value); + } + + foreach (var onlineKV in statusList.NewOnline) + { + if (onlinePeers.TryAdd(onlineKV.Value.EphemeralId, onlineKV.Value)) + PeerOnline?.Invoke(onlineKV.Value); + } + } + + public Dictionary GetPeerList() { - if(state.IsSuccesful) - { - // notify that a connection is opened, like socket accept - } + Dictionary copy = new Dictionary(); + foreach (var peerKv in onlinePeers) + { + var sts = new PeerStatus(); + sts.EphemeralId = peerKv.Value.EphemeralId; + sts.PeerId = peerKv.Value.PeerId; + sts.OnlineSince = peerKv.Value.OnlineSince; + + copy[peerKv.Key] = sts; + } + return copy; + } + + private int disposed = 0; + + + public double GetTime() + { + return timeSync.GetTime(); + } + + public DateTime GetDateTime() + { + return timeSync.GetDateTime(); } public void Disconnect() @@ -151,6 +277,17 @@ public void Disconnect() sslClient.Disconnect(); } + private void HandleDisconnected() + { + IsConnected = false; + + timeSync.StopAutoTimeSync(); + Disconnected?.Invoke(); + } + DateTime ITimeProvider.GetTime() + { + return timeSync.GetDateTime(); + } } } diff --git a/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationProvider.cs b/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationProvider.cs index 2263acd..b876718 100644 --- a/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationProvider.cs +++ b/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationProvider.cs @@ -1,6 +1,6 @@ namespace NetworkLibrary.DistributedP2P.Client { - internal interface IClientAuthenticationProvider + public interface IClientAuthenticationProvider { IClientAuthenticationToken Authenticate(); } diff --git a/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationToken.cs b/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationToken.cs index fa821dc..f6627d9 100644 --- a/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationToken.cs +++ b/NetworkLibrary/DistributedP2P/Client/IClientAuthenticationToken.cs @@ -1,6 +1,6 @@ namespace NetworkLibrary.DistributedP2P.Client { - internal interface IClientAuthenticationToken + public interface IClientAuthenticationToken { string Token { get; } string AuthenticationMethod { get; } diff --git a/NetworkLibrary/DistributedP2P/Client/IClientConnection.cs b/NetworkLibrary/DistributedP2P/Client/IClientConnection.cs deleted file mode 100644 index a52f092..0000000 --- a/NetworkLibrary/DistributedP2P/Client/IClientConnection.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace NetworkLibrary.DistributedP2P.Client -{ - internal interface IClientConnection - { - void SenAsyncMessage(MessageEnvelope message); - } -} diff --git a/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs b/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs index 35e2696..439713c 100644 --- a/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs @@ -10,5 +10,7 @@ public interface ITcpChannel event Action BytesReceived; event Action Disconnected; void SendAsync(byte[] buffer, int offset, int count); + + void Start(); } } \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs b/NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs index 0bd9d1b..9a51a25 100644 --- a/NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs @@ -69,5 +69,10 @@ private void ClientDisconnected() { Disconnected?.Invoke(); } + + public void Start() + { + throw new NotImplementedException(); + } } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs index eced4e8..e746d2c 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs @@ -1,4 +1,5 @@ using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Server; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -8,14 +9,16 @@ namespace NetworkLibrary.DistributedP2P.Client.StateManagement //Signed Challenge Tokens internal class ClientConnectionState : ConversationStateBase { - private readonly IClientConnection connection; + private readonly IDistributedConnection connection; private readonly IClientDbConnection clientDbConnector; private readonly IClientAuthenticationToken authToken; private TaskCompletionSource Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - public ClientConnectionState(Guid stateId, IClientConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken):base(stateId) + public Guid SessionId { get; private set; } + + public ClientConnectionState(Guid stateId, IDistributedConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken):base(stateId) { this.connection = connection; this.clientDbConnector = clientDbConnector; @@ -26,9 +29,7 @@ public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - case InternalConstants.ConnectionStart: - Start(); - break; + case InternalConstants.ConnectionGetClientPublicData: SendClientPublicData(message); break; @@ -41,7 +42,7 @@ public override void HandleMessage(MessageEnvelope message) } } - private void Start() + public void Start() { MessageEnvelope msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionReq; @@ -51,7 +52,7 @@ private void Start() msg.KeyValuePairs.Add("AuthMethod", authToken.AuthenticationMethod); msg.KeyValuePairs.Add("AdditionalData", authToken.AdditionalData); - connection.SenAsyncMessage(msg); + connection.SendAsyncMessage(msg); // now server will authenticate after this // may ask additional data to link, if we are first timer // then succes or fail @@ -63,12 +64,13 @@ private void SendClientPublicData(MessageEnvelope message) response.Header = InternalConstants.ConnectionAckClientPublicData; response.Payload = clientDbConnector.GetClientPublicData(); - connection.SenAsyncMessage(response); + connection.SendAsyncMessage(response); } private void HandleConnectionSucces(MessageEnvelope message) { + SessionId = message.To; Completed(succes: true); } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index 5b7142f..1c52b1f 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -1,4 +1,5 @@ using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; using System; using System.Collections.Generic; @@ -11,13 +12,13 @@ namespace NetworkLibrary.DistributedP2P.Client.StateManagement { internal class ClientPipeState : ConversationStateBase { - private readonly IClientConnection connection; + private readonly IDistributedConnection connection; private Guid destinationPeer; public Socket ConnectedSocket { get; private set; } public EndpointData SuccesfullEndpoint { get; private set; } - public ClientPipeState(Guid stateId, IClientConnection connection) : base(stateId) + public ClientPipeState(Guid stateId, IDistributedConnection connection) : base(stateId) { this.connection = connection; } @@ -26,18 +27,22 @@ public void Start(Guid destinationPeer) { this.destinationPeer = destinationPeer; var msg = CreateEnvelope(); - msg.Header = InternalConstants.PipeRequest; + msg.Header = InternalConstants.PipeRequestTcp; msg.To = destinationPeer; - connection.SenAsyncMessage(msg); + connection.SendAsyncMessage(msg); } public override void HandleMessage(MessageEnvelope message) { switch(message.Header) { - case InternalConstants.PipeTokenDelivery: - HandlePipeToken(message); + case InternalConstants.PipeTokenDeliveryTcp: + HandlePipeTokenTcp(message); + break; + + case InternalConstants.PipeTokenDeliveryUdp: + HandlePipeTokenUdp(message); break; case InternalConstants.ConnectionAckGood: @@ -50,34 +55,43 @@ public override void HandleMessage(MessageEnvelope message) } } - private async void HandlePipeToken(MessageEnvelope message) - { - int off = message.PayloadOffset; - var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); - foreach (EndpointData endpoint in pipeData.PipeEndpoints) + private async void HandlePipeTokenTcp(MessageEnvelope message) + { + try { - Socket connected = await TryConnectWithTimeout(endpoint); - if(connected != null) + int off = message.PayloadOffset; + var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); + + foreach (EndpointData endpoint in pipeData.PipeEndpoints) { - bool success = await TokenExchange(connected,pipeData.Token); - if(success) + Socket connected = await TryConnectWithTimeout(endpoint); + if (connected != null) { - OnConnectionSuccessful(endpoint,connected); - return; - } - else - { - try { connected.Close(); connected.Dispose(); } catch { } + bool success = await TokenExchange(connected, pipeData.Token); + if (success) + { + OnConnectionSuccessful(endpoint, connected); + return; + } + else + { + try { connected.Close(); connected.Dispose(); } catch { } + } } + // connect send token + //wait a data to come + //then send ack } - // connect send token - //wait a data to come - //then send ack - } - OnConnectionFail(); - return; + OnConnectionFail(); + return; + } + catch + { + OnConnectionFail(); + } + } @@ -174,6 +188,75 @@ private async Task TokenExchange(Socket connectedSocket, byte[] token, int } } + + + private async void HandlePipeTokenUdp(MessageEnvelope message) + { + int off = message.PayloadOffset; + var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); + + var connected = new Socket(SocketType.Dgram, ProtocolType.Udp); + + foreach (EndpointData endpoint in pipeData.PipeEndpoints) + { + + bool success = await TokenExchange(connected, pipeData.Token); + if (success) + { + OnConnectionSuccessful(endpoint, connected); + return; + } + else + { + try { connected.Close(); connected.Dispose(); } catch { } + } + + // connect send token + //wait a data to come + //then send ack + } + + OnConnectionFail(); + return; + } + + private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndPoint remoteEndPoint, int timeoutMs = 500) + { + try + { + udpSocket.Connect(remoteEndPoint); + + var sendTask = udpSocket.SendAsync(new ArraySegment(token), SocketFlags.None); + var sendTimeout = Task.Delay(timeoutMs); + + if (await Task.WhenAny(sendTask, sendTimeout) == sendTimeout || + sendTask.Result != token.Length) + { + return false; + } + + var responseBuffer = new byte[1024]; + var receiveTask = udpSocket.ReceiveAsync(new ArraySegment(responseBuffer), SocketFlags.None); + var receiveTimeout = Task.Delay(timeoutMs); + + if (await Task.WhenAny(receiveTask, receiveTimeout) == receiveTimeout) + { + return false; + } + + return receiveTask.Result == 1; + } + catch + { + return false; + } + finally + { + try { udpSocket.Close(); } catch { } + } + } + + private void OnConnectionSuccessful(EndpointData endpoint, Socket socket) { if (IsCompleted()) @@ -184,14 +267,14 @@ private void OnConnectionSuccessful(EndpointData endpoint, Socket socket) var msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionAckGood; - connection.SenAsyncMessage(msg); + connection.SendAsyncMessage(msg); } private void OnConnectionFail() { var msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionAckBad; - connection.SenAsyncMessage(msg); + connection.SendAsyncMessage(msg); Completed(false); } diff --git a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs index cd13c2e..645b2ac 100644 --- a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs +++ b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs @@ -22,6 +22,7 @@ internal abstract class ConversationStateBase:IConversationState public ConversationStateBase(Guid stateId) { + this.StateId = stateId; Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); } @@ -40,7 +41,7 @@ public void Cancel() } } - protected MessageEnvelope CreateErrorMsg(string err) + protected virtual MessageEnvelope CreateErrorMsg(string err) { var msg = CreateEnvelope(); msg.Header = InternalConstants.Error; @@ -50,7 +51,7 @@ protected MessageEnvelope CreateErrorMsg(string err) }; return msg; } - protected MessageEnvelope CreateEnvelope() + protected virtual MessageEnvelope CreateEnvelope() { MessageEnvelope msg = new MessageEnvelope(); msg.MessageId = StateId; @@ -69,7 +70,7 @@ protected void Completed(bool succes) if (Interlocked.CompareExchange(ref isComplete, 1, 0) == 0) { - IsSuccesful = false; + IsSuccesful = succes; OnComplete?.Invoke(this); Completion.SetResult(this); OnComplete = null; diff --git a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs index 3520b91..d7a2cc3 100644 --- a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs +++ b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs @@ -13,7 +13,10 @@ internal class InternalConstants public const string Error = "4"; public const string ConnectionGetClientPublicData = "5"; public const string ConnectionAckClientPublicData = "6"; - public const string PipeRequest = "7"; - public const string PipeTokenDelivery = "7"; + public const string PipeRequestTcp = "7"; + public const string PipeRequestUdp = "8"; + public const string PipeTokenDeliveryTcp = "9"; + public const string PipeTokenDeliveryUdp = "a"; + public const string PublishPeerList = "b"; } } diff --git a/NetworkLibrary/DistributedP2P/Components/TimeSync.cs b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs new file mode 100644 index 0000000..34fc5da --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NetworkLibrary.Utils; +using NetworkLibrary.P2P; +using NetworkLibrary.DistributedP2P.Server; + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class TimeSync + { + + private Stopwatch clientClock = Stopwatch.StartNew(); + private double timeOffset; + private TimeSpan timeOffsetd; + private long syncCount = 0; + + private List timesHistory = new List(); + private List timesHistoryd = new List(); + private readonly SemaphoreSlim asyncLock = new SemaphoreSlim(1, 1); + + IDistributedConnection connection; + + public TimeSync(IDistributedConnection connection) + { + this.connection = connection; + } + + AsyncDispatcher timesyncOperation; + public void StartAutoTimeSync(int periodMs, bool usePTP = false) + { + Interlocked.Exchange(ref timesyncOperation, new AsyncDispatcher())?.Abort(); + int failureCount = 0; + timesyncOperation.LoopPeriodicTask(async () => + { + try + { + + bool result = await SyncTime(usePTP).ConfigureAwait(false); + if ( result == false) + { + if (++failureCount > 2) + { + StopAutoTimeSync(); + } + + } + else + { + failureCount = 0; + } + + } + catch (Exception e) + { + StopAutoTimeSync(); + } + + }, periodMs).ConfigureAwait(false); + } + + public void StopAutoTimeSync() + { + timesyncOperation?.Abort(); + ClearData(); + } + + private async void ClearData() + { + try + { + await asyncLock.WaitAsync().ConfigureAwait(false); + //not sure + syncCount = 0; + timesHistory.Clear(); + timesHistoryd.Clear(); + } + catch { } + finally + { + asyncLock.Release(); + } + + } + + public async Task SyncTime(bool usePtp = false) + { + try + { + await asyncLock.WaitAsync().ConfigureAwait(false); + + //12 first, 3,2,1 + + int sampleSize = 3; + var sCnt = Interlocked.CompareExchange(ref syncCount, 0, 0); + + if (sCnt == 0) + sampleSize = 12; + + if (sCnt > 50) + sampleSize = 2; + + if (sCnt > 100) + sampleSize = 1; + + + for (int i = 0; i < sampleSize; i++) + { + var result = usePtp ? await GetOffsetPTP().ConfigureAwait(false) : await GetOffsetNTP().ConfigureAwait(false); + if (result.Succes) + { + timesHistory.Add(result.PreciseTime); + timesHistoryd.Add(result.DateTimeOffset); + } + else return false; + } + + if (timesHistory.Count < 4) + return false; + + var times = Statistics.FilterOutliers(timesHistory); + var timesd = Statistics.FilterOutliers(timesHistoryd); + if (timesHistory.Count > 600) + { + timesHistory = timesHistory.Skip(60).ToList(); + timesHistoryd = timesHistoryd.Skip(60).ToList(); + } + + double average = times.Sum() / times.Count(); + double averaged = timesd.Sum(ts => ts.Ticks) / timesd.Count(); + timeOffset = average; + + if (sCnt > 0) + { + var calculatedAvg = TimeSpan.FromTicks((long)averaged); + if (timeOffsetd < calculatedAvg) + { + timeOffsetd = calculatedAvg; + } + } + else + { + timeOffsetd = TimeSpan.FromTicks((long)averaged); + } + + Interlocked.Increment(ref syncCount); + return true; + + } + finally + { + asyncLock.Release(); + } + + } + + class TimeResult { public double PreciseTime; public bool Succes; public DateTime ServerUTC; public TimeSpan DateTimeOffset; } + + private async Task GetOffsetNTP() + { + var msg = new MessageEnvelope() + { + Header = Constants.TimeSync, + IsInternal = true, + }; + var now = clientClock.Elapsed.TotalMilliseconds; + var nowd = DateTime.UtcNow; + + var response = await connection.SendMessageAndWaitResponse(msg).ConfigureAwait(false); + if (response.Header != MessageEnvelope.RequestTimeout) + { + + var serverTime = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); + var serverTimed = response.TimeStamp; + + var now1 = clientClock.Elapsed.TotalMilliseconds; + var now1d = DateTime.UtcNow; + + var timeOffset = ((serverTime - now) + (serverTime - now1)) / 2; + var timeOffsetd = ((serverTimed - nowd) + (serverTimed - now1d)).TotalMilliseconds / 2; + TimeSpan offd = TimeSpan.FromMilliseconds(timeOffsetd); + + return new TimeResult() { PreciseTime = timeOffset, DateTimeOffset = offd, Succes = true }; + } + return new TimeResult(); + + } + + private async Task GetOffsetPTP() + { + + var t1 = await GetServerTime(); + if (t1.Succes) + { + var t2 = clientClock.Elapsed.TotalMilliseconds; + var t2d = DateTime.UtcNow; + + var t3 = t2; + var t3d = t2d; + + var t4 = await GetServerTime(); + + if (t4.Succes) + { + double offset = (((t4.PreciseTime - t3) - (t2 - t1.PreciseTime)) / 2); + double offsetd = (((t4.ServerUTC - t3d) - (t2d - t1.ServerUTC)).TotalMilliseconds / 2); + TimeSpan offd = TimeSpan.FromMilliseconds(offsetd); + return new TimeResult() { PreciseTime = offset, DateTimeOffset = offd, Succes = true }; + } + else + return new TimeResult(); + } + else + return new TimeResult(); + + } + + private async Task GetServerTime() + { + var msg = new MessageEnvelope() + { + Header = Constants.TimeSync, + IsInternal = true, + }; + var response = await connection.SendMessageAndWaitResponse(msg); + if (response.Header != MessageEnvelope.RequestTimeout) + { + var serverTime = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); + return new TimeResult() { PreciseTime = serverTime, ServerUTC = response.TimeStamp, Succes = true }; + } + return new TimeResult(); + + } + + public double GetTime() + { + return clientClock.Elapsed.TotalMilliseconds + timeOffset; + } + + public DateTime GetDateTime() + { + return DateTime.UtcNow.Add(timeOffsetd); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index eba4f9d..34bb95d 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -18,6 +18,9 @@ using System.Threading.Tasks; using NetworkLibrary.DistributedP2P.Server.StateManagement; using System.Security.Cryptography; +using NetworkLibrary.DistributedP2P.SimpleRelay; +using NetworkLibrary.Utils; +using NetworkLibrary.P2P.Components.HolePunch; namespace NetworkLibrary.DistributedP2P.Server { @@ -34,7 +37,7 @@ public class ServerParameters public int TcpPort; public int UdpPort; } - public class DistributedLobbyServerBase : IDistributedConnection where S : ISerializer, new() + public class DistributedLobbyServerBase : IDistributedConnection,IDisposable where S : ISerializer, new() { public readonly int SSlPort; public readonly int TcpPort; @@ -74,47 +77,50 @@ public void StartServer() serverClock.Start(); sslServer = new SecureMessageServer(SSlPort, serverCertificate); - tcpServer = new AsyncTcpServer(TcpPort); - udpServer = new AsyncUdpServer(UdpPort); var random = RandomNumberGenerator.Create(); var key = new byte[32]; random.GetNonZeroBytes(key); - pipeManager = new PipeManager(tcpServer,udpServer,key); + pipeManager = new PipeManager(TcpPort, UdpPort, key); sslServer.OnClientRequestedConnection += ValidateSslConnection; sslServer.OnClientAccepted += SslClientAccepted; sslServer.OnClientDisconnected += SslClientDisconnected; - tcpServer.OnClientAccepting += ValidateTcpConnection; sslServer.OnMessageReceived += SslMessageReceived; - - udpServer.StartServer(); - tcpServer.StartServer(); sslServer.StartServer(); sessionManager = new SessionManager(this); + sessionManager.PeerListPublish += PublishPeerList; } + + private bool ValidateSslConnection(Socket acceptedSocket) { // we will do Ddos protection here; return true; } - private bool ValidateTcpConnection(Socket acceptedSocket) + + private void SslClientAccepted(Guid ephemeralClientId) { - return true; + TimerService.RegisterTimer(ephemeralClientId, 20000, () => + { + if (!sessionManager.IsSessionActive(ephemeralClientId)) + { + sslServer.CloseSession(ephemeralClientId); + } + }); } - private void SslClientAccepted(Guid ephemeralClientId) + private void HandleConnRequest(MessageEnvelope msg) { - Guid stateId = Guid.NewGuid(); - var state = new ServerConnectionState(stateId, ephemeralClientId, this, authenticator, dbConnector); + Guid stateId = msg.MessageId; + var state = new ServerConnectionState(stateId, msg.From, this, authenticator, dbConnector); stateManager.RegisterState(state); - - state.Start(); + state.HandleMessage(msg); state.OnComplete += ConnectionStateComplete; } @@ -125,8 +131,28 @@ private void ConnectionStateComplete(IConversationState state_) if (state.IsSuccesful) { var sessionEp = sslServer.GetSessionEndpoint(state.EphemeralClientId); - sessionManager.CreateSession(state.clientDbInfo, state.EphemeralClientId, sessionEp); + var statusList = sessionManager.CreateSession(state.clientDbInfo, state.EphemeralClientId, sessionEp); + if(statusList!=null) + PublishPeerList(new List() { statusList }); + } + } + + private void PublishPeerList(List pubList) + { + var msg = new MessageEnvelope(); + msg.Header = InternalConstants.PublishPeerList; + msg.IsInternal = true; + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + foreach (var pubData in pubList) + { + stream.Position32 = 0; + KnownTypeSerializer.SerializePeerStatusList(stream, pubData); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + + sslServer.SendAsyncMessage(pubData.WhoNeedsToKnow, msg); } + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } // so here message can be p2p message, clients business. @@ -151,16 +177,19 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) // ping message.From = clientId; - + if (stateManager.HandleMessage(message)) return; - if (sessionManager.HandleMessage(clientId, message)) - return; + switch (message.Header) { - case InternalConstants.PipeRequest: + case InternalConstants.ConnectionReq: + HandleConnRequest(message); + break; + + case InternalConstants.PipeRequestTcp: var pipeState = new ServerPipeState(message.MessageId, this, pipeManager); stateManager.RegisterState(pipeState); @@ -184,12 +213,20 @@ public void SendAsyncMessage(Guid clientId, MessageEnvelope message) { sslServer.SendAsyncMessage(clientId, message); } - + public void SendAsyncMessage(MessageEnvelope message) + { + sslServer.SendAsyncMessage(message.To, message); + } public Task SendMessageAndWaitResponse(Guid destination, MessageEnvelope envelope) { return sslServer.SendMessageAndWaitResponse(destination, envelope); } + public Task SendMessageAndWaitResponse( MessageEnvelope envelope) + { + return sslServer.SendMessageAndWaitResponse(envelope.To, envelope); + } + private void SslClientDisconnected(Guid guid) { sessionManager.DestroySession(guid); @@ -207,5 +244,13 @@ public DateTime GetTime() // will be distributed time return DateTime.UtcNow; } + + public void Dispose() + { + sslServer.ShutdownServer(); + pipeManager.Dispose(); + } + + } } diff --git a/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs b/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs index b470294..6af5b34 100644 --- a/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs +++ b/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs @@ -8,7 +8,9 @@ namespace NetworkLibrary.DistributedP2P.Server { internal interface IDistributedConnection : ITimeProvider { + void SendAsyncMessage(MessageEnvelope msgs); void SendAsyncMessage(Guid a, MessageEnvelope msgs); Task SendMessageAndWaitResponse(Guid a, MessageEnvelope msg); + Task SendMessageAndWaitResponse( MessageEnvelope msg); } } diff --git a/NetworkLibrary/DistributedP2P/Server/ServerSession.cs b/NetworkLibrary/DistributedP2P/Server/ServerSession.cs index 35a1a44..489816c 100644 --- a/NetworkLibrary/DistributedP2P/Server/ServerSession.cs +++ b/NetworkLibrary/DistributedP2P/Server/ServerSession.cs @@ -1,29 +1,103 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; +using static System.Collections.Specialized.BitVector32; namespace NetworkLibrary.DistributedP2P.Server { - enum Sessionstate - { - Uninitialized, - Authenticating, - ObtainingClientInfo, - EstablishingUdp, - Running - } // should hold the data about the client. Keys etc everything. + class PeerStatusList + { + public Guid WhoNeedsToKnow; + public ConcurrentDictionary NewOnline = new ConcurrentDictionary(); + public ConcurrentDictionary WentOffline = new ConcurrentDictionary(); + + public bool IsEmpty() + { + return NewOnline.Count == 0 && WentOffline.Count == 0; + } + } + public class PeerStatus + { + public Guid EphemeralId; + public Guid PeerId; + public DateTime OnlineSince; + } internal class ServerSession { - public Sessionstate state; public IClientDbInfo ClientInfo { get; } - public ServerSession(IClientDbInfo clientInfo) + public Guid PeerId { get; internal set; } + public Guid EphemeralId { get; internal set; } + public DateTime OnlineSince { get; internal set; } + + + private PeerStatusList statusList = new PeerStatusList(); + + ConcurrentDictionary onlinePeers = new ConcurrentDictionary(); + PeerStatus status = new PeerStatus(); + public ServerSession(IClientDbInfo clientInfo,Guid ephemeralId) { ClientInfo = clientInfo; + EphemeralId = ephemeralId; + PeerId = clientInfo.ClientId; + statusList.WhoNeedsToKnow = EphemeralId; + OnlineSince = DateTime.UtcNow; + + status.EphemeralId = EphemeralId; + status.PeerId = PeerId; + status.OnlineSince = OnlineSince; + } + internal bool Knows(Guid newPeer) + { + return true; + } + internal void AddNewPeer(Guid ephemeralId,PeerStatus status) + { + if (onlinePeers.TryAdd(ephemeralId, status)) + { + statusList.NewOnline.TryAdd(ephemeralId, status); + statusList.WentOffline.TryRemove(ephemeralId, out _); + } + } + + internal void RemovePeer(Guid ephemeralId) + { + if (onlinePeers.TryRemove(ephemeralId, out var status)) + { + statusList.WentOffline.TryAdd(ephemeralId, status); + statusList.NewOnline.TryRemove(ephemeralId, out _); + } + } + + internal PeerStatus GetPeerStatus() + { + return status; + } + + internal PeerStatusList GetPublishInfo() + { + if (statusList.IsEmpty()) + return null; + // must be hard copy + // either hard copy or lock until all published over network. + PeerStatusList list = new PeerStatusList(); + list.WentOffline = new ConcurrentDictionary(statusList.WentOffline); + list.NewOnline = new ConcurrentDictionary(statusList.NewOnline); + list.WhoNeedsToKnow = EphemeralId; + + return list; + } + + internal void ResetPublishInfo() + { + statusList.WentOffline.Clear(); + statusList.NewOnline.Clear(); + } } } diff --git a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs index 3240a98..fff4712 100644 --- a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs +++ b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs @@ -10,59 +10,122 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Security.Cryptography; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace NetworkLibrary.DistributedP2P.Server { - class Flags - { - public const byte Route = 1; - } - - // Creation, and managing destruction of sessions. - //Created after Authentication. - // Routing messages between sessions. - // Where do we put rooms? probably here internal class SessionManager where S : ISerializer, new() { internal ConcurrentDictionary serverSessions = new ConcurrentDictionary(); + internal HashSet lastSnapshot = new HashSet(); + private TaskCompletionSource publishTrigger = new TaskCompletionSource(); - IDistributedConnection serverConnection; + public event Action> PeerListPublish; + IDistributedConnection serverConnection; + private bool shutdown; + private object publishMutex = new object(); public SessionManager(IDistributedConnection serverConnection) { this.serverConnection = serverConnection; + PublishRoutine(); } - internal bool HandleMessage(Guid from, MessageEnvelope envelope) { - switch (envelope.Header) + Guid to = envelope.To; + envelope.To = Guid.Empty; + envelope.From = from; + serverConnection.SendAsyncMessage(to, envelope); + + return true; + } + + public PeerStatusList CreateSession(IClientDbInfo clientInfo, Guid ephemeralClientId, IPEndPoint sessionEp) + { + var newSession = new ServerSession(clientInfo, ephemeralClientId); + lock (publishMutex) { - + if (!serverSessions.ContainsKey(ephemeralClientId)) + { + foreach (var existingSessionKV in serverSessions) + { + if (existingSessionKV.Value.Knows(newSession.PeerId)) + { + existingSessionKV.Value.AddNewPeer(ephemeralClientId, newSession.GetPeerStatus()); + newSession.AddNewPeer(existingSessionKV.Value.EphemeralId, existingSessionKV.Value.GetPeerStatus()); + } + } + serverSessions.TryAdd(ephemeralClientId, newSession); + + } + // for instant sync of new peer + var pubInfo = newSession.GetPublishInfo(); + newSession.ResetPublishInfo(); + + publishTrigger.TrySetResult(true); + return pubInfo; } - return false; + } - + internal void DestroySession(Guid ephemeralClientId) + { + lock (publishMutex) + { + if (serverSessions.TryRemove(ephemeralClientId, out ServerSession session)) + { + foreach (var existingSessionKV in serverSessions) + { + existingSessionKV.Value.RemovePeer(ephemeralClientId); + } + publishTrigger.TrySetResult(true); + } + } + } - public void CreateSession(IClientDbInfo clientInfo, Guid ephemeralClientId, IPEndPoint sessionEp) + internal bool IsSessionActive(Guid ephemeralClientId) { - serverSessions.TryAdd(ephemeralClientId, new ServerSession(clientInfo)); - //Send a feedback + return serverSessions.ContainsKey(ephemeralClientId); } - internal void DestroySession(Guid guid) + + internal async void PublishRoutine() { + List PubList = new List(); + while (!shutdown) + { + await publishTrigger.Task; + Interlocked.Exchange(ref publishTrigger, new TaskCompletionSource()); - } + PubList.Clear(); + + lock (publishMutex) + { + foreach (var sessionKV in serverSessions) + { + PeerStatusList toPublish = sessionKV.Value.GetPublishInfo(); + if (toPublish != null) + { + sessionKV.Value.ResetPublishInfo(); + PubList.Add(toPublish); + } + } + } + + PeerListPublish?.Invoke(PubList); + await Task.Delay(1000); + } + } } } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs index 4669b45..0f35809 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs @@ -26,12 +26,12 @@ public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection this.dbConnector = dbConnector; } - internal void Start() - { - var msg = CreateEnvelope(); - msg.Header = InternalConstants.ConnectionStart; - connection.SendAsyncMessage(EphemeralClientId, msg); - } + //internal void Start() + //{ + // var msg = CreateEnvelope(); + // msg.Header = InternalConstants.ConnectionStart; + // connection.SendAsyncMessage(EphemeralClientId, msg); + //} public override void HandleMessage(MessageEnvelope message) { @@ -145,7 +145,7 @@ private void ReplyGood() { var msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionAckGood; - + msg.To = EphemeralClientId; lock (cancellationMutex) { if (IsCompleted()) @@ -163,7 +163,6 @@ private void ReplyError(string err) Completed(false); } - - + } } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs index b4942cb..6856ed0 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs @@ -1,5 +1,6 @@ using NetworkLibrary.Components; using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.SimpleRelay; using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.P2P.Generic; using NetworkLibrary.Utils; @@ -46,8 +47,11 @@ public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - case InternalConstants.PipeRequest: - HandlePipeRequest(message); + case InternalConstants.PipeRequestTcp: + HandlePipeRequest(message, true); + break; + case InternalConstants.PipeRequestUdp: + HandlePipeRequest(message, false); break; case InternalConstants.ConnectionAckGood: @@ -61,30 +65,31 @@ public override void HandleMessage(MessageEnvelope message) } } - private void HandlePipeRequest(MessageEnvelope message) + private void HandlePipeRequest(MessageEnvelope message, bool tcp) { this.from = message.From; this.to = message.To; - ObtainPipeToken().ContinueWith(HandlePipeToken); + ObtainPipeToken(tcp).ContinueWith(t=>HandlePipeToken(t,tcp)); } - private Task ObtainPipeToken() + private Task ObtainPipeToken(bool tcp) { //do only local for now - byte[] token = piper.GetPipeToken(tcp: true); + byte[] token = piper.GetPipeToken(tcp); PipeData data = new PipeData(); data.Token = token; - data.PipeEndpoints = new List(); + data.PipeEndpoints = new List() { new EndpointData("127.0.0.1", 20011) }; + return Task.FromResult(data); } - private void HandlePipeToken(Task task) + private void HandlePipeToken(Task task, bool tcp) { var data = task.Result; var msg = CreateEnvelope(); - msg.Header = InternalConstants.PipeTokenDelivery; + msg.Header = tcp?InternalConstants.PipeTokenDeliveryTcp:InternalConstants.PipeTokenDeliveryUdp; var stream = SharerdMemoryStreamPool.RentStreamStatic(); stream.Position32 = 0; @@ -125,6 +130,5 @@ private void HandleGoodAck(MessageEnvelope message) } } - } } diff --git a/NetworkLibrary/DistributedP2P/Server/PipeManager.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/PipeManager.cs similarity index 91% rename from NetworkLibrary/DistributedP2P/Server/PipeManager.cs rename to NetworkLibrary/DistributedP2P/SimpleRelay/PipeManager.cs index dae3759..47b1202 100644 --- a/NetworkLibrary/DistributedP2P/Server/PipeManager.cs +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/PipeManager.cs @@ -16,7 +16,9 @@ using System.Threading.Tasks; using System.Threading; using System.Security.Cryptography; -namespace NetworkLibrary.DistributedP2P.Server +using System.Net.Sockets; + +namespace NetworkLibrary.DistributedP2P.SimpleRelay { internal class PipeToken { @@ -56,9 +58,9 @@ internal int StoreTokenFragment(byte[] tokenFragment, int offset, int count) } } - internal class PipeManager + internal class PipeManager:IDisposable { - int tokenLifetimeMs = 20000; + int tokenLifetimeMs = 200000; internal AsyncTcpServer TcpServer; internal AsyncUdpServer UdpServer; @@ -75,12 +77,13 @@ internal class PipeManager PrivateKeySign signer; readonly object tokenMtex = new object(); - public PipeManager(AsyncTcpServer tcpServer, AsyncUdpServer udpServer, byte[] pipeKey) + public PipeManager(int TcpPort, int UdpPort, byte[] pipeKey) { - cryptoKey = pipeKey; + TcpServer = new AsyncTcpServer(TcpPort); + TcpServer.GatherConfig = ScatterGatherConfig.UseBuffer; + UdpServer = new AsyncUdpServer(UdpPort); - TcpServer = tcpServer; - UdpServer = udpServer; + cryptoKey = pipeKey; TcpServer.OnClientAccepted += TcpClientAccepted; TcpServer.OnClientDisconnected += HandleTcpPipeDisconnect; @@ -89,14 +92,18 @@ public PipeManager(AsyncTcpServer tcpServer, AsyncUdpServer udpServer, byte[] pi UdpServer.OnBytesRecieved += HandleUdpBytes; signer = new PrivateKeySign(pipeKey); + + UdpServer.StartServer(); + TcpServer.StartServer(); + } - + private void TcpClientAccepted(Guid guid) { tokenStorage.TryAdd(guid, new TcpTokenStorage()); - TimerService.RegisterTimer(guid, tokenLifetimeMs, ()=>HandleTcpClientTimeout(guid)); - + TimerService.RegisterTimer(guid, tokenLifetimeMs, () => HandleTcpClientTimeout(guid)); + } private void HandleTcpClientTimeout(Guid guid) @@ -115,7 +122,7 @@ private void CancelTimeout(Guid guid) tokenStorage.TryRemove(guid, out _); } - + private void HandleTcpBytes(Guid clientId, byte[] bytes, int offset, int count) { @@ -179,7 +186,7 @@ private void ManagePipeToken(Guid guid, byte[] bytes, int offset, int count) { PipeToken token = DeserializePipeToken(storage.Token, 0); - if (activeTcpPipeStates.TryGetValue(token.Token, out var pipeState)) + if (activeTcpPipeStates.TryGetValue(token.Token, out var pipeState)) { if (VerifyToken(storage.Token, token.Expiration)) { @@ -217,7 +224,7 @@ private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int { byte[] tokenBytes = ByteCopy.ToArray(bytes, offset, count); - if(tokenBytes.Length != PipeData.TokenLength) + if (tokenBytes.Length != PipeData.TokenLength) { return; } @@ -250,7 +257,7 @@ private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int UdpServer.RemoveClient(clientEp); } } - + } private bool VerifyToken(byte[] Token, DateTime expiration) @@ -258,7 +265,7 @@ private bool VerifyToken(byte[] Token, DateTime expiration) var calculatedSignature = signer.Sign(Token, 0, 24); if (SignatureMatch(calculatedSignature, Token)) { - if (DateTime.UtcNow < expiration) + if (DateTime.UtcNow < expiration) return true; return false; } @@ -326,7 +333,7 @@ internal void HandleTcpPipeDisconnect(Guid from) tokenStorage.TryRemove(from, out _); } - internal byte[] GetPipeToken( bool tcp) + internal byte[] GetPipeToken(bool tcp) { byte[] data = new byte[PipeData.TokenLength]; int offset = 0; @@ -335,12 +342,12 @@ internal byte[] GetPipeToken( bool tcp) pipeData.Token = Guid.NewGuid(); pipeData.Expiration = DateTime.UtcNow.AddMilliseconds(tokenLifetimeMs); - PrimitiveEncoder.WriteGuid(data,ref offset, pipeData.Token);//16 + PrimitiveEncoder.WriteGuid(data, ref offset, pipeData.Token);//16 long Time = pipeData.Expiration.ToBinary(); PrimitiveEncoder.WriteFixedInt64(data, ref offset, Time); //8 - byte[] signature = signer.Sign(data); + byte[] signature = signer.Sign(data, 0, 24); Buffer.BlockCopy(signature, 0, data, offset, 32);//32 if (tcp) @@ -364,7 +371,12 @@ private void RegisterUdpToken(PipeToken pipeData) } + public void Dispose() + { + TcpServer.ShutdownServer(); + UdpServer.Dispose(); + } } - + } diff --git a/NetworkLibrary/DistributedP2P/Server/PipeState.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/PipeState.cs similarity index 91% rename from NetworkLibrary/DistributedP2P/Server/PipeState.cs rename to NetworkLibrary/DistributedP2P/SimpleRelay/PipeState.cs index cbe0d36..c53ac80 100644 --- a/NetworkLibrary/DistributedP2P/Server/PipeState.cs +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/PipeState.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using System.Text; -namespace NetworkLibrary.DistributedP2P.Server +namespace NetworkLibrary.DistributedP2P.SimpleRelay { internal class PipeState { @@ -12,7 +12,7 @@ internal class PipeState internal List Clients = new List(); internal PipeToken pipeData; private object mtex = new object(); - + public PipeState(PipeToken pipeData) { this.pipeData = pipeData; @@ -30,6 +30,6 @@ internal void RegisterClient(T guid) Clients.Add(guid); } - + } } diff --git a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs index 9c0d4f1..99df714 100644 --- a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs +++ b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs @@ -5,6 +5,7 @@ using NetworkLibrary.Utils; using System; using System.Collections.Generic; +using System.IO; using System.Reflection; using System.Threading; @@ -13,7 +14,88 @@ namespace NetworkLibrary.P2P.Components.HolePunch public class KnownTypeSerializer { + #region AuthenticatedPeerList + internal static void SerializePeerStatusList(PooledMemoryStream stream, PeerStatusList statusList) + { + byte index = 0; + // Reserve for index + int oldPos = stream.Position32; + stream.WriteByte(index); + + if (statusList.NewOnline.Count>0) + { + PrimitiveEncoder.WriteInt32(stream, statusList.NewOnline.Count); + foreach (var item in statusList.NewOnline) + { + SerializePeerStatus(stream, item.Value); + } + index = 1; + } + if (statusList.WentOffline.Count>0) + { + PrimitiveEncoder.WriteInt32(stream, statusList.WentOffline.Count); + foreach (var item in statusList.WentOffline) + { + SerializePeerStatus(stream, item.Value); + } + index += 2; + } + + var buf = stream.GetBuffer(); + buf[oldPos] = index; + } + + internal static PeerStatusList DeserializePeerStatusList(byte[] buffer, ref int offset) + { + var index = buffer[offset++]; + + var data = new PeerStatusList(); + if ((index & 1) != 0) + { + int len = PrimitiveEncoder.ReadInt32(buffer, ref offset); + for (int i = 0; i < len; i++) + { + //online + var sts = DeserializePeerStatus(buffer, ref offset); + data.NewOnline.TryAdd(sts.EphemeralId, sts); + } + + } + + + if ((index & 1 << 1) != 0) + { + int len = PrimitiveEncoder.ReadInt32(buffer, ref offset); + for (int i = 0; i < len; i++) + { + //offline + var sts = DeserializePeerStatus(buffer, ref offset); + data.WentOffline.TryAdd(sts.EphemeralId, sts); + } + } + return data; + } + + internal static void SerializePeerStatus(PooledMemoryStream stream, PeerStatus status) + { + PrimitiveEncoder.WriteGuid(stream, status.EphemeralId); + PrimitiveEncoder.WriteGuid(stream, status.PeerId); + PrimitiveEncoder.WriteDatetime(stream, status.OnlineSince); + } + + internal static PeerStatus DeserializePeerStatus(byte[] buffer, ref int offset) + { + PeerStatus peerStatus = new PeerStatus(); + peerStatus.EphemeralId = PrimitiveEncoder.ReadGuid(buffer,ref offset); + peerStatus.PeerId = PrimitiveEncoder.ReadGuid(buffer, ref offset); + peerStatus.OnlineSince = PrimitiveEncoder.ReadDatetime(buffer, ref offset); + + return peerStatus; + } + + + #endregion #region PipeData internal static void SerializePipeData(PooledMemoryStream stream, PipeData pipeData) diff --git a/NetworkLibrary/P2P/Components/HolePunch/Messages.cs b/NetworkLibrary/P2P/Components/HolePunch/Messages.cs index 54bdcd1..636f8b3 100644 --- a/NetworkLibrary/P2P/Components/HolePunch/Messages.cs +++ b/NetworkLibrary/P2P/Components/HolePunch/Messages.cs @@ -32,5 +32,12 @@ public EndpointData(IPEndPoint ep) Ip = ep.Address.GetAddressBytes(); Port = ep.Port; } + + public EndpointData(string ip, int port) + { + IPAddress add = IPAddress.Parse(ip); + Ip = add.GetAddressBytes(); + this.Port = port; + } } } diff --git a/NetworkLibrary/P2P/Generic/RelayClientBase.cs b/NetworkLibrary/P2P/Generic/RelayClientBase.cs index b219987..546b5a8 100644 --- a/NetworkLibrary/P2P/Generic/RelayClientBase.cs +++ b/NetworkLibrary/P2P/Generic/RelayClientBase.cs @@ -362,7 +362,7 @@ private async Task GetServerTime() return new TimeResult(); } - public void GetTcpStatistics(out TcpStatistics stats) => tcpMessageClient.GetStatistics(out stats); + public void GetTcpStatistics(out TcpStatistics stats) => tcpMessageClient.GetStatistics(out stats); private bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { diff --git a/NetworkLibrary/TCP/AES/AesTcpClient.cs b/NetworkLibrary/TCP/AES/AesTcpClient.cs index 9796818..97ee434 100644 --- a/NetworkLibrary/TCP/AES/AesTcpClient.cs +++ b/NetworkLibrary/TCP/AES/AesTcpClient.cs @@ -44,17 +44,17 @@ private protected override IAsyncSession CreateSession(SocketAsyncEventArgs e, G // this.algorithm = algorithm; // sessionInternal.Algorithm = algorithm; //} - private protected override IAsyncSession CreateSession(Socket socket, Guid sessionId) - { - var session = new AesTcpSession(socket, sessionId, algorithm); - session.socketSendBufferSize = SocketSendBufferSize; - session.SocketRecieveBufferSize = SocketRecieveBufferSize; - session.MaxIndexedMemory = MaxIndexedMemory; - session.DropOnCongestion = DropOnCongestion; - session.UseQueue = false; - sessionInternal = session; - return session; - } + //private protected override IAsyncSession CreateSession(Socket socket, Guid sessionId) + //{ + // var session = new AesTcpSession(socket, sessionId, algorithm); + // session.socketSendBufferSize = SocketSendBufferSize; + // session.SocketRecieveBufferSize = SocketRecieveBufferSize; + // session.MaxIndexedMemory = MaxIndexedMemory; + // session.DropOnCongestion = DropOnCongestion; + // session.UseQueue = false; + // sessionInternal = session; + // return session; + //} public void SendAsync(byte[] data1, int offset1, int count1, byte[] data2, int offset2, int count2) { diff --git a/NetworkLibrary/TCP/Base/AsyncTcpClient.cs b/NetworkLibrary/TCP/Base/AsyncTcpClient.cs index fe92d28..3445627 100644 --- a/NetworkLibrary/TCP/Base/AsyncTcpClient.cs +++ b/NetworkLibrary/TCP/Base/AsyncTcpClient.cs @@ -17,14 +17,16 @@ public class AsyncTpcClient : TcpClientBase, IDisposable public AsyncTpcClient() { } - protected void SetConnectedSocket(Socket clientSocket, ScatterGatherConfig config) + internal protected virtual void SetConnectedSocket(Socket clientSocket, ScatterGatherConfig config) { this.GatherConfig = config; this.clientSocket = clientSocket; IsConnected = true; var Id = Guid.NewGuid(); - session = CreateSession(clientSocket, Id); + var ea = new SocketAsyncEventArgs(); + ea.AcceptSocket = clientSocket; + session = CreateSession(ea, Id); session.OnBytesRecieved += (sessionId, bytes, offset, count) => HandleBytesRecieved(bytes, offset, count); session.OnSessionClosed += (sessionId) => OnDisconnected?.Invoke(); @@ -118,20 +120,20 @@ private protected virtual IAsyncSession CreateSession(SocketAsyncEventArgs e, Gu ses.UseQueue = false; return ses; } - private protected virtual IAsyncSession CreateSession(Socket e, Guid sessionId) - { - var ses = new TcpSession(e, sessionId); - ses.socketSendBufferSize = SocketSendBufferSize; - ses.SocketRecieveBufferSize = SocketRecieveBufferSize; - ses.MaxIndexedMemory = MaxIndexedMemory; - ses.DropOnCongestion = DropOnCongestion; - - if (GatherConfig == ScatterGatherConfig.UseQueue) - ses.UseQueue = true; - else - ses.UseQueue = false; - return ses; - } + //private protected virtual IAsyncSession CreateSession(Socket e, Guid sessionId) + //{ + // var ses = new TcpSession(e, sessionId); + // ses.socketSendBufferSize = SocketSendBufferSize; + // ses.SocketRecieveBufferSize = SocketRecieveBufferSize; + // ses.MaxIndexedMemory = MaxIndexedMemory; + // ses.DropOnCongestion = DropOnCongestion; + + // if (GatherConfig == ScatterGatherConfig.UseQueue) + // ses.UseQueue = true; + // else + // ses.UseQueue = false; + // return ses; + //} #endregion #region Send & Receive diff --git a/Protobuff/Protobuff.csproj b/Protobuff/Protobuff.csproj index 10a58a7..318d495 100644 --- a/Protobuff/Protobuff.csproj +++ b/Protobuff/Protobuff.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0 + netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0 True Protobuf.Network.Library Protobuf Network Library diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs new file mode 100644 index 0000000..4de8862 --- /dev/null +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -0,0 +1,345 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NetworkLibrary; +using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Server; +using Protobuff.Components.Serialiser; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace UnitTests.DistributedP2P +{ + class ClientAuthToken : IClientAuthenticationToken + { + public string Token { get; } = "1234"; + + public string AuthenticationMethod { get; } = "1234"; + + public string AdditionalData { get; } = "1234"; + } + class ClientAuth : IClientAuthenticationProvider + { + public IClientAuthenticationToken Authenticate() + { + return new ClientAuthToken(); + } + } + class DbInfo : IClientDbInfo + { + public bool IsValid { get; } = true; + + public string Error { get; } + + public Guid ClientId { get; } = Guid.NewGuid(); + } + class ServerDb : IServerDbConnector + { + public Task GetClientData(IAuthenticationResult result) + { + IClientDbInfo dbInfo = new DbInfo(); + return Task.FromResult(dbInfo); + } + + public Task RegisterClient(IAuthenticationResult tokenResult, byte[] payload) + { + IClientDbInfo dbInfo = new DbInfo(); + return Task.FromResult(dbInfo); + } + } + class AuthResult : IAuthenticationResult + { + public bool IsValid { get; } = true; + + public string UserId { get; } = "1234"; + + public string Error { get; } + + public IReadOnlyDictionary Claims { get; } = new Dictionary(); + } + + class ServerAuth : IAuthenticator + { + public Task Authenticate(string AuthenticationToken, string AuthenticationMethod, string Cookies) + { + IAuthenticationResult result = new AuthResult(); + return Task.FromResult(result); + } + } + + + class ClientDB : IClientDbConnection + { + public byte[] GetClientPublicData() + { + return new byte[80]; + } + } + [TestClass] + public class DistP2PServerclientTest + { + + private static DistributedLobbyServerBase ArrangeServer() + { + var dep = new Dependencies() + { + Authenticator = new ServerAuth(), + DbConnector = new ServerDb() + }; + var param = new ServerParameters() + { + certificate = null, + SSlPort = 20010, + TcpPort = 20011, + UdpPort = 20012 + }; + var server = new DistributedLobbyServerBase(dep, param); + server.StartServer(); + return server; + } + + [TestMethod] + public void ConnectTest() + { + DistributedLobbyClient distributedLobbyClient = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + using var server = ArrangeServer(); + var res = distributedLobbyClient.ConnectAsync("127.0.0.1", 20010).Result; + } + + + + + [TestMethod] + public void PipeTest() + { + DistributedLobbyClient distributedLobbyClient = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + DistributedLobbyClient distributedLobbyClient2 = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + + using var server = ArrangeServer(); + + var res = distributedLobbyClient.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = distributedLobbyClient2.ConnectAsync("127.0.0.1", 20010).Result; + + distributedLobbyClient2.PeerConnected += PeerConnected; + + var info = new ChannelInfo(); + var channel1 = distributedLobbyClient.OpenTcpChannel(distributedLobbyClient2.SessionId, info).Result; + + Assert.IsNotNull(channel1); + + byte[] data = new byte[12800000]; + + channel1.Start(); + channel1.SendAsync(data, 0, data.Length); + + int received = 0; + + void PeerConnected(ITcpChannel channel) + { + channel.BytesReceived += Channel_BytesReceived; + channel.Disconnected += Disconnected; + channel.Start(); + } + + void Disconnected() + { + Console.WriteLine("DC"); + tcs.SetResult(false); + } + + void Channel_BytesReceived(byte[] buff, int offset, int count) + { + received = count; + tcs.SetResult(true); + } + + var ss = tcs.Task.Result; + Assert.IsTrue(ss); + + Assert.AreEqual(received, data.Length); + + } + + + + [TestMethod] + public void MessageTest() + { + DistributedLobbyClient distributedLobbyClient = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + DistributedLobbyClient distributedLobbyClient2 = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + + TaskCompletionSource tcs = new TaskCompletionSource(); + int received = 0; + + using var server = ArrangeServer(); + + var res = distributedLobbyClient.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = distributedLobbyClient2.ConnectAsync("127.0.0.1", 20010).Result; + + distributedLobbyClient2.MessageReceived += MsgRec; + MessageEnvelope msg = new MessageEnvelope(); + msg.Header = "Greetings"; + msg.Payload = new byte[12800000]; + msg.To = distributedLobbyClient2.SessionId; + distributedLobbyClient.SendAsyncMessage(msg); + + void MsgRec(NetworkLibrary.MessageEnvelope msg) + { + tcs.SetResult(true); + received = msg.PayloadCount; + } + + var ss = tcs.Task.Result; + Assert.IsTrue(ss); + + Assert.AreEqual(received, msg.Payload.Length); + + + } + + + private static DistributedLobbyClient GetClient() + { + return new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + } + + [TestMethod] + public void StatusPublishTest() + { + using var server = ArrangeServer(); + List> clients = new List>(); + HashSet ids = new HashSet(); + + List pending = new List(); + for (int i = 0; i < 20; i++) + { + var cl = GetClient(); + Task task = cl.ConnectAsync("127.0.0.1", 20010).ContinueWith(t => + { + clients.Add(cl); + ids.Add(cl.SessionId); + }); + task.ConfigureAwait(false); + + pending.Add(task); + + } + + Task.WhenAll(pending).Wait(); + pending.Clear(); + + Thread.Sleep(1500); + VerifyPeerList(clients, ids); + + for (int i = 0; i < 5; i++)//dc 5 + { + ids.Remove(clients[i].SessionId); + clients[i].Disconnect(); + clients.RemoveAt(i); + } + + Thread.Sleep(1500); + VerifyPeerList(clients, ids); + + for (int i = 0; i < 15; i++) //+ 15 + { + var cl = GetClient(); + Task task = cl.ConnectAsync("127.0.0.1", 20010).ContinueWith(t => + { + clients.Add(cl); + ids.Add(cl.SessionId); + }); + task.ConfigureAwait(false); + + pending.Add(task); + + } + + Task.WhenAll(pending).Wait(); + pending.Clear(); + + Thread.Sleep(1500); + VerifyPeerList(clients, ids); + + + for (int i = 0; i < 15; i++) //+- 15 + { + var cl = GetClient(); + var res = cl.ConnectAsync("127.0.0.1", 20010).Result; + clients.Add(cl); + ids.Add(cl.SessionId); + + ids.Remove(clients[i].SessionId); + clients[i].Disconnect(); + clients.RemoveAt(i); + + } + + Thread.Sleep(1500); + VerifyPeerList(clients, ids); + } + + private static void VerifyPeerList(List> clients, HashSet ids) + { + foreach (var client in clients) + { + var pl = client.GetPeerList(); + Assert.IsNotNull(pl); + Assert.IsTrue(pl.Count == clients.Count - 1); + + foreach (var peerKv in pl) + { + Assert.IsTrue(ids.Contains(peerKv.Key)); + Assert.IsTrue(peerKv.Key != client.SessionId); + } + } + } + + [TestMethod] + public void SendAndWait() + { + using var server = ArrangeServer(); + var cl1 = GetClient(); + var cl2 = GetClient(); + + var res = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + + + cl1.MessageReceived += Cl1_MessageReceived; + void Cl1_MessageReceived(MessageEnvelope obj) + { + obj.To = cl2.SessionId; + cl1.SendAsyncMessage(obj); + } + + var msg = new MessageEnvelope(); + msg.Header = "Hello"; + msg.To = cl1.SessionId; + var response = cl2.SendMessageAndWaitResponse(msg).Result; + + Assert.IsTrue (response.Header != MessageEnvelope.RequestTimeout); + } + + [TestMethod] + public void Timesync() + { + using var server = ArrangeServer(); + var cl1 = GetClient(); + Thread.Sleep(1337); + var cl2 = GetClient(); + + var res = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + double time1 =cl1.GetTime(); + double time2 = cl2.GetTime(); + Assert.IsTrue(Math.Abs(time1 - time2) < 1); + } + + } +} From ecc9f807e61d143d564050f12abac0e155deeb3e Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Thu, 3 Apr 2025 16:54:02 +0200 Subject: [PATCH 11/27] distributed p2p- UdpHolepunch , tests --- .../Components/Crypto/KeyDerivation/HKDF.cs | 4 +- .../Channels/ByteMessageChannel.cs | 2 +- .../DistributedP2P/Channels/RawTcpSocket.cs | 22 ++ .../DistributedP2P/Channels/RawUdpSocket.cs | 22 ++ .../SecureByteMessageChannel.cs} | 20 +- .../DistributedP2P/Client/ChannelInfo.cs | 16 +- .../Client/DistributedLobbyClient.cs | 148 +++++++++++--- .../DistributedP2P/Client/IChannel.cs | 11 + .../DistributedP2P/Client/ITcpChannel.cs | 16 -- .../StateManagement/ClientConnectionState.cs | 28 ++- .../Client/StateManagement/ClientPipeState.cs | 75 ++++++- .../ClientUdpHolepunchState.cs | 191 ++++++++++++++++++ .../Components/ConversationStateBase.cs | 15 +- .../DistributedP2P/Components/IPHelper.cs | 170 ++++++++++++++++ .../Components/ITimeProvider.cs | 6 +- .../Components/InternalConstants.cs | 9 + .../Components/PreciseTimeAwaiter.cs | 24 +++ .../DistributedP2P/Components/TimerService.cs | 4 + .../Server/DistributedLobbyServer.cs | 32 ++- .../Server/IDistributedConnection.cs | 1 + .../DistributedP2P/Server/ServerSession.cs | 12 +- .../DistributedP2P/Server/SessionManager.cs | 12 +- .../StateManagement/ServerConnectionState.cs | 52 +++-- .../Server/StateManagement/ServerPipeState.cs | 65 ++++-- .../ServerUdpHolepunchState.cs | 176 ++++++++++++++++ NetworkLibrary/TCP/AES/AesTcpClient.cs | 14 +- NetworkLibrary/UDP/AsyncUdpServerLitecs.cs | 1 + .../DistributedP2P/DistP2PServerclientTest.cs | 134 +++++++++++- 28 files changed, 1162 insertions(+), 120 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Channels/RawTcpSocket.cs create mode 100644 NetworkLibrary/DistributedP2P/Channels/RawUdpSocket.cs rename NetworkLibrary/DistributedP2P/{Client/SecureTcpChannel.cs => Channels/SecureByteMessageChannel.cs} (76%) create mode 100644 NetworkLibrary/DistributedP2P/Client/IChannel.cs delete mode 100644 NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/IPHelper.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs diff --git a/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs b/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs index 59814ba..382a1c2 100644 --- a/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs +++ b/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs @@ -8,7 +8,7 @@ namespace NetworkLibrary.Components.Crypto.KeyDerivation public class HKDFLite { // HKDF implementation (RFC 5869) - public static byte[] DeriveKey(byte[] ikm, byte[] salt = null, byte[] info = null, int outputLength = 32) + public static byte[] DeriveKey(byte[] sharedSecret, byte[] salt = null, byte[] info = null, int outputLength = 32) { if (salt == null) { @@ -20,7 +20,7 @@ public static byte[] DeriveKey(byte[] ikm, byte[] salt = null, byte[] info = nul info = Encoding.UTF8.GetBytes("AES-GCM-Info"); } - byte[] prk = HkdfExtract(salt, ikm); + byte[] prk = HkdfExtract(salt, sharedSecret); return HkdfExpand(prk, info, outputLength); } diff --git a/NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs index 73edd34..9dd1f7e 100644 --- a/NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs @@ -7,7 +7,7 @@ namespace NetworkLibrary.DistributedP2P.Channels { - public class ByteMessageChannel : ITcpChannel + public class ByteMessageChannel : IChannel { public ChannelInfo Info { get; private set; } diff --git a/NetworkLibrary/DistributedP2P/Channels/RawTcpSocket.cs b/NetworkLibrary/DistributedP2P/Channels/RawTcpSocket.cs new file mode 100644 index 0000000..628731b --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/RawTcpSocket.cs @@ -0,0 +1,22 @@ +using NetworkLibrary.DistributedP2P.Client; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Net.Sockets; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Channels +{ + public class RawTcpSocket : IChannel + { + public Socket socket; + public RawTcpSocket(ChannelInfo info, Socket socket) + { + this.socket = socket; + Info = info; + } + + public ChannelInfo Info { get; } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Channels/RawUdpSocket.cs b/NetworkLibrary/DistributedP2P/Channels/RawUdpSocket.cs new file mode 100644 index 0000000..b535c3d --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/RawUdpSocket.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using System.Text; +using NetworkLibrary.DistributedP2P.Client; + + +namespace NetworkLibrary.DistributedP2P.Channels +{ + public class RawUdpSocket:IChannel + { + public Socket socket; + public RawUdpSocket(ChannelInfo info,Socket socket ) + { + this.socket = socket; + Info = info; + } + + public ChannelInfo Info { get; } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureByteMessageChannel.cs similarity index 76% rename from NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs rename to NetworkLibrary/DistributedP2P/Channels/SecureByteMessageChannel.cs index 9a51a25..d7e1bc6 100644 --- a/NetworkLibrary/DistributedP2P/Client/SecureTcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureByteMessageChannel.cs @@ -2,11 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.TCP.AES; -namespace NetworkLibrary.DistributedP2P.Client +namespace NetworkLibrary.DistributedP2P.Channels { - public class SecureTcpChannel : ITcpChannel + public class SecureByteMessageChannel : IChannel { AesTcpClient client; AesTcpServer server; @@ -15,23 +16,23 @@ public class SecureTcpChannel : ITcpChannel public ChannelInfo Info { get; private set; } - public event Action BytesReceived; + public event Action BytesReceived; public event Action Disconnected; - public SecureTcpChannel(AesTcpClient client, ChannelInfo info) + public SecureByteMessageChannel(AesTcpClient client, ChannelInfo info) { this.client = client; - this.Info = info; + Info = info; clientMode = true; client.OnBytesReceived += ClientBytesReceived; client.OnDisconnected += ClientDisconnected; } - public SecureTcpChannel(AesTcpServer server, ChannelInfo info) + public SecureByteMessageChannel(AesTcpServer server, ChannelInfo info) { this.server = server; - this.Info = info; + Info = info; clientId = server.Sessions.First().Key; server.OnBytesReceived += ServerBytesReceived; @@ -72,7 +73,10 @@ private void ClientDisconnected() public void Start() { - throw new NotImplementedException(); + if (clientMode) + { + client.Start(); + } } } } diff --git a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs index df46ec6..c438c69 100644 --- a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs +++ b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs @@ -5,10 +5,22 @@ namespace NetworkLibrary.DistributedP2P.Client { + public enum ChannelType + { + RawTcp,RawUdp,ByteMessage,SecureByteMessage + } public class ChannelInfo { - public AesMode AesMode { get; } - public string ChannelName { get; } + public ChannelType ChannelType { get; internal set; } + + public string ChannelName { get; internal set; } + + internal bool RequiresKeyExchange() + { + if(ChannelType == ChannelType.SecureByteMessage) + return true; + return false; + } } } diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 229699c..27c51ec 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -1,4 +1,5 @@ -using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.Components.Crypto; +using NetworkLibrary.Components.Crypto.DiffieHellman; using NetworkLibrary.Components.Crypto.KeyDerivation; using NetworkLibrary.DistributedP2P.Channels; using NetworkLibrary.DistributedP2P.Client.StateManagement; @@ -6,6 +7,7 @@ using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.MessageProtocol; using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.TCP.AES; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -26,7 +28,7 @@ namespace NetworkLibrary.DistributedP2P.Client private ConcurrentDictionary onlinePeers = new ConcurrentDictionary(); - public event Action PeerConnected; + public event Action PeerConnected; public event Action PeerOnline; public event Action PeerOffline; @@ -51,6 +53,7 @@ public DistributedLobbyClient(IClientDbConnection clientDbConnector, sslClient = new SecureMessageClient(certificate); sslClient.OnMessageReceived += HandleServerMsg; sslClient.OnDisconnected += HandleDisconnected; + timeSync = new TimeSync(this); } @@ -76,8 +79,6 @@ public async Task ConnectAsync(string ip, int port) { SessionId = conState.SessionId; IsConnected = true; - timeSync = new TimeSync(this); - await timeSync.SyncTime(); timeSync.StartAutoTimeSync(5000); return true; } @@ -88,6 +89,7 @@ public async Task ConnectAsync(string ip, int port) else return false; } + #region Send public void SendAsyncMessage(MessageEnvelope message) { sslClient.SendAsyncMessage(message); @@ -108,11 +110,18 @@ public Task SendMessageAndWaitResponse(Guid a, MessageEnvelope msg.To = a; return sslClient.SendMessageAndWaitResponse(msg); } - public async Task OpenTcpSocket(Guid destinationPeer, ChannelInfo Info) + #endregion + + // we dont need this socket methods afeterall + public async Task OpenTcpSocket(Guid destinationPeer,string socketName) { - var pipeState = new ClientPipeState(Guid.NewGuid(), this); + var Info = new ChannelInfo(); + Info.ChannelName = socketName; + Info.ChannelType = ChannelType.RawTcp; + + var pipeState = new ClientPipeState(Guid.NewGuid(), this, Info); stateManager.RegisterState(pipeState); - pipeState.Start(destinationPeer); + pipeState.Start(destinationPeer, tcp: true); await pipeState.WaitCompletion(); @@ -124,30 +133,39 @@ public async Task OpenTcpSocket(Guid destinationPeer, ChannelInfo Info) return null; } - - public async Task OpenTcpChannel(Guid destinationPeer, ChannelInfo Info) + public async Task OpenUdpSocket(Guid destinationPeer, string socketName) { - var pipeState = new ClientPipeState(Guid.NewGuid(), this); + var Info = new ChannelInfo(); + Info.ChannelName = socketName; + Info.ChannelType = ChannelType.RawUdp; + + var pipeState = new ClientPipeState(Guid.NewGuid(), this,Info); stateManager.RegisterState(pipeState); - pipeState.Start(destinationPeer); + pipeState.Start(destinationPeer, tcp:false); await pipeState.WaitCompletion(); if (pipeState.IsSuccesful) { - var info = new ChannelInfo(); - var channel = new ByteMessageChannel(info, pipeState.ConnectedSocket); - return channel; + Console.WriteLine("PipeSuccesfull"); + return pipeState.ConnectedSocket; + } + return null; + } - Console.WriteLine("PipeSuccesfull"); - return null; - var symetricKey = await PerformDHWithPeer(destinationPeer); - if (symetricKey != null) - { - //var channel = new SecureTcpChannel(Info, pipeState.ConnectedSocket, symetricKey); - return null; - } + public async Task OpenTcpChannel(Guid destinationPeer, ChannelInfo Info) + { + var pipeState = new ClientPipeState(Guid.NewGuid(), this, Info); + stateManager.RegisterState(pipeState); + pipeState.Start(destinationPeer,tcp: true); + + await pipeState.WaitCompletion(); + + if (pipeState.IsSuccesful) + { + IChannel channel = CreateChannel(pipeState); + return channel; } return null; } @@ -157,16 +175,41 @@ private void HandlePipeCreated(IConversationState state) if (state.IsSuccesful) { var pipeState = (ClientPipeState)state; + IChannel channel = CreateChannel(pipeState); - var info = new ChannelInfo(); - var channel = new ByteMessageChannel(info, pipeState.ConnectedSocket); - PeerConnected?.Invoke(channel); + if (channel != null) + PeerConnected?.Invoke(channel); Console.WriteLine("DestPeer Conn Succesfull"); // notify that a connection is opened, like socket accept } } + private static IChannel CreateChannel(ClientPipeState pipeState) + { + IChannel channel = null; + switch (pipeState.ChannelInfo.ChannelType) + { + case ChannelType.RawTcp: + channel = new RawTcpSocket(pipeState.ChannelInfo, pipeState.ConnectedSocket); + break; + case ChannelType.RawUdp: + channel = new RawUdpSocket(pipeState.ChannelInfo, pipeState.ConnectedSocket); + break; + case ChannelType.ByteMessage: + channel = new ByteMessageChannel(pipeState.ChannelInfo, pipeState.ConnectedSocket); + break; + case ChannelType.SecureByteMessage: + var symetricKey = HKDFLite.DeriveKey(pipeState.sharedSecret,outputLength:16); + var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey,AesMode.GCM); + AesTcpClient client = new AesTcpClient(algo,pipeState.ConnectedSocket); + channel = new SecureByteMessageChannel(client, pipeState.ChannelInfo); + break; + } + + return channel; + } + private async Task PerformDHWithPeer(Guid destinationPeer) { @@ -201,14 +244,16 @@ private void HandleServerMsg(MessageEnvelope envelope) switch (envelope.Header) { - case InternalConstants.PipeTokenDeliveryTcp: + case InternalConstants.PipeRequestTcp: + case InternalConstants.PipeRequestUdp: - var pipeState = new ClientPipeState(envelope.MessageId, this); + var pipeState = new ClientPipeState(envelope, this); pipeState.OnComplete += HandlePipeCreated; stateManager.RegisterState(pipeState); pipeState.HandleMessage(envelope); break; + case InternalConstants.PublishPeerList: var buffer = envelope.Payload; @@ -219,6 +264,10 @@ private void HandleServerMsg(MessageEnvelope envelope) break; + case InternalConstants.RequestHolepunchUdp: + ManageUdpHolepunchRequest(envelope); + break; + } } else @@ -229,6 +278,7 @@ private void HandleServerMsg(MessageEnvelope envelope) } + private void PublishPeerStatusEvents(PeerStatusList statusList) { foreach (var offlineKV in statusList.WentOffline) @@ -259,8 +309,41 @@ public Dictionary GetPeerList() return copy; } - private int disposed = 0; + public async Task TryUdpHolePunch(Guid destination) + { + var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this); + stateManager.RegisterState(state); + state.Start(); + + await state.WaitCompletion(); + + //if (state.IsSuccesful) + //{ + // state.Socket.SendTo( new byte[689], state.SuccesfulEndpoint); + //} + return state.IsSuccesful; + } + + private void ManageUdpHolepunchRequest(MessageEnvelope envelope) + { + var state = new ClientUdpHolepunchState(envelope.MessageId, envelope.From, this); + stateManager.RegisterState(state); + state.OnComplete += State_OnComplete; + state.HandleMessage(envelope); + + void State_OnComplete(IConversationState obj) + { + if (state.IsSuccesful) + { + //byte[] buff = new byte[1024]; + //int rec = state.Socket.Receive(buff); + //Console.WriteLine("YIPPIE"); + } + } + + } + public double GetTime() { @@ -271,6 +354,10 @@ public DateTime GetDateTime() { return timeSync.GetDateTime(); } + public Task SyncTime() + { + return timeSync.SyncTime(); + } public void Disconnect() { @@ -285,9 +372,6 @@ private void HandleDisconnected() Disconnected?.Invoke(); } - DateTime ITimeProvider.GetTime() - { - return timeSync.GetDateTime(); - } + } } diff --git a/NetworkLibrary/DistributedP2P/Client/IChannel.cs b/NetworkLibrary/DistributedP2P/Client/IChannel.cs new file mode 100644 index 0000000..24e3ea1 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/IChannel.cs @@ -0,0 +1,11 @@ +using System; + +namespace NetworkLibrary.DistributedP2P.Client +{ + + public interface IChannel + { + ChannelInfo Info { get; } + + } +} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs b/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs deleted file mode 100644 index 439713c..0000000 --- a/NetworkLibrary/DistributedP2P/Client/ITcpChannel.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace NetworkLibrary.DistributedP2P.Client -{ - - public interface ITcpChannel - { - ChannelInfo Info { get; } - - event Action BytesReceived; - event Action Disconnected; - void SendAsync(byte[] buffer, int offset, int count); - - void Start(); - } -} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs index e746d2c..819eb8f 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs @@ -1,5 +1,7 @@ using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Server; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.P2P.Generic; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -18,7 +20,7 @@ internal class ClientConnectionState : ConversationStateBase public Guid SessionId { get; private set; } - public ClientConnectionState(Guid stateId, IDistributedConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken):base(stateId) + public ClientConnectionState(Guid stateId, IDistributedConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken):base(stateId,20000) { this.connection = connection; this.clientDbConnector = clientDbConnector; @@ -29,7 +31,9 @@ public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - + case InternalConstants.SyncTime: + SyncTime(message); + break; case InternalConstants.ConnectionGetClientPublicData: SendClientPublicData(message); break; @@ -39,9 +43,22 @@ public override void HandleMessage(MessageEnvelope message) case InternalConstants.Error: HandleConnectionFail(message); break; + + } } + private void SyncTime(MessageEnvelope message) + { + var task = connection.SyncTime().ContinueWith(t => + { + MessageEnvelope msg = CreateEnvelope(); + msg.Header = InternalConstants.SyncTime; + connection.SendAsyncMessage(msg); + }); + + } + public void Start() { MessageEnvelope msg = CreateEnvelope(); @@ -52,6 +69,13 @@ public void Start() msg.KeyValuePairs.Add("AuthMethod", authToken.AuthenticationMethod); msg.KeyValuePairs.Add("AdditionalData", authToken.AdditionalData); + List locals = IPHelper.GetLocalIPAddresses4(); + int i = 0; + foreach (var ip in locals) + { + msg.KeyValuePairs[ip] = null; + } + connection.SendAsyncMessage(msg); // now server will authenticate after this // may ask additional data to link, if we are first timer diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index 1c52b1f..84db409 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -7,6 +7,7 @@ using System.Net.Sockets; using System.Text; using System.Threading.Tasks; +using NetworkLibrary.Components.Crypto.DiffieHellman; namespace NetworkLibrary.DistributedP2P.Client.StateManagement { @@ -17,18 +18,40 @@ internal class ClientPipeState : ConversationStateBase public Socket ConnectedSocket { get; private set; } public EndpointData SuccesfullEndpoint { get; private set; } + public byte[] sharedSecret { get; private set; } - public ClientPipeState(Guid stateId, IDistributedConnection connection) : base(stateId) + public ChannelInfo ChannelInfo; + private DiffieHellman df; + public ClientPipeState(Guid stateId, IDistributedConnection connection,ChannelInfo info) : base(stateId, 20000) { this.connection = connection; + this.ChannelInfo = info; } - public void Start(Guid destinationPeer) + public ClientPipeState(MessageEnvelope message,IDistributedConnection connection) : base(message.MessageId) + { + this.connection = connection; + + } + + public void Start(Guid destinationPeer, bool tcp) { this.destinationPeer = destinationPeer; + var msg = CreateEnvelope(); - msg.Header = InternalConstants.PipeRequestTcp; + msg.Header = tcp?InternalConstants.PipeRequestTcp:InternalConstants.PipeRequestUdp; msg.To = destinationPeer; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); + msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; + + if (ChannelInfo.RequiresKeyExchange()) + { + df = new DiffieHellman(); + byte[] key = df.GetPublicKey(); + string keyStr = Convert.ToBase64String(key); + msg.KeyValuePairs["DH"] = keyStr; + } connection.SendAsyncMessage(msg); } @@ -37,6 +60,12 @@ public override void HandleMessage(MessageEnvelope message) { switch(message.Header) { + // the listener + case InternalConstants.PipeRequestTcp: + case InternalConstants.PipeRequestUdp: + HandleConnectionRequest(message); + break; + case InternalConstants.PipeTokenDeliveryTcp: HandlePipeTokenTcp(message); break; @@ -55,6 +84,27 @@ public override void HandleMessage(MessageEnvelope message) } } + private void HandleConnectionRequest(MessageEnvelope message) + { + ChannelInfo = new ChannelInfo(); + ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); + ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PipeReqAck; + + if (ChannelInfo.RequiresKeyExchange()) + { + df = new DiffieHellman(); + byte[] key = df.GetPublicKey(); + string keyStr = Convert.ToBase64String(key); + + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["DH"] = keyStr; + } + + connection.SendAsyncMessage(msg); + } private async void HandlePipeTokenTcp(MessageEnvelope message) { @@ -63,6 +113,9 @@ private async void HandlePipeTokenTcp(MessageEnvelope message) int off = message.PayloadOffset; var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); + if (ChannelInfo.RequiresKeyExchange()) + GenerateSharedSecret(message.KeyValuePairs["DH"]); + foreach (EndpointData endpoint in pipeData.PipeEndpoints) { Socket connected = await TryConnectWithTimeout(endpoint); @@ -94,7 +147,10 @@ private async void HandlePipeTokenTcp(MessageEnvelope message) } - + private void GenerateSharedSecret(string otherPublic) + { + sharedSecret = df.CalculateSharedSecret(Convert.FromBase64String(otherPublic)); + } private async Task TryConnectWithTimeout(EndpointData endpoint, int timeout = 500) { @@ -192,6 +248,10 @@ private async Task TokenExchange(Socket connectedSocket, byte[] token, int private async void HandlePipeTokenUdp(MessageEnvelope message) { + + if (ChannelInfo.RequiresKeyExchange()) + GenerateSharedSecret(message.KeyValuePairs["DH"]); + int off = message.PayloadOffset; var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); @@ -200,7 +260,7 @@ private async void HandlePipeTokenUdp(MessageEnvelope message) foreach (EndpointData endpoint in pipeData.PipeEndpoints) { - bool success = await TokenExchange(connected, pipeData.Token); + bool success = await UdpTokenExchange(connected, pipeData.Token, endpoint.ToIpEndpoint()); if (success) { OnConnectionSuccessful(endpoint, connected); @@ -250,10 +310,7 @@ private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndP { return false; } - finally - { - try { udpSocket.Close(); } catch { } - } + } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs new file mode 100644 index 0000000..db26b57 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -0,0 +1,191 @@ +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Server; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.UDP; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Client.StateManagement +{ + internal class ClientUdpHolepunchState : ConversationStateBase + { + private readonly Guid destId; + private readonly IDistributedConnection connection; + public Socket Socket; + + public IPEndPoint SuccesfulEndpoint { get; private set; } + + public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection) : base(stateId, 20000) + { + this.destId = destId; + this.connection = connection; + } + + //the initiator + public void Start() + { + int port = StartUdpSocket(); + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.RequestHolepunchUdp; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs[port.ToString()] = null; + msg.To = destId; + + connection.SendAsyncMessage(msg); + } + + + + public override void HandleMessage(MessageEnvelope message) + { + switch (message.Header) + { + case InternalConstants.RequestHolepunchUdp: + HandleRemoteHpRequest(message); + break; + + case InternalConstants.StartHPUdp: + message.LockBytes(); + ThreadPool.UnsafeQueueUserWorkItem((s) => StartHolepunchRoutine(message), null); + break; + + case InternalConstants.PunchSuccesAck: + HandleSucces(); + break; + case InternalConstants.PunchFailAck: + HandleFailure(); + break; + } + } + + + + + + // the destination of hp + private void HandleRemoteHpRequest(MessageEnvelope message) + { + int port = StartUdpSocket(); + var msg = CreateEnvelope(); + msg.Header = InternalConstants.AckRequestHolepunchUdp; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs[port.ToString()] = null; + msg.To = destId; + + connection.SendAsyncMessage(msg); + } + + + private void StartHolepunchRoutine(MessageEnvelope message) + { + var epMsg = KnownTypeSerializer.DeserializeEndpointTransferMessage(message.Payload, message.PayloadOffset); + var time = double.Parse(message.KeyValuePairs["Time"]); + + foreach (EndpointData localEp in epMsg.LocalEndpoints) + { + for (int i = 0; i < 2; i++) + { + TryPunch(localEp); + PreciseTimeAwaiter.Wait(20); + } + } + + EndpointData publicEp = new EndpointData() { Ip = epMsg.IpRemote, Port = epMsg.PortRemote }; + + var now = connection.GetTime(); + PreciseTimeAwaiter.Wait(time - now); + + for (int i = 0; i < 4; i++) + { + TryPunch(publicEp); + PreciseTimeAwaiter.Wait(20 * i); + } + + } + + private void TryPunch(EndpointData localEp) + { + Socket.SendTo(new byte[1] { 0xFF }, SocketFlags.None, localEp.ToIpEndpoint()); + } + + private int StartUdpSocket() + { + Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + Socket.Bind(new IPEndPoint(IPAddress.Any, 0)); + + ReceiveOnceAsync().ContinueWith(Received); + + return ((IPEndPoint)Socket.LocalEndPoint).Port; + + } + + private async Task ReceiveOnceAsync() + { + var buffer = BufferPool.RentBuffer(64000); + var remoteEP = (EndPoint)new IPEndPoint(IPAddress.Any, 0); + + try + { + var receiveTask = Socket.ReceiveFromAsync(new ArraySegment(buffer), SocketFlags.None, remoteEP); + + var completedTask = await Task.WhenAny(receiveTask, Task.Delay(5000)); + if (completedTask == receiveTask) + { + var result = await receiveTask; + return ((IPEndPoint)result.RemoteEndPoint); + } + else + { + TimedOut(); + return null; + } + } + catch + { + Cancel(); + return null; + } + finally + { + BufferPool.ReturnBuffer(buffer); + } + } + + private void Received(Task task) + { + SuccesfulEndpoint = task.Result; + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSucces; + connection.SendAsyncMessage(msg); + } + + private void TimedOut() + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchFail; + connection.SendAsyncMessage(msg); + } + + private void HandleFailure() + { + Completed(false); + } + + private void HandleSucces() + { + Completed(true); + } + + + + + } + + +} diff --git a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs index 645b2ac..5e5b4df 100644 --- a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs +++ b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs @@ -20,10 +20,23 @@ internal abstract class ConversationStateBase:IConversationState private TaskCompletionSource Completion; private int isComplete = 0; - public ConversationStateBase(Guid stateId) + public ConversationStateBase(Guid stateId, int timeout = -1) { this.StateId = stateId; Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + if (timeout > 0) + { + TimerService.RegisterTimer(stateId, timeout, OnTimeOut); + } + } + + private void OnTimeOut() + { + if (!IsCompleted()) + { + Cancel(); + } } public abstract void HandleMessage(MessageEnvelope message); diff --git a/NetworkLibrary/DistributedP2P/Components/IPHelper.cs b/NetworkLibrary/DistributedP2P/Components/IPHelper.cs new file mode 100644 index 0000000..76c19c2 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/IPHelper.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class IPHelper + { + public static List GetLocalIPAddresses4() + { + List ipAddresses = new List(); + NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); + foreach (NetworkInterface ni in interfaces) + { + if (ni.OperationalStatus != OperationalStatus.Up || + ni.NetworkInterfaceType == NetworkInterfaceType.Loopback || + ni.Description.Contains("VMware") || + ni.Description.Contains("Hyper-V") || + ni.Description.Contains("VirtualBox") || + ni.Name.Contains("vEthernet")) + continue; + + IPInterfaceProperties ipProps = ni.GetIPProperties(); + foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses) + { + if (addr.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + { + ipAddresses.Add(addr.Address.ToString()); + } + } + } + return ipAddresses; + } + + // based on reserved private networks by RFC 1918 + public static bool IsPrivateIPAddress(IPEndPoint endpont) + { + IPAddress address = endpont.Address; + byte[] bytes = address.GetAddressBytes(); + + + // 10.0.0.0 - 10.255.255.255 (10/8 prefix) + if (bytes[0] == 10) + return true; + + // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) + if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) + return true; + + // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) + if (bytes[0] == 192 && bytes[1] == 168) + return true; + + // 169.254.0.0 - 169.254.255.255 (169.254/16 prefix, link-local) + if (bytes[0] == 169 && bytes[1] == 254) + return true; + + // 127.0.0.0 - 127.255.255.255 (127/8 prefix, loopback) + if (bytes[0] == 127) + return true; + + return false; + } + + // based on reserved private networks by RFC 1918 + public static bool IsPrivateIPAddress(string ipAddress) + { + if (!IPAddress.TryParse(ipAddress, out IPAddress address)) + return false; + + if (address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) + return false; + + byte[] bytes = address.GetAddressBytes(); + + + // 10.0.0.0 - 10.255.255.255 (10/8 prefix) + if (bytes[0] == 10) + return true; + + // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) + if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) + return true; + + // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) + if (bytes[0] == 192 && bytes[1] == 168) + return true; + + // 169.254.0.0 - 169.254.255.255 (169.254/16 prefix, link-local) + if (bytes[0] == 169 && bytes[1] == 254) + return true; + + // 127.0.0.0 - 127.255.255.255 (127/8 prefix, loopback) + if (bytes[0] == 127) + return true; + + return false; + } + + public static void ExtractLocalIpsWithMatchingSubnet(List fromLocals, List ToLocals, out List toFrom, out List toTo) + { + // Get all possible subnet pairs between the two lists + var subnetPairs = from a in fromLocals + from b in ToLocals + let subnetA = GetSubnet(a) + let subnetB = GetSubnet(b) + where subnetA == subnetB + select new { a, b }; + + // Group by the matching subnets to maintain order + var matchedSubnets = subnetPairs + .GroupBy(pair => GetSubnet(pair.a)) + .OrderByDescending(g => g.Key) // Just to have consistent ordering + .SelectMany(g => g.Select(p => p)); + + // First add all IPs with matching subnets (ordered by subnet matches) + var orderedToFrom = new List(); + var orderedToTo = new List(); + + foreach (var pair in matchedSubnets) + { + if (!orderedToTo.Contains(pair.a)) + { + orderedToTo.Add(pair.a); + } + if (!orderedToFrom.Contains(pair.b)) + { + orderedToFrom.Add(pair.b); + } + } + + // Then add remaining IPs that didn't have matches + foreach (var ip in ToLocals) + { + if (!orderedToFrom.Contains(ip)) + { + orderedToFrom.Add(ip); + } + } + + foreach (var ip in fromLocals) + { + if (!orderedToTo.Contains(ip)) + { + orderedToTo.Add(ip); + } + } + + toFrom = orderedToFrom; + toTo = orderedToTo; + } + + // Helper method to get the first two octets (subnet /16) + private static string GetSubnet(string ip) + { + var parts = ip.Split('.'); + if (parts.Length >= 2) + { + return $"{parts[0]}.{parts[1]}"; + } + return ip; // fallback for invalid IPs (though shouldn't happen with valid IPs) + } + + + + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/ITimeProvider.cs b/NetworkLibrary/DistributedP2P/Components/ITimeProvider.cs index 852c311..d341002 100644 --- a/NetworkLibrary/DistributedP2P/Components/ITimeProvider.cs +++ b/NetworkLibrary/DistributedP2P/Components/ITimeProvider.cs @@ -1,9 +1,13 @@ using System; +using System.Threading.Tasks; namespace NetworkLibrary.DistributedP2P.Components { public interface ITimeProvider { - DateTime GetTime(); + DateTime GetDateTime(); + double GetTime(); + + Task SyncTime(); } } \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs index d7a2cc3..e54b1dd 100644 --- a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs +++ b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs @@ -15,8 +15,17 @@ internal class InternalConstants public const string ConnectionAckClientPublicData = "6"; public const string PipeRequestTcp = "7"; public const string PipeRequestUdp = "8"; + public const string PipeReqAck = "d"; public const string PipeTokenDeliveryTcp = "9"; public const string PipeTokenDeliveryUdp = "a"; public const string PublishPeerList = "b"; + public const string SyncTime = "c"; + public const string RequestHolepunchUdp = "d"; + public const string AckRequestHolepunchUdp = "e"; + public const string StartHPUdp = "f"; + public const string PunchSucces = "g"; + public const string PunchFail = "h"; + public const string PunchSuccesAck = "i"; + public const string PunchFailAck = "j"; } } diff --git a/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs b/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs new file mode 100644 index 0000000..4c018ef --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class PreciseTimeAwaiter + { + static Stopwatch sw = Stopwatch.StartNew(); + public static void Wait(double miliseconds) + { + double time = sw.Elapsed.TotalMilliseconds; + double until = time+miliseconds; + + while (until > sw.Elapsed.TotalMilliseconds) + { + Thread.SpinWait(10); + } + + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/TimerService.cs b/NetworkLibrary/DistributedP2P/Components/TimerService.cs index ae8bd1f..e43635c 100644 --- a/NetworkLibrary/DistributedP2P/Components/TimerService.cs +++ b/NetworkLibrary/DistributedP2P/Components/TimerService.cs @@ -29,5 +29,9 @@ public static void CancelTimeout(Guid guid) } + internal static void RegisterTimer(Guid stateId, object onTimeOut) + { + throw new NotImplementedException(); + } } } diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 34bb95d..907c7e1 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -52,7 +52,7 @@ public class ServerParameters IAuthenticator authenticator; IServerDbConnector dbConnector; - SessionManager sessionManager; + SessionManager sessionManager; Components.StateManager stateManager = new Components.StateManager(); PipeManager piper; Stopwatch serverClock = new Stopwatch(); @@ -90,7 +90,7 @@ public void StartServer() sslServer.OnMessageReceived += SslMessageReceived; sslServer.StartServer(); - sessionManager = new SessionManager(this); + sessionManager = new SessionManager(this); sessionManager.PeerListPublish += PublishPeerList; } @@ -117,6 +117,7 @@ private void SslClientAccepted(Guid ephemeralClientId) private void HandleConnRequest(MessageEnvelope msg) { + Guid stateId = msg.MessageId; var state = new ServerConnectionState(stateId, msg.From, this, authenticator, dbConnector); stateManager.RegisterState(state); @@ -131,7 +132,7 @@ private void ConnectionStateComplete(IConversationState state_) if (state.IsSuccesful) { var sessionEp = sslServer.GetSessionEndpoint(state.EphemeralClientId); - var statusList = sessionManager.CreateSession(state.clientDbInfo, state.EphemeralClientId, sessionEp); + var statusList = sessionManager.CreateSession(state.clientDbInfo, state.EphemeralClientId, sessionEp, state.clientLocalIps); if(statusList!=null) PublishPeerList(new List() { statusList }); } @@ -197,6 +198,21 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) break; + + case InternalConstants.PipeRequestUdp: + + var pipeState1 = new ServerPipeState(message.MessageId, this, pipeManager); + stateManager.RegisterState(pipeState1); + pipeState1.HandleMessage(message); + + break; + + case InternalConstants.RequestHolepunchUdp: + var state = new ServerUdpHolepunchState(message.MessageId, this, sessionManager); + stateManager.RegisterState(state); + state.HandleMessage(message); + break; + case Constants.TimeSync: byte[] time = new byte[8]; @@ -239,12 +255,20 @@ public void ShutDownServer() udpServer.Dispose(); } - public DateTime GetTime() + public DateTime GetDateTime() { // will be distributed time return DateTime.UtcNow; } + public double GetTime() + { + return 0; + } + public Task SyncTime() + { + throw new NotImplementedException(); + } public void Dispose() { sslServer.ShutdownServer(); diff --git a/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs b/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs index 6af5b34..680ed23 100644 --- a/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs +++ b/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs @@ -8,6 +8,7 @@ namespace NetworkLibrary.DistributedP2P.Server { internal interface IDistributedConnection : ITimeProvider { + void SendAsyncMessage(MessageEnvelope msgs); void SendAsyncMessage(Guid a, MessageEnvelope msgs); Task SendMessageAndWaitResponse(Guid a, MessageEnvelope msg); diff --git a/NetworkLibrary/DistributedP2P/Server/ServerSession.cs b/NetworkLibrary/DistributedP2P/Server/ServerSession.cs index 489816c..5f0d618 100644 --- a/NetworkLibrary/DistributedP2P/Server/ServerSession.cs +++ b/NetworkLibrary/DistributedP2P/Server/ServerSession.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Net; using System.Text; using static System.Collections.Specialized.BitVector32; @@ -31,17 +32,22 @@ internal class ServerSession public IClientDbInfo ClientInfo { get; } public Guid PeerId { get; internal set; } public Guid EphemeralId { get; internal set; } + public IPEndPoint ClientPublicIp { get; } + public List ClientLocalIps { get; } public DateTime OnlineSince { get; internal set; } private PeerStatusList statusList = new PeerStatusList(); + private ConcurrentDictionary onlinePeers = new ConcurrentDictionary(); + private PeerStatus status = new PeerStatus(); - ConcurrentDictionary onlinePeers = new ConcurrentDictionary(); - PeerStatus status = new PeerStatus(); - public ServerSession(IClientDbInfo clientInfo,Guid ephemeralId) + + public ServerSession(IClientDbInfo clientInfo,Guid ephemeralId, System.Net.IPEndPoint clientPublicIp, List clientLocalIps) { ClientInfo = clientInfo; EphemeralId = ephemeralId; + ClientPublicIp = clientPublicIp; + ClientLocalIps = clientLocalIps; PeerId = clientInfo.ClientId; statusList.WhoNeedsToKnow = EphemeralId; OnlineSince = DateTime.UtcNow; diff --git a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs index fff4712..707ef53 100644 --- a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs +++ b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs @@ -22,7 +22,7 @@ namespace NetworkLibrary.DistributedP2P.Server - internal class SessionManager where S : ISerializer, new() + internal class SessionManager { internal ConcurrentDictionary serverSessions = new ConcurrentDictionary(); @@ -50,9 +50,9 @@ internal bool HandleMessage(Guid from, MessageEnvelope envelope) return true; } - public PeerStatusList CreateSession(IClientDbInfo clientInfo, Guid ephemeralClientId, IPEndPoint sessionEp) + public PeerStatusList CreateSession(IClientDbInfo clientInfo, Guid ephemeralClientId, IPEndPoint clientPublicIp, List clientLocalIps) { - var newSession = new ServerSession(clientInfo, ephemeralClientId); + var newSession = new ServerSession(clientInfo, ephemeralClientId, clientPublicIp, clientLocalIps); lock (publishMutex) { if (!serverSessions.ContainsKey(ephemeralClientId)) @@ -99,6 +99,12 @@ internal bool IsSessionActive(Guid ephemeralClientId) } + internal bool GetSessionData(Guid guid, out ServerSession sesData) + { + return serverSessions.TryGetValue(guid, out sesData); + + } + internal async void PublishRoutine() { List PubList = new List(); diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs index 0f35809..2a54584 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs @@ -1,6 +1,7 @@ using NetworkLibrary.DistributedP2P.Components; using System; using System.Collections.Generic; +using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -18,7 +19,9 @@ internal class ServerConnectionState : ConversationStateBase private readonly IServerDbConnector dbConnector; private IAuthenticationResult tokenResult; - public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection connection, IAuthenticator authenticator, IServerDbConnector dbConnector):base(stateId) + public List clientLocalIps; + + public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection connection, IAuthenticator authenticator, IServerDbConnector dbConnector):base(stateId,20000) { this.EphemeralClientId = clientId; this.connection = connection; @@ -26,13 +29,7 @@ public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection this.dbConnector = dbConnector; } - //internal void Start() - //{ - // var msg = CreateEnvelope(); - // msg.Header = InternalConstants.ConnectionStart; - // connection.SendAsyncMessage(EphemeralClientId, msg); - //} - + public override void HandleMessage(MessageEnvelope message) { if (IsCompleted()) @@ -43,13 +40,20 @@ public override void HandleMessage(MessageEnvelope message) HandleInitialConnectionRequest(message); break; + case InternalConstants.SyncTime: + HandleTimeSyncComplete(message); + break; + case InternalConstants.ConnectionAckClientPublicData: HandleClientPublicData(message); break; } } - + private void HandleTimeSyncComplete(MessageEnvelope message) + { + SendGood(); + } private void HandleInitialConnectionRequest(MessageEnvelope message) { @@ -68,6 +72,17 @@ private void HandleInitialConnectionRequest(MessageEnvelope message) ReplyError("No authentication method provided"); return; } + message.KeyValuePairs.Remove("AuthToken"); + message.KeyValuePairs.Remove("AuthMethod"); + if(message.KeyValuePairs.ContainsKey("AdditionalData")) + message.KeyValuePairs.Remove("AdditionalData"); + + clientLocalIps = new List(); + + foreach (var kv in message.KeyValuePairs) + { + clientLocalIps.Add(kv.Key); + } authenticator.Authenticate(token, method, additionalData).ContinueWith(HandleAuthentication); } @@ -102,7 +117,7 @@ private void HandleClientData(IClientDbInfo dbResult, IAuthenticationResult toke if (dbResult.IsValid) { clientDbInfo = dbResult; - ReplyGood(); + SyncTime(); } else { @@ -133,7 +148,7 @@ private void HandleDbRegistration(Task task) if (dbResult.IsValid) { clientDbInfo = dbResult; - ReplyGood(); + SyncTime(); } else { @@ -141,7 +156,19 @@ private void HandleDbRegistration(Task task) } } - private void ReplyGood() + private void SyncTime() + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.SyncTime; + msg.To = EphemeralClientId; + + if (IsCompleted()) + return; + + connection.SendAsyncMessage(EphemeralClientId, msg); + } + + private void SendGood() { var msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionAckGood; @@ -154,7 +181,6 @@ private void ReplyGood() connection.SendAsyncMessage(EphemeralClientId, msg); Completed(true); } - } private void ReplyError(string err) diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs index 6856ed0..733ffe8 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs @@ -1,12 +1,10 @@ -using NetworkLibrary.Components; +using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.SimpleRelay; using NetworkLibrary.P2P.Components.HolePunch; -using NetworkLibrary.P2P.Generic; using NetworkLibrary.Utils; using System; using System.Collections.Generic; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,7 +15,7 @@ class PipeData public const int TokenLength = 32 + 24;//32 bytes signature, 24 bytes token public byte[] Token { get; set; } // localhost, localip, publicip - public List PipeEndpoints { get; set; } = new List(); + public List PipeEndpoints { get; set; } = new List(); } internal class ServerPipeState : ConversationStateBase @@ -27,7 +25,12 @@ internal class ServerPipeState : ConversationStateBase private int ackCount = 0; private readonly IDistributedConnection connection; - public ServerPipeState(Guid stateId, IDistributedConnection connection, PipeManager piper) : base(stateId) + private string ChannelType; + bool isTcpPipe = false; + private string destinationDhPublicKey; + private string requesterDhPublicKey; + private ChannelInfo chInfo = new ChannelInfo(); + public ServerPipeState(Guid stateId, IDistributedConnection connection, PipeManager piper) : base(stateId, 20000) { this.connection = connection; this.piper = piper; @@ -45,7 +48,7 @@ public ServerPipeState(Guid stateId, IDistributedConnection connection, PipeMan public override void HandleMessage(MessageEnvelope message) { - switch (message.Header) + switch (message.Header) { case InternalConstants.PipeRequestTcp: HandlePipeRequest(message, true); @@ -54,8 +57,12 @@ public override void HandleMessage(MessageEnvelope message) HandlePipeRequest(message, false); break; + case InternalConstants.PipeReqAck://do nack also + HandlePipeReqAck(message); + break; + case InternalConstants.ConnectionAckGood: - HandleGoodAck(message); + HandleGoodAck(message); break; case InternalConstants.ConnectionAckBad: @@ -65,38 +72,66 @@ public override void HandleMessage(MessageEnvelope message) } } + private void HandlePipeReqAck(MessageEnvelope message) + { + if (chInfo.RequiresKeyExchange()) + destinationDhPublicKey = message.KeyValuePairs["DH"]; + + ObtainPipeToken().ContinueWith(HandlePipeToken); + } + private void HandlePipeRequest(MessageEnvelope message, bool tcp) { this.from = message.From; this.to = message.To; - ObtainPipeToken(tcp).ContinueWith(t=>HandlePipeToken(t,tcp)); + ChannelType = message.KeyValuePairs["Type"]; + + chInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); + chInfo.ChannelName = message.KeyValuePairs["Name"]; + + if (chInfo.RequiresKeyExchange()) + { + requesterDhPublicKey = message.KeyValuePairs["DH"]; + message.KeyValuePairs.Remove("DH"); + } + + connection.SendAsyncMessage(message); + isTcpPipe = tcp; } - private Task ObtainPipeToken(bool tcp) + private Task ObtainPipeToken() { //do only local for now - byte[] token = piper.GetPipeToken(tcp); + byte[] token = piper.GetPipeToken(isTcpPipe); PipeData data = new PipeData(); data.Token = token; - data.PipeEndpoints = new List() { new EndpointData("127.0.0.1", 20011) }; - + data.PipeEndpoints = new List() { new EndpointData("127.0.0.1", isTcpPipe ? 20011 : 20012) }; + return Task.FromResult(data); } - private void HandlePipeToken(Task task, bool tcp) + private void HandlePipeToken(Task task) { var data = task.Result; var msg = CreateEnvelope(); - msg.Header = tcp?InternalConstants.PipeTokenDeliveryTcp:InternalConstants.PipeTokenDeliveryUdp; + msg.Header = isTcpPipe ? InternalConstants.PipeTokenDeliveryTcp : InternalConstants.PipeTokenDeliveryUdp; var stream = SharerdMemoryStreamPool.RentStreamStatic(); stream.Position32 = 0; KnownTypeSerializer.SerializePipeData(stream, data); - msg.SetPayload(stream.GetBuffer(),0,stream.Position32); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + msg.KeyValuePairs = new Dictionary(); + + if(chInfo.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = destinationDhPublicKey; connection.SendAsyncMessage(from, msg); + + if (chInfo.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = requesterDhPublicKey; + connection.SendAsyncMessage(to, msg); SharerdMemoryStreamPool.ReturnStreamStatic(stream); diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs new file mode 100644 index 0000000..39eb745 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs @@ -0,0 +1,176 @@ +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.Utils; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; + +namespace NetworkLibrary.DistributedP2P.Server.StateManagement +{ + internal class ServerUdpHolepunchState : ConversationStateBase + { + private readonly IDistributedConnection connection; + private readonly SessionManager sessionManager; + Guid From; + Guid To; + int fromPort; + int toPort; + private int succesCount; + + public ServerUdpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) + { + this.connection = connection; + this.sessionManager = sessionManager; + } + + public override void HandleMessage(MessageEnvelope message) + { + switch (message.Header) + { + case InternalConstants.RequestHolepunchUdp: + HandleHolepunchRequest(message); + break; + case InternalConstants.AckRequestHolepunchUdp: + HandleHolepunchRequestAck(message); + break; + case InternalConstants.PunchSucces: + HandleSucces(message); + break; + case InternalConstants.PunchFail: + HandleFailure(message); + break; + } + } + + // obtain port from destination endpoint + private void HandleHolepunchRequest(MessageEnvelope message) + { + From = message.From; + To = message.To; + fromPort = int.Parse(message.KeyValuePairs.First().Key); + + connection.SendAsyncMessage(message); + } + + private void HandleHolepunchRequestAck(MessageEnvelope message) + { + toPort = int.Parse(message.KeyValuePairs.First().Key); + + //signal + double startTime = connection.GetTime(); + startTime += 500; + + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.StartHPUdp; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Time"] = startTime.ToString(CultureInfo.InvariantCulture); + + sessionManager.GetSessionData(From, out ServerSession sesFrom); + sessionManager.GetSessionData(To, out ServerSession sesTo); + + if (sesFrom != null && sesTo != null) + { + ObtainIpEndpoints(sesFrom, sesTo, out var FromNeedsToKnow, out var ToNeedsToKnow); + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.Position32 = 0; + + KnownTypeSerializer.SerializeEndpointTransferMessage(stream, FromNeedsToKnow); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + msg.To = From; + connection.SendAsyncMessage(msg); + + stream.Position32 = 0; + + KnownTypeSerializer.SerializeEndpointTransferMessage(stream, ToNeedsToKnow); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + msg.To = To; + connection.SendAsyncMessage(msg); + } + else + { + Cancel(); + } + + } + + + private void ObtainIpEndpoints(ServerSession sesFrom, ServerSession sesTo, out EndpointTransferMessage FromNeedsToKnow, out EndpointTransferMessage ToNeedsToKnow) + { + FromNeedsToKnow = new EndpointTransferMessage(); + ToNeedsToKnow = new EndpointTransferMessage(); + + IPHelper.ExtractLocalIpsWithMatchingSubnet(sesFrom.ClientLocalIps, + sesTo.ClientLocalIps, + out List Locals_From_NeedsToKnow, + out List Locals_To_NeedsToKnow); + + foreach (var local in Locals_From_NeedsToKnow) + { + EndpointData data = new EndpointData(local, toPort); + FromNeedsToKnow.LocalEndpoints.Add(data); + } + + foreach (var local in Locals_To_NeedsToKnow) + { + EndpointData data = new EndpointData(local, fromPort); + ToNeedsToKnow.LocalEndpoints.Add(data); + } + + // "From" is same network as the server. + if (IPHelper.IsPrivateIPAddress(sesFrom.ClientPublicIp)) + { + // "To" needs to get server adress to connect + // 0.0.0.0:0 means serverIp + ToNeedsToKnow.IpRemote = new byte[4]; + ToNeedsToKnow.PortRemote = fromPort; + + } + else + { + // send just the publicIp of "From" to "To" + ToNeedsToKnow.IpRemote = sesFrom.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + ToNeedsToKnow.PortRemote = fromPort; + } + + // "To" is same network as the server. + if (IPHelper.IsPrivateIPAddress(sesTo.ClientPublicIp)) + { + //"From" needs to get Server adress + FromNeedsToKnow.IpRemote = new byte[4]; + FromNeedsToKnow.PortRemote = toPort; + } + else + { + // send just the public to "From" + FromNeedsToKnow.IpRemote = sesTo.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + FromNeedsToKnow.PortRemote = toPort; + } + + } + + private void HandleFailure(MessageEnvelope message) + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchFailAck; + connection.SendAsyncMessage(From, msg); + connection.SendAsyncMessage(To, msg); + + } + + private void HandleSucces(MessageEnvelope message) + { + if (Interlocked.Increment(ref succesCount) == 2) + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSuccesAck; + connection.SendAsyncMessage(From, msg); + connection.SendAsyncMessage(To, msg); + } + } + + } +} diff --git a/NetworkLibrary/TCP/AES/AesTcpClient.cs b/NetworkLibrary/TCP/AES/AesTcpClient.cs index 97ee434..52b463d 100644 --- a/NetworkLibrary/TCP/AES/AesTcpClient.cs +++ b/NetworkLibrary/TCP/AES/AesTcpClient.cs @@ -14,7 +14,7 @@ public class AesTcpClient : AsyncTpcClient { private AesTcpSession sessionInternal; private ConcurrentAesAlgorithm algorithm; - + private Socket connectedSocket; public AesTcpClient(ConcurrentAesAlgorithm algorithm) { this.algorithm = algorithm; @@ -22,11 +22,21 @@ public AesTcpClient(ConcurrentAesAlgorithm algorithm) public AesTcpClient(Socket clientSocket, ConcurrentAesAlgorithm algorithm) { - this.algorithm = algorithm; SetConnectedSocket(clientSocket,ScatterGatherConfig.UseBuffer); } + internal AesTcpClient(ConcurrentAesAlgorithm algorithm,Socket clientSocket) + { + this.connectedSocket = clientSocket; + this.algorithm = algorithm; + } + + internal void Start() + { + SetConnectedSocket(connectedSocket, ScatterGatherConfig.UseBuffer); + } + private protected override IAsyncSession CreateSession(SocketAsyncEventArgs e, Guid sessionId) { var session = new AesTcpSession(e, sessionId,algorithm); diff --git a/NetworkLibrary/UDP/AsyncUdpServerLitecs.cs b/NetworkLibrary/UDP/AsyncUdpServerLitecs.cs index d61d9d6..71ad99a 100644 --- a/NetworkLibrary/UDP/AsyncUdpServerLitecs.cs +++ b/NetworkLibrary/UDP/AsyncUdpServerLitecs.cs @@ -51,6 +51,7 @@ public AsyncUdpServerLite(int port) ServerSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true); // Not compatible with Unity.. //ServerSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true); + int dscpValue = 40; // Critical services byte tos = (byte)(dscpValue << 2); // Shift left by 2 bits ServerSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.TypeOfService, tos); diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index 4de8862..c22bdaf 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -1,10 +1,12 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NetworkLibrary; +using NetworkLibrary.DistributedP2P.Channels; using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.DistributedP2P.Server; using Protobuff.Components.Serialiser; using System; using System.Collections.Generic; +using System.Diagnostics.Tracing; using System.Threading; using System.Threading.Tasks; @@ -107,8 +109,6 @@ public void ConnectTest() } - - [TestMethod] public void PipeTest() { @@ -116,6 +116,7 @@ public void PipeTest() DistributedLobbyClient distributedLobbyClient2 = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); TaskCompletionSource tcs = new TaskCompletionSource(); + ManualResetEvent mre = new ManualResetEvent(false); using var server = ArrangeServer(); @@ -126,7 +127,9 @@ public void PipeTest() distributedLobbyClient2.PeerConnected += PeerConnected; var info = new ChannelInfo(); - var channel1 = distributedLobbyClient.OpenTcpChannel(distributedLobbyClient2.SessionId, info).Result; + info.ChannelName = "Test"; + info.ChannelType = ChannelType.ByteMessage; + var channel1 = (ByteMessageChannel)distributedLobbyClient.OpenTcpChannel(distributedLobbyClient2.SessionId, info).Result; Assert.IsNotNull(channel1); @@ -134,11 +137,15 @@ public void PipeTest() channel1.Start(); channel1.SendAsync(data, 0, data.Length); + Thread.Sleep(100); + mre.Set(); int received = 0; - void PeerConnected(ITcpChannel channel) + void PeerConnected(IChannel channel_) { + mre.WaitOne();//emulate bad syncronisation + var channel = (ByteMessageChannel)channel_; channel.BytesReceived += Channel_BytesReceived; channel.Disconnected += Disconnected; channel.Start(); @@ -147,13 +154,13 @@ void PeerConnected(ITcpChannel channel) void Disconnected() { Console.WriteLine("DC"); - tcs.SetResult(false); + tcs.TrySetResult(false); } void Channel_BytesReceived(byte[] buff, int offset, int count) { received = count; - tcs.SetResult(true); + tcs.TrySetResult(true); } var ss = tcs.Task.Result; @@ -162,7 +169,109 @@ void Channel_BytesReceived(byte[] buff, int offset, int count) Assert.AreEqual(received, data.Length); } + [TestMethod] + public void SecurePipeTest() + { + DistributedLobbyClient distributedLobbyClient = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + DistributedLobbyClient distributedLobbyClient2 = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + + TaskCompletionSource tcs = new TaskCompletionSource(); + ManualResetEvent mre = new ManualResetEvent(false); + + using var server = ArrangeServer(); + var res = distributedLobbyClient.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = distributedLobbyClient2.ConnectAsync("127.0.0.1", 20010).Result; + + distributedLobbyClient2.PeerConnected += PeerConnected; + + var info = new ChannelInfo(); + info.ChannelName = "Test"; + info.ChannelType = ChannelType.SecureByteMessage; + var channel1 = (SecureByteMessageChannel)distributedLobbyClient.OpenTcpChannel(distributedLobbyClient2.SessionId, info).Result; + + Assert.IsNotNull(channel1); + + byte[] data = new byte[12800000]; + + channel1.Start(); + channel1.SendAsync(data, 0, data.Length); + Thread.Sleep(100); + mre.Set(); + + int received = 0; + + void PeerConnected(IChannel channel_) + { + mre.WaitOne();//emulate bad syncronisation + var channel = (SecureByteMessageChannel)channel_; + channel.BytesReceived += Channel_BytesReceived; + channel.Disconnected += Disconnected; + channel.Start(); + } + + void Disconnected() + { + Console.WriteLine("DC"); + tcs.TrySetResult(false); + } + + void Channel_BytesReceived(byte[] buff, int offset, int count) + { + received = count; + tcs.TrySetResult(true); + } + + var ss = tcs.Task.Result; + Assert.IsTrue(ss); + + Assert.AreEqual(received, data.Length); + + } + + [TestMethod] + public void PipeTestUdp() + { + + + TaskCompletionSource tcs = new TaskCompletionSource(); + ManualResetEvent mre = new ManualResetEvent(false); + + int received = 0; + + using var server = ArrangeServer(); + var cl1 = GetClient(); + var cl2 = GetClient(); + + var res = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + cl2.PeerConnected += Cl2_PeerConnected; + + var info = new ChannelInfo(); + var channel1 = cl1.OpenUdpSocket(cl2.SessionId, "Test").Result; + Assert.IsNotNull(channel1); + + channel1.Send(new byte[1337]); + Thread.Sleep(100); + mre.Set(); + + void Cl2_PeerConnected(IChannel obj) + { + mre.WaitOne(); + var udpSocket = (RawUdpSocket)obj; + byte[] buffer = new byte[2048]; + udpSocket.socket.ReceiveAsync(new ArraySegment(buffer), System.Net.Sockets.SocketFlags.None).ContinueWith( + t => { + received = t.Result; + tcs.SetResult(true); + }); + } + + var ss = tcs.Task.Result; + Assert.AreEqual(received, 1337); + + } [TestMethod] @@ -341,5 +450,18 @@ public void Timesync() Assert.IsTrue(Math.Abs(time1 - time2) < 1); } + [TestMethod] + public void UdpHolepunch() + { + var cl1 = GetClient(); + var cl2 = GetClient(); + using var server = ArrangeServer(); + var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + var res = cl1.TryUdpHolePunch(cl2.SessionId).Result; + Assert.IsTrue(res); + } + } } From c5986a5335a6d5c824e0bdee9143c28e0e705f55 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Fri, 4 Apr 2025 13:44:08 +0200 Subject: [PATCH 12/27] holpunch & udp channel, DH exchange --- .../Channels/SecureUdpMessageChannel.cs | 97 +++++++++ .../DistributedP2P/Channels/UdpChannel.cs | 124 ++++++++++++ .../Channels/UdpMessageChannel.cs | 150 ++++++++++++++ .../DistributedP2P/Client/ChannelInfo.cs | 5 +- .../Client/DistributedLobbyClient.cs | 52 +++-- .../Client/StateManagement/ClientPipeState.cs | 2 + .../ClientUdpHolepunchState.cs | 185 +++++++++++++----- .../Components/PreciseTimeAwaiter.cs | 2 + .../DistributedP2P/Components/UdpFlags.cs | 19 ++ .../Server/DistributedLobbyServer.cs | 2 +- .../ServerUdpHolepunchState.cs | 38 +++- NetworkLibrary/UDP/Jumbo/JumboModule.cs | 3 +- NetworkLibrary/UDP/Jumbo/Sender.cs | 117 ++--------- .../UDP/Reliable/Components/ReliableModule.cs | 10 + .../DistributedP2P/DistP2PServerclientTest.cs | 94 ++++++++- 15 files changed, 718 insertions(+), 182 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs create mode 100644 NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs create mode 100644 NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/UdpFlags.cs diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs new file mode 100644 index 0000000..3df64b0 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using System.Text; +using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.UDP.Jumbo; +using NetworkLibrary.UDP.Reliable.Components; +using NetworkLibrary.Utils; +using System.Net; +using NetworkLibrary.Components; + +namespace NetworkLibrary.DistributedP2P.Channels +{ + internal class SecureUdpMessageChannel:UdpMessageChannel + { + private readonly ConcurrentAesAlgorithm algo; + byte[] decryptBuff = new byte[65555]; + public SecureUdpMessageChannel(Socket udpSocket, IPEndPoint associatedEndpoint,ConcurrentAesAlgorithm algo, ChannelInfo info) : base(udpSocket, associatedEndpoint, info) + { + this.algo = algo; + } + + protected override void BytesReceived(byte[] buffer, int offset, int count) + { + var flag = (UdpFlags)buffer[offset++]; + count--; + count = algo.DecryptInto(buffer, offset, count, decryptBuff, 0); + buffer = decryptBuff; + offset = 0; + + switch (flag) + { + case UdpFlags.StandardMessage: + HandleMessage(buffer, offset, count); + break; + case UdpFlags.JumboMessage: + HandleJumboSegment(buffer, offset, count); + break; + case UdpFlags.ReliableMessage: + HandleRudpSegment(buffer, offset, count); + break; + case UdpFlags.KeepAliveMessage: + break; + + case UdpFlags.HP: + case UdpFlags.HPAck: + break; + } + } + + public override void Send(byte[] buffer, int offset, int count) + { + if (count > 64000) + { + JumboUdp.Send(buffer, offset, count); + } + else + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)UdpFlags.StandardMessage); + + stream.Reserve(count + 128); + stream.Position32 += algo.EncryptInto(buffer, offset, count, stream.GetBuffer(), 1); + + + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + } + + protected override void SendJumboSegment(byte[] buffer, int offset, int count) + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)UdpFlags.JumboMessage); + + stream.Reserve(count + 128); + stream.Position32 += algo.EncryptInto(buffer, offset, count, stream.GetBuffer(), 1); + + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + + protected override void SendRudpSegment(ReliableModule module,byte[] buffer, int offset, int count) + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)UdpFlags.ReliableMessage); + + stream.Reserve(count+128); + stream.Position32 += algo.EncryptInto(buffer, offset, count, stream.GetBuffer(),1); + + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + } + +} diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs new file mode 100644 index 0000000..078cb13 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs @@ -0,0 +1,124 @@ +using NetworkLibrary.DistributedP2P.Client; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Channels +{ + public class UdpChannel:IChannel,IDisposable + { + private Socket udpSocket; + private SocketAsyncEventArgs receiveArgs; + private readonly IPEndPoint associatedEndpoint; + + public ChannelInfo Info { get; private set; } + + public event Action OnMessageReceived; + + public UdpChannel(Socket udpSocket, IPEndPoint associatedEndpoint, ChannelInfo info) + { + this.udpSocket = udpSocket; + this.associatedEndpoint = associatedEndpoint; + Info = info; + } + + public void Start() + { + StartReceiver(); + } + + public void Send(byte[] data, int offset, int count) + { + udpSocket.SendTo(data, offset, count, SocketFlags.None, associatedEndpoint); + } + + private void StartReceiver() + { + var buff = BufferPool.RentBuffer(65536); + + receiveArgs = new SocketAsyncEventArgs(); + receiveArgs.SetBuffer(buff,0,buff.Length); + receiveArgs.Completed += OnReceiveCompleted; + receiveArgs.RemoteEndPoint = associatedEndpoint; + Receive(); + + } + + private void Receive() + { + if (!udpSocket.ReceiveFromAsync(receiveArgs)) + { + OnReceiveCompleted(null, receiveArgs); + } + } + + private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) + { + try + { + if (e.SocketError != SocketError.Success) + { + HandleSocketError(e.SocketError); + return; + } + + if (e.BytesTransferred > 0) + { + + try + { + ProcessReceivedData(e.Buffer, e.Offset, e.BytesTransferred, e.RemoteEndPoint); + } + catch (Exception ex) + { + Console.WriteLine($"Error processing received data: {ex}"); + } + } + + Receive(); + } + catch (Exception ex) + { + Console.WriteLine($"Error in receive completion: {ex}"); + } + } + + private void ProcessReceivedData(byte[] buffer, int offset, int bytesTransferred, EndPoint remoteEndPoint) + { + OnMessageReceived?.Invoke(buffer, offset, bytesTransferred); + } + + private void HandleSocketError(SocketError error) + { + Console.WriteLine($"Socket error occurred: {error}"); + } + + + + public void Dispose() + { + try + { + if (receiveArgs != null) + { + + BufferPool.ReturnBuffer(receiveArgs.Buffer); + receiveArgs.Dispose(); + receiveArgs = null; + } + + if (udpSocket != null) + { + udpSocket.Close(); + udpSocket.Dispose(); + udpSocket = null; + } + } + catch { } + + } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs new file mode 100644 index 0000000..f2f2321 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs @@ -0,0 +1,150 @@ +using NetworkLibrary.Components.Crypto.Algorithms; +using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.UDP.Jumbo; +using NetworkLibrary.UDP.Reliable.Components; +using NetworkLibrary.Utils; +using System; +using System.Net; +using System.Net.Sockets; + +namespace NetworkLibrary.DistributedP2P.Channels +{ + internal class UdpMessageChannel : IChannel + { + public ChannelInfo Info { get; private set; } + + private UdpChannel innerchannel; + + public event Action OnMessageReceived; + + protected JumboModule JumboUdp = new JumboModule(0); + protected ReliableModule ReliableUdp; + + public UdpMessageChannel(Socket udpSocket, IPEndPoint associatedEndpoint, ChannelInfo info) + { + + Info = info; + innerchannel = new UdpChannel(udpSocket, associatedEndpoint, info); + JumboUdp.SendToSocket = SendJumboSegment; + JumboUdp.MessageReceived = HandleMessage; + + SenderModule sender = new SenderModule(); + + sender.MaxSegmentSize = 1280; + sender.MinWindowSize = 1280*2; + + ReliableUdp = new ReliableModule(associatedEndpoint,sender); + + ReliableUdp.OnReceived += (e, b, o, c) => HandleMessage(b, o, c); + ReliableUdp.OnSend += SendRudpSegment; + + } + + public void Start() + { + innerchannel.OnMessageReceived += BytesReceived; + innerchannel.Start(); + } + + protected virtual void BytesReceived(byte[] buffer, int offset, int count) + { + // filter flags + var flag = (UdpFlags)buffer[offset++]; + count--; + + switch (flag) + { + case UdpFlags.StandardMessage: + HandleMessage(buffer, offset, count); + break; + case UdpFlags.JumboMessage: + HandleJumboSegment(buffer, offset, count); + break; + case UdpFlags.ReliableMessage: + HandleRudpSegment(buffer, offset, count); + break; + case UdpFlags.KeepAliveMessage: + break; + + case UdpFlags.HP: + case UdpFlags.HPAck: + break; + } + } + + + + protected void HandleMessage(byte[] buffer, int offset, int count) + { + OnMessageReceived?.Invoke(buffer, offset, count); + } + + protected void HandleJumboSegment(byte[] buffer, int offset, int count) + { + JumboUdp.HandleReceivedSegment(buffer, offset, count); + } + + protected virtual void SendJumboSegment(byte[] arg1, int arg2, int arg3) + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)UdpFlags.JumboMessage); + stream.Write(arg1, arg2, arg3); + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + + protected void HandleRudpSegment(byte[] buffer, int offset, int count) + { + ReliableUdp.HandleBytes(buffer, offset, count); + } + protected virtual void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)UdpFlags.ReliableMessage); + stream.Write(buffer, offset, count); + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + + + public virtual void Send(byte[] buffer, int offset, int count) + { + + if (count > 64000) + { + JumboUdp.Send(buffer, offset, count); + } + else + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)UdpFlags.StandardMessage); + stream.Write(buffer, offset, count); + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + + } + + public void SendReliable(byte[] buffer, int offset, int count) + { + ReliableUdp.Send(buffer, offset, count); + } + + + protected void SendInternal(byte[] bytes, int offset, int count) + { + try + { + innerchannel.Send(bytes, offset, count); + } + catch (Exception e) + { + } + + } + public void Dispose() + { + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs index c438c69..e508d6b 100644 --- a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs +++ b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs @@ -7,7 +7,7 @@ namespace NetworkLibrary.DistributedP2P.Client { public enum ChannelType { - RawTcp,RawUdp,ByteMessage,SecureByteMessage + RawTcp,RawUdp,ByteMessage,SecureByteMessage,UdpMessage,SecureUdpMessage } public class ChannelInfo { @@ -18,7 +18,8 @@ public class ChannelInfo internal bool RequiresKeyExchange() { - if(ChannelType == ChannelType.SecureByteMessage) + if(ChannelType == ChannelType.SecureByteMessage || + ChannelType == ChannelType.SecureUdpMessage) return true; return false; } diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 27c51ec..44bbc2b 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -15,6 +15,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; +using NetworkLibrary.Components; namespace NetworkLibrary.DistributedP2P.Client { @@ -200,11 +201,12 @@ private static IChannel CreateChannel(ClientPipeState pipeState) channel = new ByteMessageChannel(pipeState.ChannelInfo, pipeState.ConnectedSocket); break; case ChannelType.SecureByteMessage: - var symetricKey = HKDFLite.DeriveKey(pipeState.sharedSecret,outputLength:16); - var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey,AesMode.GCM); - AesTcpClient client = new AesTcpClient(algo,pipeState.ConnectedSocket); + var symetricKey = HKDFLite.DeriveKey(pipeState.sharedSecret, outputLength: 16); + var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey, AesMode.GCM); + AesTcpClient client = new AesTcpClient(algo, pipeState.ConnectedSocket); channel = new SecureByteMessageChannel(client, pipeState.ChannelInfo); break; + } return channel; @@ -309,24 +311,36 @@ public Dictionary GetPeerList() return copy; } - public async Task TryUdpHolePunch(Guid destination) + public async Task TryUdpHolePunch(Guid destination, ChannelInfo info) { - var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this); + var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this,info); stateManager.RegisterState(state); state.Start(); await state.WaitCompletion(); - //if (state.IsSuccesful) - //{ - // state.Socket.SendTo( new byte[689], state.SuccesfulEndpoint); - //} - return state.IsSuccesful; + if (state.IsSuccesful) + { + if(info.ChannelType == ChannelType.SecureUdpMessage) + { + var key = HKDFLite.DeriveKey(state.SharedSecret, outputLength: 16); + var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(key, AesMode.GCM); + IChannel ch = new SecureUdpMessageChannel(state.Socket, state.SuccesfulEndpoint, algo, info); + return ch; + } + else + { + IChannel ch = new UdpMessageChannel(state.Socket, state.SuccesfulEndpoint, info); + return ch; + } + + } + return null; } private void ManageUdpHolepunchRequest(MessageEnvelope envelope) { - var state = new ClientUdpHolepunchState(envelope.MessageId, envelope.From, this); + var state = new ClientUdpHolepunchState(envelope.MessageId, envelope.From, this,null); stateManager.RegisterState(state); state.OnComplete += State_OnComplete; state.HandleMessage(envelope); @@ -335,9 +349,19 @@ void State_OnComplete(IConversationState obj) { if (state.IsSuccesful) { - //byte[] buff = new byte[1024]; - //int rec = state.Socket.Receive(buff); - //Console.WriteLine("YIPPIE"); + if(state.info.ChannelType == ChannelType.SecureUdpMessage) + { + var key = HKDFLite.DeriveKey(state.SharedSecret, outputLength: 16); + var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(key, AesMode.GCM); + IChannel ch = new SecureUdpMessageChannel(state.Socket, state.SuccesfulEndpoint, algo, state.info); + PeerConnected?.Invoke(ch); + } + else + { + IChannel ch = new UdpMessageChannel(state.Socket, state.SuccesfulEndpoint, state.info); + PeerConnected?.Invoke(ch); + } + } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index 84db409..0a7052d 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -256,6 +256,8 @@ private async void HandlePipeTokenUdp(MessageEnvelope message) var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); var connected = new Socket(SocketType.Dgram, ProtocolType.Udp); + connected.SendBufferSize = 12800000; + connected.ReceiveBufferSize = 12800000; foreach (EndpointData endpoint in pipeData.PipeEndpoints) { diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index db26b57..67fc2d6 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -1,4 +1,5 @@ -using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.UDP; @@ -17,24 +18,38 @@ internal class ClientUdpHolepunchState : ConversationStateBase private readonly Guid destId; private readonly IDistributedConnection connection; public Socket Socket; + private bool remoteSucces; + private bool isInitiator; - public IPEndPoint SuccesfulEndpoint { get; private set; } + public IPEndPoint SuccesfulEndpoint; - public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection) : base(stateId, 20000) + private DiffieHellman df = new DiffieHellman(); + private byte[] otherPublicKey; + public byte[] SharedSecret; + public ChannelInfo info; + public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, ChannelInfo info) : base(stateId, 20000) { this.destId = destId; this.connection = connection; + this.info = info; } //the initiator public void Start() { + isInitiator = true; int port = StartUdpSocket(); var msg = CreateEnvelope(); msg.Header = InternalConstants.RequestHolepunchUdp; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs[port.ToString()] = null; + msg.KeyValuePairs["Port"] = port.ToString(); + msg.KeyValuePairs["Type"] = ((int)info.ChannelType).ToString(); + msg.KeyValuePairs["Name"] = info.ChannelName; + + if(info.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + msg.To = destId; connection.SendAsyncMessage(msg); @@ -56,7 +71,7 @@ public override void HandleMessage(MessageEnvelope message) break; case InternalConstants.PunchSuccesAck: - HandleSucces(); + HandleRemoteSucces(); break; case InternalConstants.PunchFailAck: HandleFailure(); @@ -64,18 +79,22 @@ public override void HandleMessage(MessageEnvelope message) } } - - - - - // the destination of hp + // the destination peer of hp private void HandleRemoteHpRequest(MessageEnvelope message) { + info = new ChannelInfo(); + info.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); + info.ChannelName = message.KeyValuePairs["Name"]; + int port = StartUdpSocket(); var msg = CreateEnvelope(); msg.Header = InternalConstants.AckRequestHolepunchUdp; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs[port.ToString()] = null; + msg.KeyValuePairs["Port"] = port.ToString(); + + if (info.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + msg.To = destId; connection.SendAsyncMessage(msg); @@ -86,80 +105,133 @@ private void StartHolepunchRoutine(MessageEnvelope message) { var epMsg = KnownTypeSerializer.DeserializeEndpointTransferMessage(message.Payload, message.PayloadOffset); var time = double.Parse(message.KeyValuePairs["Time"]); - + if(info.RequiresKeyExchange()) + otherPublicKey = Convert.FromBase64String(message.KeyValuePairs["DH"]); + + foreach (EndpointData localEp in epMsg.LocalEndpoints) { for (int i = 0; i < 2; i++) { - TryPunch(localEp); + TryPunch(localEp, UdpFlags.HP); PreciseTimeAwaiter.Wait(20); + if (IsCompleted()) return; } } + if (IsCompleted()) return; EndpointData publicEp = new EndpointData() { Ip = epMsg.IpRemote, Port = epMsg.PortRemote }; var now = connection.GetTime(); - PreciseTimeAwaiter.Wait(time - now); + var delay = now - time; + if(delay>500) + delay = 0; + PreciseTimeAwaiter.Wait(delay); + if (IsCompleted()) return; - for (int i = 0; i < 4; i++) + for (int i = 0; i < 5; i++) { - TryPunch(publicEp); - PreciseTimeAwaiter.Wait(20 * i); + TryPunch(publicEp,UdpFlags.HP); + PreciseTimeAwaiter.Wait(20 * i*i); + if (IsCompleted()) return; } } - private void TryPunch(EndpointData localEp) + private void TryPunch(EndpointData ep, UdpFlags flag ) + { + var ipep = ep.ToIpEndpoint(); + TryPunch(ipep, flag); + } + + private void TryPunch(IPEndPoint ep, UdpFlags flag) { - Socket.SendTo(new byte[1] { 0xFF }, SocketFlags.None, localEp.ToIpEndpoint()); + Log("SendingTo " + ep.ToString()); + Socket.SendTo(new byte[1] { (byte)flag }, SocketFlags.None, ep); } private int StartUdpSocket() { Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + Socket.SendBufferSize = 12800000; + Socket.ReceiveBufferSize = 12800000; + Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true); + Socket.Bind(new IPEndPoint(IPAddress.Any, 0)); - ReceiveOnceAsync().ContinueWith(Received); + Receive(); return ((IPEndPoint)Socket.LocalEndPoint).Port; - } - private async Task ReceiveOnceAsync() + private async void Receive() { var buffer = BufferPool.RentBuffer(64000); - var remoteEP = (EndPoint)new IPEndPoint(IPAddress.Any, 0); - - try + int receivedOnce = 0; + while (true) { - var receiveTask = Socket.ReceiveFromAsync(new ArraySegment(buffer), SocketFlags.None, remoteEP); - - var completedTask = await Task.WhenAny(receiveTask, Task.Delay(5000)); - if (completedTask == receiveTask) + try { - var result = await receiveTask; - return ((IPEndPoint)result.RemoteEndPoint); + var remoteEP = (EndPoint)new IPEndPoint(IPAddress.Any, 0); + var receiveTask = Socket.ReceiveFromAsync(new ArraySegment(buffer), SocketFlags.None, remoteEP); + + var completedTask = await Task.WhenAny(receiveTask, Task.Delay(5000)); + if (completedTask == receiveTask) + { + SocketReceiveFromResult received = receiveTask.Result; + if (received.ReceivedBytes != 1) + { + Cancel(); + return; + } + + if (buffer[0] == (byte)UdpFlags.HP) + { + // this must be only once + if (Interlocked.CompareExchange(ref receivedOnce, 1,0) ==0) + TryPunch((IPEndPoint)received.RemoteEndPoint, UdpFlags.HPAck); + + Log("[-]Received 0xFF from " + ((IPEndPoint)received.RemoteEndPoint).ToString()); + } + else if (buffer[0] == (byte)UdpFlags.HPAck) + { + ReceivedBidirectional(received.RemoteEndPoint); + return; + } + else + { + Cancel(); + } + } + else + { + TimedOut(); + } } - else + catch { - TimedOut(); - return null; + Cancel(); + } + finally + { + BufferPool.ReturnBuffer(buffer); } } - catch - { - Cancel(); - return null; - } - finally - { - BufferPool.ReturnBuffer(buffer); - } + } - private void Received(Task task) + private void ReceivedBidirectional(EndPoint remoteEndPoint) { - SuccesfulEndpoint = task.Result; + + if (remoteEndPoint == null) return; + + var ipep = (IPEndPoint)remoteEndPoint; + + if (Interlocked.CompareExchange(ref SuccesfulEndpoint, ipep, null) != null) + return; + + Log("Received From " + SuccesfulEndpoint.ToString()); + var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchSucces; connection.SendAsyncMessage(msg); @@ -170,6 +242,7 @@ private void TimedOut() var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchFail; connection.SendAsyncMessage(msg); + Cancel(); } private void HandleFailure() @@ -177,12 +250,30 @@ private void HandleFailure() Completed(false); } - private void HandleSucces() + private void HandleRemoteSucces() { - Completed(true); + // Completed(true); + + remoteSucces = true; + CheckSucces(); } + private void CheckSucces() + { + if (SuccesfulEndpoint != null && remoteSucces) + { + if (info.RequiresKeyExchange()) + SharedSecret = df.CalculateSharedSecret(otherPublicKey); + + Completed(true); + } + } + private void Log(string log) + { + string prefix = isInitiator ? "A: " : "B: "; + Console.WriteLine(prefix+log); + } } diff --git a/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs b/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs index 4c018ef..143d680 100644 --- a/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs +++ b/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs @@ -11,6 +11,8 @@ internal class PreciseTimeAwaiter static Stopwatch sw = Stopwatch.StartNew(); public static void Wait(double miliseconds) { + if (miliseconds <= 0) + return; double time = sw.Elapsed.TotalMilliseconds; double until = time+miliseconds; diff --git a/NetworkLibrary/DistributedP2P/Components/UdpFlags.cs b/NetworkLibrary/DistributedP2P/Components/UdpFlags.cs new file mode 100644 index 0000000..8438905 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/UdpFlags.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Components +{ + [Flags] + public enum UdpFlags:byte + { + StandardMessage = 1, + JumboMessage = 2, + ReliableMessage = 4, + KeepAliveMessage = 8, + HP = 16, + HPAck = 32, + + } + +} diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 907c7e1..9d30a9a 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -263,7 +263,7 @@ public DateTime GetDateTime() public double GetTime() { - return 0; + return serverClock.Elapsed.TotalMilliseconds; } public Task SyncTime() { diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs index 39eb745..1b6beff 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs @@ -1,4 +1,5 @@ -using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.Utils; using System; @@ -16,7 +17,10 @@ internal class ServerUdpHolepunchState : ConversationStateBase Guid From; Guid To; int fromPort; + string fromPublicKey; int toPort; + string toPublicKey; + ChannelInfo info; private int succesCount; public ServerUdpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) @@ -47,16 +51,23 @@ public override void HandleMessage(MessageEnvelope message) // obtain port from destination endpoint private void HandleHolepunchRequest(MessageEnvelope message) { + info = new ChannelInfo(); + info.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); + info.ChannelName = message.KeyValuePairs["Name"]; + From = message.From; To = message.To; - fromPort = int.Parse(message.KeyValuePairs.First().Key); - + fromPort = int.Parse(message.KeyValuePairs["Port"]); + if(info.RequiresKeyExchange()) + fromPublicKey = message.KeyValuePairs["DH"]; connection.SendAsyncMessage(message); } private void HandleHolepunchRequestAck(MessageEnvelope message) { - toPort = int.Parse(message.KeyValuePairs.First().Key); + toPort = int.Parse(message.KeyValuePairs["Port"]); + if (info.RequiresKeyExchange()) + toPublicKey = message.KeyValuePairs["DH"]; //signal double startTime = connection.GetTime(); @@ -81,6 +92,8 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) KnownTypeSerializer.SerializeEndpointTransferMessage(stream, FromNeedsToKnow); msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); msg.To = From; + if (info.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = toPublicKey; connection.SendAsyncMessage(msg); stream.Position32 = 0; @@ -88,6 +101,8 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) KnownTypeSerializer.SerializeEndpointTransferMessage(stream, ToNeedsToKnow); msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); msg.To = To; + if (info.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = fromPublicKey; connection.SendAsyncMessage(msg); } else @@ -158,17 +173,24 @@ private void HandleFailure(MessageEnvelope message) msg.Header = InternalConstants.PunchFailAck; connection.SendAsyncMessage(From, msg); connection.SendAsyncMessage(To, msg); + Completed(false); } private void HandleSucces(MessageEnvelope message) { + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSuccesAck; + + if(message.From == From) + connection.SendAsyncMessage(To, msg); + else if(message.From == To) + connection.SendAsyncMessage(From, msg); + if (Interlocked.Increment(ref succesCount) == 2) { - var msg = CreateEnvelope(); - msg.Header = InternalConstants.PunchSuccesAck; - connection.SendAsyncMessage(From, msg); - connection.SendAsyncMessage(To, msg); + Completed(true); } } diff --git a/NetworkLibrary/UDP/Jumbo/JumboModule.cs b/NetworkLibrary/UDP/Jumbo/JumboModule.cs index aeb6842..95b5b0c 100644 --- a/NetworkLibrary/UDP/Jumbo/JumboModule.cs +++ b/NetworkLibrary/UDP/Jumbo/JumboModule.cs @@ -10,10 +10,11 @@ public class JumboModule Sender sender; public Action SendToSocket; public Action MessageReceived; - public JumboModule() + public JumboModule(int reserveforPrefix = 38) { this.receiver = new Receiver(); this.sender = new Sender(); + sender.ReserveForPrefix = reserveforPrefix; sender.OnSend = SendBytesToSocket; receiver.OnMessageExtracted = HandleExtractedMessage; } diff --git a/NetworkLibrary/UDP/Jumbo/Sender.cs b/NetworkLibrary/UDP/Jumbo/Sender.cs index 0f5ba19..245ffbe 100644 --- a/NetworkLibrary/UDP/Jumbo/Sender.cs +++ b/NetworkLibrary/UDP/Jumbo/Sender.cs @@ -27,81 +27,10 @@ private static byte[] GetBuffer() public Action OnSend; public void ProcessBytes(byte[] buffer, int offset, int count) { - //if (count < 256000) - //{ - // ProcessBytesUnsafe(buffer, offset, count); - //} - //else - { - ProcessBytesSafe(buffer, offset, count); - } - } - private unsafe void ProcessBytesUnsafe(byte[] buffer, int offset, int count) - { - var b = stackalloc byte[count]; - fixed (byte* message_ = &buffer[offset]) - Buffer.MemoryCopy(message_, b, count, count); - - - int totalNumSeq = count / FragmentSize; - if (count % FragmentSize != 0) - { - totalNumSeq++; - } - - var msgNo = Interlocked.Increment(ref currentMsgNo); - byte curresntSeq = 1; - - var tempBuff = GetBuffer(); - while (count > FragmentSize) - { - int offset_ = ReserveForPrefix; - int tempbuffCnt = 0; - - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_; - WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); - curresntSeq++; - tempbuffCnt += offset_; - - // ByteCopy.BlockCopy(buffer, offset, tempBuff, offset_, fragmentsize); - fixed (byte* dest = &tempBuff[offset_]) - Buffer.MemoryCopy(b+offset, dest, FragmentSize, FragmentSize); - offset += FragmentSize; - count -= FragmentSize; - tempbuffCnt += FragmentSize; - var cc = tempBuff[tempbuffCnt-1]; - var a = buffer[offset]; - - OnSend?.Invoke(tempBuff, 0, tempbuffCnt); - - } - if (count > 0) - { - int offset_ = ReserveForPrefix; - int tempbuffCnt = 0; - - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_; - WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); - curresntSeq++; - tempbuffCnt += offset_; - - var a = buffer[offset - 1]; - a = buffer[offset]; - a = buffer[offset + 1]; - - // ByteCopy.BlockCopy(buffer, offset, tempBuff, offset_, count); - fixed (byte* dest = &tempBuff[offset_]) - Buffer.MemoryCopy(b + offset, dest, count, count); - tempbuffCnt += count; - OnSend?.Invoke(tempBuff, 0, tempbuffCnt); - } + + ProcessBytesSafe(buffer, offset, count); } + private void ProcessBytesSafe(byte[] buffer, int offset, int count) { @@ -124,10 +53,7 @@ private void ProcessBytesSafe(byte[] buffer, int offset, int count) int offset_ = ReserveForPrefix; int tempbuffCnt = 0; - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_; + WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); curresntSeq++; tempbuffCnt += offset_; @@ -148,10 +74,7 @@ private void ProcessBytesSafe(byte[] buffer, int offset, int count) int offset_ = ReserveForPrefix; int tempbuffCnt = 0; - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_; + WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); curresntSeq++; tempbuffCnt += offset_; @@ -181,10 +104,7 @@ public void ProcessBytes(in Segment first, in Segment second) int offset_ = ReserveForPrefix; int tempbuffCnt = 0; - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_; + WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); curresntSeq++; tempbuffCnt += offset_; @@ -208,10 +128,7 @@ public void ProcessBytes(in Segment first, in Segment second) offset_ = ReserveForPrefix; tempbuffCnt = 0; - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_; + WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); curresntSeq++; tempbuffCnt += offset_; @@ -230,10 +147,7 @@ public void ProcessBytes(in Segment first, in Segment second) offset_ = ReserveForPrefix; tempbuffCnt = 0; - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_; + WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); curresntSeq++; tempbuffCnt += offset_; @@ -262,10 +176,7 @@ public unsafe void ProcessBytes(in SegmentUnsafe first, in Segment second) int offset_ = ReserveForPrefix; int tempbuffCnt = 0; - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_; + WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); curresntSeq++; tempbuffCnt += offset_; @@ -293,10 +204,7 @@ public unsafe void ProcessBytes(in SegmentUnsafe first, in Segment second) offset_ = ReserveForPrefix; tempbuffCnt = 0; - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_;// this includes reserve + WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); curresntSeq++; tempbuffCnt += offset_; @@ -315,10 +223,7 @@ public unsafe void ProcessBytes(in SegmentUnsafe first, in Segment second) offset_ = ReserveForPrefix; tempbuffCnt = 0; - //PrimitiveEncoder.WriteInt32(tempBuff, ref offset_, msgNo); - //tempBuff[offset_++] = (byte)totalNumSeq; - //tempBuff[offset_++] = curresntSeq++; - //tempbuffCnt += offset_; + WriteMetadata((ushort)FragmentSize, msgNo, (byte)totalNumSeq, curresntSeq, tempBuff, ref offset_); curresntSeq++; tempbuffCnt += offset_; diff --git a/NetworkLibrary/UDP/Reliable/Components/ReliableModule.cs b/NetworkLibrary/UDP/Reliable/Components/ReliableModule.cs index 50c9f8c..9ffcf62 100644 --- a/NetworkLibrary/UDP/Reliable/Components/ReliableModule.cs +++ b/NetworkLibrary/UDP/Reliable/Components/ReliableModule.cs @@ -20,6 +20,16 @@ public ReliableModule(IPEndPoint adress) receiver.OnMessageReceived += OutputMessageReceived; } + public ReliableModule(IPEndPoint adress,SenderModule sender) + { + this.sender = sender; + Endpoint = adress; + sender.SendRequested += OutputDataBytesToSend; + + receiver.SendFeedback += OutputFeedbacks; + receiver.OnMessageReceived += OutputMessageReceived; + } + private void OutputMessageReceived(byte[] buffer, int offset, int count) { OnReceived?.Invoke(this, buffer, offset, count); diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index c22bdaf..da4e1c1 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -445,23 +445,111 @@ public void Timesync() var res = cl1.ConnectAsync("127.0.0.1", 20010).Result; var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; - double time1 =cl1.GetTime(); + double time1 = cl1.GetTime(); double time2 = cl2.GetTime(); + double time3 = server.GetTime(); Assert.IsTrue(Math.Abs(time1 - time2) < 1); + Assert.IsTrue(Math.Abs(time1 - time3) < 1); } [TestMethod] public void UdpHolepunch() { + + TaskCompletionSource tcs = new TaskCompletionSource(); + ManualResetEvent mre = new ManualResetEvent(false); + int received = 0; + var cl1 = GetClient(); var cl2 = GetClient(); using var server = ArrangeServer(); + var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; - var res = cl1.TryUdpHolePunch(cl2.SessionId).Result; - Assert.IsTrue(res); + cl2.PeerConnected += Cl2_PeerConnected; + + var info = new ChannelInfo(); + info.ChannelType = ChannelType.UdpMessage; + info.ChannelName = "Test"; + var channel1 = (UdpMessageChannel)cl1.TryUdpHolePunch(cl2.SessionId, info).Result; + Assert.IsNotNull(channel1); + channel1.Start(); + + var data = new byte[1280000]; + data[0] = 1; + channel1.SendReliable(data, 0, data.Length); + Thread.Sleep(100); + + void Cl2_PeerConnected(IChannel obj) + { + var ch = (UdpMessageChannel)obj; + ch.OnMessageReceived += Ch_OnMessageReceived; + ch.Start(); + } + + void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) + { + received = arg3; + tcs.TrySetResult(true); + } + + var ss = tcs.Task.Result; + Assert.AreEqual(data.Length, received); + + + + } + + [TestMethod] + public void UdpHolepunchSecure() + { + + TaskCompletionSource tcs = new TaskCompletionSource(); + ManualResetEvent mre = new ManualResetEvent(false); + int received = 0; + + var cl1 = GetClient(); + var cl2 = GetClient(); + using var server = ArrangeServer(); + + var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + cl2.PeerConnected += Cl2_PeerConnected; + + var info = new ChannelInfo(); + info.ChannelType = ChannelType.SecureUdpMessage; + info.ChannelName = "Test"; + var channel1 = (SecureUdpMessageChannel)cl1.TryUdpHolePunch(cl2.SessionId, info).Result; + Assert.IsNotNull(channel1); + channel1.Start(); + + var data = new byte[1280000]; + data[0] = 1; + channel1.SendReliable(data,0,data.Length); + Thread.Sleep(100); + + void Cl2_PeerConnected(IChannel obj) + { + var ch = (SecureUdpMessageChannel)obj; + ch.OnMessageReceived += Ch_OnMessageReceived; + ch.Start(); + } + + void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) + { + received = arg3; + tcs.TrySetResult(true); + } + + var ss = tcs.Task.Result; + Assert.AreEqual(data.Length, received); + + + } + } } From 1604533eeda3f124da811e02d6a6c866e83a9627 Mon Sep 17 00:00:00 2001 From: dogancan ozturk Date: Mon, 7 Apr 2025 06:55:45 +0200 Subject: [PATCH 13/27] finalized udp hp --- .../Channels/SecureUdpMessageChannel.cs | 10 +- .../DistributedP2P/Channels/UdpChannel.cs | 7 +- .../Channels/UdpMessageChannel.cs | 12 +- .../DistributedP2P/Client/ChannelInfo.cs | 9 + .../Client/DistributedLobbyClient.cs | 8 +- .../ClientUdpHolepunchState.cs | 157 ++++++++++++------ .../Components/ConversationStateBase.cs | 2 +- .../DistributedP2P/Components/IPHelper.cs | 4 +- .../ServerUdpHolepunchState.cs | 29 ++-- 9 files changed, 157 insertions(+), 81 deletions(-) diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs index 3df64b0..0383f71 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs @@ -12,11 +12,11 @@ namespace NetworkLibrary.DistributedP2P.Channels { - internal class SecureUdpMessageChannel:UdpMessageChannel + public class SecureUdpMessageChannel:UdpMessageChannel { private readonly ConcurrentAesAlgorithm algo; byte[] decryptBuff = new byte[65555]; - public SecureUdpMessageChannel(Socket udpSocket, IPEndPoint associatedEndpoint,ConcurrentAesAlgorithm algo, ChannelInfo info) : base(udpSocket, associatedEndpoint, info) + public SecureUdpMessageChannel(Socket udpSocket, IPEndPoint receiveEp, ConcurrentAesAlgorithm algo, ChannelInfo info) : base(udpSocket, receiveEp, info) { this.algo = algo; } @@ -25,6 +25,10 @@ protected override void BytesReceived(byte[] buffer, int offset, int count) { var flag = (UdpFlags)buffer[offset++]; count--; + + if (flag == UdpFlags.HP || flag == UdpFlags.HPAck) + return; + count = algo.DecryptInto(buffer, offset, count, decryptBuff, 0); buffer = decryptBuff; offset = 0; @@ -81,7 +85,7 @@ protected override void SendJumboSegment(byte[] buffer, int offset, int count) SharerdMemoryStreamPool.ReturnStreamStatic(stream); } - protected override void SendRudpSegment(ReliableModule module,byte[] buffer, int offset, int count) + internal override void SendRudpSegment(ReliableModule module,byte[] buffer, int offset, int count) { var stream = SharerdMemoryStreamPool.RentStreamStatic(); stream.WriteByte((byte)UdpFlags.ReliableMessage); diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs index 078cb13..0d165d2 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Sockets; using System.Text; +using System.Threading; namespace NetworkLibrary.DistributedP2P.Channels { @@ -17,10 +18,10 @@ public class UdpChannel:IChannel,IDisposable public event Action OnMessageReceived; - public UdpChannel(Socket udpSocket, IPEndPoint associatedEndpoint, ChannelInfo info) + public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) { this.udpSocket = udpSocket; - this.associatedEndpoint = associatedEndpoint; + this.associatedEndpoint = receiveEp; Info = info; } @@ -50,7 +51,7 @@ private void Receive() { if (!udpSocket.ReceiveFromAsync(receiveArgs)) { - OnReceiveCompleted(null, receiveArgs); + ThreadPool.UnsafeQueueUserWorkItem((s)=> OnReceiveCompleted(null, receiveArgs),null); } } diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs index f2f2321..eab9a5a 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs @@ -10,7 +10,7 @@ namespace NetworkLibrary.DistributedP2P.Channels { - internal class UdpMessageChannel : IChannel + public class UdpMessageChannel : IChannel { public ChannelInfo Info { get; private set; } @@ -19,13 +19,13 @@ internal class UdpMessageChannel : IChannel public event Action OnMessageReceived; protected JumboModule JumboUdp = new JumboModule(0); - protected ReliableModule ReliableUdp; + internal ReliableModule ReliableUdp; - public UdpMessageChannel(Socket udpSocket, IPEndPoint associatedEndpoint, ChannelInfo info) + public UdpMessageChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) { Info = info; - innerchannel = new UdpChannel(udpSocket, associatedEndpoint, info); + innerchannel = new UdpChannel(udpSocket, receiveEp, info); JumboUdp.SendToSocket = SendJumboSegment; JumboUdp.MessageReceived = HandleMessage; @@ -34,7 +34,7 @@ public UdpMessageChannel(Socket udpSocket, IPEndPoint associatedEndpoint, Channe sender.MaxSegmentSize = 1280; sender.MinWindowSize = 1280*2; - ReliableUdp = new ReliableModule(associatedEndpoint,sender); + ReliableUdp = new ReliableModule(receiveEp,sender); ReliableUdp.OnReceived += (e, b, o, c) => HandleMessage(b, o, c); ReliableUdp.OnSend += SendRudpSegment; @@ -98,7 +98,7 @@ protected void HandleRudpSegment(byte[] buffer, int offset, int count) { ReliableUdp.HandleBytes(buffer, offset, count); } - protected virtual void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) + internal virtual void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) { var stream = SharerdMemoryStreamPool.RentStreamStatic(); stream.WriteByte((byte)UdpFlags.ReliableMessage); diff --git a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs index e508d6b..35bd48d 100644 --- a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs +++ b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs @@ -11,6 +11,15 @@ public enum ChannelType } public class ChannelInfo { + public ChannelInfo() + { + } + + public ChannelInfo(ChannelType channelType, string channelName) + { + ChannelType = channelType; + ChannelName = channelName; + } public ChannelType ChannelType { get; internal set; } diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 44bbc2b..4f121a7 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -45,6 +45,7 @@ public bool IsConnected private set => Interlocked.Exchange(ref connected, value ? 1 : 0); } + private EndpointData serverEndpoint= new EndpointData(); public DistributedLobbyClient(IClientDbConnection clientDbConnector, IClientAuthenticationProvider clientAuthProvider, X509Certificate2 certificate = null) @@ -78,6 +79,7 @@ public async Task ConnectAsync(string ip, int port) if (conState.IsSuccesful) { + serverEndpoint = new EndpointData(ip, port); SessionId = conState.SessionId; IsConnected = true; timeSync.StartAutoTimeSync(5000); @@ -313,7 +315,7 @@ public Dictionary GetPeerList() public async Task TryUdpHolePunch(Guid destination, ChannelInfo info) { - var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this,info); + var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this,serverEndpoint,info); stateManager.RegisterState(state); state.Start(); @@ -330,7 +332,7 @@ public async Task TryUdpHolePunch(Guid destination, ChannelInfo info) } else { - IChannel ch = new UdpMessageChannel(state.Socket, state.SuccesfulEndpoint, info); + IChannel ch = new UdpMessageChannel(state.Socket, state.SuccesfulEndpoint,info); return ch; } @@ -340,7 +342,7 @@ public async Task TryUdpHolePunch(Guid destination, ChannelInfo info) private void ManageUdpHolepunchRequest(MessageEnvelope envelope) { - var state = new ClientUdpHolepunchState(envelope.MessageId, envelope.From, this,null); + var state = new ClientUdpHolepunchState(envelope.MessageId, envelope.From, this,serverEndpoint, null); stateManager.RegisterState(state); state.OnComplete += State_OnComplete; state.HandleMessage(envelope); diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 67fc2d6..50c554c 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -1,24 +1,27 @@ -using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.Components; +using NetworkLibrary.Components.Crypto.DiffieHellman; using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; -using NetworkLibrary.UDP; +using NetworkLibrary.Utils; using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Net; using System.Net.Sockets; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace NetworkLibrary.DistributedP2P.Client.StateManagement { + //Todo 0.0.0.0 means server ip! internal class ClientUdpHolepunchState : ConversationStateBase { private readonly Guid destId; private readonly IDistributedConnection connection; + private readonly EndpointData serverEndpoint; public Socket Socket; - private bool remoteSucces; private bool isInitiator; public IPEndPoint SuccesfulEndpoint; @@ -27,10 +30,11 @@ internal class ClientUdpHolepunchState : ConversationStateBase private byte[] otherPublicKey; public byte[] SharedSecret; public ChannelInfo info; - public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, ChannelInfo info) : base(stateId, 20000) + public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 20000) { this.destId = destId; this.connection = connection; + this.serverEndpoint = serverEndpoint; this.info = info; } @@ -38,6 +42,8 @@ public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection public void Start() { isInitiator = true; + Log(StateId.ToString()); + int port = StartUdpSocket(); var msg = CreateEnvelope(); @@ -47,9 +53,9 @@ public void Start() msg.KeyValuePairs["Type"] = ((int)info.ChannelType).ToString(); msg.KeyValuePairs["Name"] = info.ChannelName; - if(info.RequiresKeyExchange()) + if (info.RequiresKeyExchange()) msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); - + msg.To = destId; connection.SendAsyncMessage(msg); @@ -71,7 +77,7 @@ public override void HandleMessage(MessageEnvelope message) break; case InternalConstants.PunchSuccesAck: - HandleRemoteSucces(); + HandleRemoteSucces(message); break; case InternalConstants.PunchFailAck: HandleFailure(); @@ -82,6 +88,8 @@ public override void HandleMessage(MessageEnvelope message) // the destination peer of hp private void HandleRemoteHpRequest(MessageEnvelope message) { + Log(StateId.ToString()); + info = new ChannelInfo(); info.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); info.ChannelName = message.KeyValuePairs["Name"]; @@ -100,54 +108,81 @@ private void HandleRemoteHpRequest(MessageEnvelope message) connection.SendAsyncMessage(msg); } - + byte[] zeros = new byte[4]; private void StartHolepunchRoutine(MessageEnvelope message) { var epMsg = KnownTypeSerializer.DeserializeEndpointTransferMessage(message.Payload, message.PayloadOffset); var time = double.Parse(message.KeyValuePairs["Time"]); - if(info.RequiresKeyExchange()) + + if (info.RequiresKeyExchange()) otherPublicKey = Convert.FromBase64String(message.KeyValuePairs["DH"]); - - foreach (EndpointData localEp in epMsg.LocalEndpoints) + // if there are local endpoints to test + if (epMsg.LocalEndpoints.Count > 0) { - for (int i = 0; i < 2; i++) + var now0 = connection.GetTime(); + var delay0 = (time - now0) / 4; + PreciseTimeAwaiter.Wait(delay0); + + foreach (EndpointData localEp in epMsg.LocalEndpoints) { - TryPunch(localEp, UdpFlags.HP); - PreciseTimeAwaiter.Wait(20); - if (IsCompleted()) return; + for (int i = 0; i < 2; i++) + { + TryPunch(localEp, UdpFlags.HP); + PreciseTimeAwaiter.Wait(20); + if (IsCompleted()) return; + } } } + if (IsCompleted()) return; - EndpointData publicEp = new EndpointData() { Ip = epMsg.IpRemote, Port = epMsg.PortRemote }; + + + // use server ip, peer is on same network as server + bool useServerIp = epMsg.IpRemote.SequenceEqual(zeros); + EndpointData publicEp = new EndpointData() { Ip = useServerIp?serverEndpoint.Ip: epMsg.IpRemote, Port = epMsg.PortRemote }; + var now = connection.GetTime(); - var delay = now - time; - if(delay>500) + var delay = time - now; + if (delay > 500) delay = 0; + Log("Delay: " + delay.ToString() + "ms"); PreciseTimeAwaiter.Wait(delay); if (IsCompleted()) return; for (int i = 0; i < 5; i++) { - TryPunch(publicEp,UdpFlags.HP); - PreciseTimeAwaiter.Wait(20 * i*i); + TryPunch(publicEp, UdpFlags.HP); + PreciseTimeAwaiter.Wait(20 * i * i); if (IsCompleted()) return; } } - private void TryPunch(EndpointData ep, UdpFlags flag ) + private void TryPunch(EndpointData ep, UdpFlags flag) { var ipep = ep.ToIpEndpoint(); TryPunch(ipep, flag); } - + private object m = new object(); + PooledMemoryStream stream = new PooledMemoryStream(); private void TryPunch(IPEndPoint ep, UdpFlags flag) { - Log("SendingTo " + ep.ToString()); - Socket.SendTo(new byte[1] { (byte)flag }, SocketFlags.None, ep); + lock (m) + { + + Log($"Sending {flag.ToString() }To " + ep.ToString()); + + stream.Position = 0; + stream.WriteByte((byte)flag); + var epd = new EndpointData(ep); + KnownTypeSerializer.SerializeEndpointData(stream, epd); + + Socket.SendTo(stream.GetBuffer(),stream.Position32, SocketFlags.None, ep); + } + } private int StartUdpSocket() @@ -163,11 +198,11 @@ private int StartUdpSocket() return ((IPEndPoint)Socket.LocalEndPoint).Port; } - private async void Receive() { var buffer = BufferPool.RentBuffer(64000); int receivedOnce = 0; + int receivedAck = 0; while (true) { try @@ -179,45 +214,54 @@ private async void Receive() if (completedTask == receiveTask) { SocketReceiveFromResult received = receiveTask.Result; - if (received.ReceivedBytes != 1) - { - Cancel(); - return; - } - + if (buffer[0] == (byte)UdpFlags.HP) { // this must be only once - if (Interlocked.CompareExchange(ref receivedOnce, 1,0) ==0) - TryPunch((IPEndPoint)received.RemoteEndPoint, UdpFlags.HPAck); + if (Interlocked.CompareExchange(ref receivedOnce, 1, 0) == 0) + { + var ipep = (IPEndPoint)received.RemoteEndPoint; + Log("[-]Received 0xFF from " + ipep.ToString()); + TryPunch((IPEndPoint)received.RemoteEndPoint, UdpFlags.HPAck); + } - Log("[-]Received 0xFF from " + ((IPEndPoint)received.RemoteEndPoint).ToString()); } else if (buffer[0] == (byte)UdpFlags.HPAck) { - ReceivedBidirectional(received.RemoteEndPoint); + Log("[+]Received 0x0F From " + ((IPEndPoint)received.RemoteEndPoint).ToString()); + + if (Interlocked.CompareExchange(ref receivedAck, 1, 0) == 0) + { + TryPunch((IPEndPoint)received.RemoteEndPoint, UdpFlags.HPAck); + ReceivedBidirectional(received.RemoteEndPoint); + } return; } else { + Log("Cancel1"); Cancel(); + return; } } else { TimedOut(); + return; } } - catch + catch(Exception e) { + Log("ERROR" + e.Message); Cancel(); + return; } finally { BufferPool.ReturnBuffer(buffer); } } - + } private void ReceivedBidirectional(EndPoint remoteEndPoint) @@ -230,8 +274,6 @@ private void ReceivedBidirectional(EndPoint remoteEndPoint) if (Interlocked.CompareExchange(ref SuccesfulEndpoint, ipep, null) != null) return; - Log("Received From " + SuccesfulEndpoint.ToString()); - var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchSucces; connection.SendAsyncMessage(msg); @@ -239,6 +281,7 @@ private void ReceivedBidirectional(EndPoint remoteEndPoint) private void TimedOut() { + Log("Timed out"); var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchFail; connection.SendAsyncMessage(msg); @@ -247,36 +290,44 @@ private void TimedOut() private void HandleFailure() { + Log("Failed Punch"); + Completed(false); } - private void HandleRemoteSucces() + private void HandleRemoteSucces(MessageEnvelope message) { - // Completed(true); + + if (info.RequiresKeyExchange()) + SharedSecret = df.CalculateSharedSecret(otherPublicKey); - remoteSucces = true; - CheckSucces(); + Log("Punched"); + Completed(true); } - private void CheckSucces() + protected override void Completed(bool succes) { - if (SuccesfulEndpoint != null && remoteSucces) + base.Completed(succes); + if (!IsSuccesful) { - if (info.RequiresKeyExchange()) - SharedSecret = df.CalculateSharedSecret(otherPublicKey); - - Completed(true); + try + { + Socket?.Close(); + Socket?.Dispose(); + } + catch { } } } - + private void Log(string log) { + return; string prefix = isInitiator ? "A: " : "B: "; - Console.WriteLine(prefix+log); + Console.WriteLine(prefix + log); } } - + } diff --git a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs index 5e5b4df..a9c091a 100644 --- a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs +++ b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs @@ -78,7 +78,7 @@ protected bool IsCompleted() } - protected void Completed(bool succes) + protected virtual void Completed(bool succes) { if (Interlocked.CompareExchange(ref isComplete, 1, 0) == 0) { diff --git a/NetworkLibrary/DistributedP2P/Components/IPHelper.cs b/NetworkLibrary/DistributedP2P/Components/IPHelper.cs index 76c19c2..5bd8832 100644 --- a/NetworkLibrary/DistributedP2P/Components/IPHelper.cs +++ b/NetworkLibrary/DistributedP2P/Components/IPHelper.cs @@ -39,7 +39,7 @@ public static List GetLocalIPAddresses4() public static bool IsPrivateIPAddress(IPEndPoint endpont) { IPAddress address = endpont.Address; - byte[] bytes = address.GetAddressBytes(); + byte[] bytes = address.MapToIPv4().GetAddressBytes(); // 10.0.0.0 - 10.255.255.255 (10/8 prefix) @@ -74,7 +74,7 @@ public static bool IsPrivateIPAddress(string ipAddress) if (address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) return false; - byte[] bytes = address.GetAddressBytes(); + byte[] bytes = address.MapToIPv4().GetAddressBytes(); // 10.0.0.0 - 10.255.255.255 (10/8 prefix) diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs index 1b6beff..42a786f 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs @@ -123,17 +123,26 @@ private void ObtainIpEndpoints(ServerSession sesFrom, ServerSession sesTo, out E out List Locals_From_NeedsToKnow, out List Locals_To_NeedsToKnow); - foreach (var local in Locals_From_NeedsToKnow) - { - EndpointData data = new EndpointData(local, toPort); - FromNeedsToKnow.LocalEndpoints.Add(data); - } + + //They need to know locals when both peers have public IPs same(coming from same NAT) + // or peers and server inside LAN, means both peers have private IPs on public ip. - foreach (var local in Locals_To_NeedsToKnow) + if ((sesFrom.ClientPublicIp.Address.Equals( sesTo.ClientPublicIp.Address)) || + (IPHelper.IsPrivateIPAddress(sesFrom.ClientPublicIp) && IPHelper.IsPrivateIPAddress(sesTo.ClientPublicIp))) { - EndpointData data = new EndpointData(local, fromPort); - ToNeedsToKnow.LocalEndpoints.Add(data); + foreach (var local in Locals_From_NeedsToKnow) + { + EndpointData data = new EndpointData(local, toPort); + FromNeedsToKnow.LocalEndpoints.Add(data); + } + + foreach (var local in Locals_To_NeedsToKnow) + { + EndpointData data = new EndpointData(local, fromPort); + ToNeedsToKnow.LocalEndpoints.Add(data); + } } + // "From" is same network as the server. if (IPHelper.IsPrivateIPAddress(sesFrom.ClientPublicIp)) @@ -182,8 +191,8 @@ private void HandleSucces(MessageEnvelope message) var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchSuccesAck; - - if(message.From == From) + msg.SetPayload(message.Payload, message.PayloadOffset, message.PayloadCount); + if (message.From == From) connection.SendAsyncMessage(To, msg); else if(message.From == To) connection.SendAsyncMessage(From, msg); From 176cabd3e30dd3f7ee4ec601baca7bfbde9cc3c8 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Mon, 7 Apr 2025 17:00:06 +0200 Subject: [PATCH 14/27] tcp holepunch on distributed p2p.To be tested on internet --- .../Crypto/DiffieHellman/DiffieHellman.cs | 4 +- .../DistributedP2P/Channels/RawTcpSocket.cs | 22 -- .../DistributedP2P/Channels/RawUdpSocket.cs | 22 -- ...eMessageChannel.cs => SecureTcpChannel.cs} | 15 +- ...pMessageChannel.cs => SecureUdpChannel.cs} | 4 +- .../{ByteMessageChannel.cs => TcpChannel.cs} | 4 +- .../DistributedP2P/Channels/TcpChannel2.cs | 223 ++++++++++++ .../DistributedP2P/Channels/UdpChannel.cs | 181 +++++----- .../DistributedP2P/Channels/UdpChannelBase.cs | 125 +++++++ .../Channels/UdpMessageChannel.cs | 150 -------- .../DistributedP2P/Client/ChannelInfo.cs | 6 +- .../Client/DistributedLobbyClient.cs | 300 ++++++++-------- .../Client/StateManagement/ClientPipeState.cs | 16 +- .../ClientTcpHolepunchState.cs | 321 ++++++++++++++++++ .../ClientUdpHolepunchState.cs | 29 +- .../DistributedP2P/Components/IPHelper.cs | 82 ++++- .../Components/InternalConstants.cs | 4 +- .../Components/PreciseTimeAwaiter.cs | 5 +- .../Server/DistributedLobbyServer.cs | 6 + .../Server/IDistributedConnection.cs | 2 +- .../ServerTcpHolepunchState.cs | 187 ++++++++++ .../ServerUdpHolepunchState.cs | 67 +--- NetworkLibrary/TCP/Base/TcpSession.cs | 69 +--- .../DistributedP2P/DistP2PServerclientTest.cs | 192 +++++++++-- 24 files changed, 1399 insertions(+), 637 deletions(-) delete mode 100644 NetworkLibrary/DistributedP2P/Channels/RawTcpSocket.cs delete mode 100644 NetworkLibrary/DistributedP2P/Channels/RawUdpSocket.cs rename NetworkLibrary/DistributedP2P/Channels/{SecureByteMessageChannel.cs => SecureTcpChannel.cs} (78%) rename NetworkLibrary/DistributedP2P/Channels/{SecureUdpMessageChannel.cs => SecureUdpChannel.cs} (93%) rename NetworkLibrary/DistributedP2P/Channels/{ByteMessageChannel.cs => TcpChannel.cs} (90%) create mode 100644 NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs create mode 100644 NetworkLibrary/DistributedP2P/Channels/UdpChannelBase.cs delete mode 100644 NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs diff --git a/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs b/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs index d083107..6424e6e 100644 --- a/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs +++ b/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs @@ -8,7 +8,7 @@ namespace NetworkLibrary.Components.Crypto.DiffieHellman { public class DiffieHellman { - // Well-known prime for DH(the well-known 2048-bit MODP group from RFC 3526) + // 2048-bit MODP group from RFC 3526 private const string PrimeHex = "0FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BB" + "EA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E" + "9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361" + @@ -18,7 +18,7 @@ public class DiffieHellman private static readonly BigInteger _prime = BigInteger.Parse(PrimeHex, System.Globalization.NumberStyles.HexNumber); - private static readonly BigInteger Generator = new BigInteger(2); + private static readonly BigInteger Generator = new BigInteger(2); // 2 is also from RFC 3526 private readonly BigInteger _privateKey; private readonly BigInteger _publicKey; diff --git a/NetworkLibrary/DistributedP2P/Channels/RawTcpSocket.cs b/NetworkLibrary/DistributedP2P/Channels/RawTcpSocket.cs deleted file mode 100644 index 628731b..0000000 --- a/NetworkLibrary/DistributedP2P/Channels/RawTcpSocket.cs +++ /dev/null @@ -1,22 +0,0 @@ -using NetworkLibrary.DistributedP2P.Client; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Net.Sockets; -using System.Text; - -namespace NetworkLibrary.DistributedP2P.Channels -{ - public class RawTcpSocket : IChannel - { - public Socket socket; - public RawTcpSocket(ChannelInfo info, Socket socket) - { - this.socket = socket; - Info = info; - } - - public ChannelInfo Info { get; } - - } -} diff --git a/NetworkLibrary/DistributedP2P/Channels/RawUdpSocket.cs b/NetworkLibrary/DistributedP2P/Channels/RawUdpSocket.cs deleted file mode 100644 index b535c3d..0000000 --- a/NetworkLibrary/DistributedP2P/Channels/RawUdpSocket.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Sockets; -using System.Text; -using NetworkLibrary.DistributedP2P.Client; - - -namespace NetworkLibrary.DistributedP2P.Channels -{ - public class RawUdpSocket:IChannel - { - public Socket socket; - public RawUdpSocket(ChannelInfo info,Socket socket ) - { - this.socket = socket; - Info = info; - } - - public ChannelInfo Info { get; } - - } -} diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureByteMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs similarity index 78% rename from NetworkLibrary/DistributedP2P/Channels/SecureByteMessageChannel.cs rename to NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs index d7e1bc6..0bea54b 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureByteMessageChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs @@ -7,7 +7,7 @@ namespace NetworkLibrary.DistributedP2P.Channels { - public class SecureByteMessageChannel : IChannel + public class SecureTcpChannel : IChannel { AesTcpClient client; AesTcpServer server; @@ -19,7 +19,7 @@ public class SecureByteMessageChannel : IChannel public event Action BytesReceived; public event Action Disconnected; - public SecureByteMessageChannel(AesTcpClient client, ChannelInfo info) + public SecureTcpChannel(AesTcpClient client, ChannelInfo info) { this.client = client; Info = info; @@ -29,17 +29,6 @@ public SecureByteMessageChannel(AesTcpClient client, ChannelInfo info) client.OnDisconnected += ClientDisconnected; } - public SecureByteMessageChannel(AesTcpServer server, ChannelInfo info) - { - this.server = server; - Info = info; - - clientId = server.Sessions.First().Key; - server.OnBytesReceived += ServerBytesReceived; - server.OnClientDisconnected += ServerClientDisconnected; - } - - private void ServerBytesReceived(Guid guid, byte[] bytes, int offset, int count) { ClientBytesReceived(bytes, offset, count); diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs similarity index 93% rename from NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs rename to NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs index 0383f71..8b2412d 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureUdpMessageChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs @@ -12,11 +12,11 @@ namespace NetworkLibrary.DistributedP2P.Channels { - public class SecureUdpMessageChannel:UdpMessageChannel + public class SecureUdpChannel:UdpChannel { private readonly ConcurrentAesAlgorithm algo; byte[] decryptBuff = new byte[65555]; - public SecureUdpMessageChannel(Socket udpSocket, IPEndPoint receiveEp, ConcurrentAesAlgorithm algo, ChannelInfo info) : base(udpSocket, receiveEp, info) + public SecureUdpChannel(Socket udpSocket, IPEndPoint receiveEp, ConcurrentAesAlgorithm algo, ChannelInfo info) : base(udpSocket, receiveEp, info) { this.algo = algo; } diff --git a/NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs similarity index 90% rename from NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs rename to NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs index 9dd1f7e..5c11e08 100644 --- a/NetworkLibrary/DistributedP2P/Channels/ByteMessageChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs @@ -7,7 +7,7 @@ namespace NetworkLibrary.DistributedP2P.Channels { - public class ByteMessageChannel : IChannel + public class TcpChannel2 : IChannel { public ChannelInfo Info { get; private set; } @@ -17,7 +17,7 @@ public class ByteMessageChannel : IChannel private ByteMessageTcpClient client; private readonly Socket connectedSocket; - public ByteMessageChannel(ChannelInfo info, Socket connectedSocket) + public TcpChannel2(ChannelInfo info, Socket connectedSocket) { Info = info; this.connectedSocket = connectedSocket; diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs new file mode 100644 index 0000000..6c6bf44 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs @@ -0,0 +1,223 @@ +using NetworkLibrary.Components; +using NetworkLibrary.Components.MessageProcessor.Unmanaged; +using NetworkLibrary.DistributedP2P.Client; +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using NetworkLibrary.Components.MessageBuffer; + +namespace NetworkLibrary.DistributedP2P.Channels +{ + public class TcpChannel:IChannel + { + public ChannelInfo Info { get; private set; } + + public event Action BytesReceived; + public event Action Disconnected; + + private readonly Socket connectedSocket; + private int totalBytesReceived; + private SocketAsyncEventArgs receiveArgs; + private int Closing = 0; + + public PooledMemoryStream sendStream = new PooledMemoryStream(); + public PooledMemoryStream flushStream = new PooledMemoryStream(); + + private object bufferMutex = new object(); + private object sendMtex = new object(); + private int sendActive = 0; + private int msgAvailable = 0; + + private SocketAsyncEventArgs sendArgs; + private ByteMessageReader reader = new ByteMessageReader(); + + public TcpChannel(ChannelInfo info, Socket connectedSocket) + { + Info = info; + this.connectedSocket = connectedSocket; + + StartReceiver(); + + sendArgs = new SocketAsyncEventArgs(); + sendArgs.Completed += Sent; + reader.OnMessageReady += (b, o, c) => BytesReceived?.Invoke(b,o,c); + } + + + public void SendAsync(byte[] buffer, int offset, int count) + { + if (IsSessionClosing()) + return; + + lock (bufferMutex) + { + sendStream.WriteInt(count);// also write flag + sendStream.Write(buffer, offset, count); + } + + SignalSend(); + } + + private void SignalSend() + { + lock (sendMtex) + { + if(Interlocked.CompareExchange(ref sendActive,1,0) == 0) + { + lock (bufferMutex) + { + var temp = sendStream; + sendStream = flushStream; + flushStream = temp; + } + sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); + if (!connectedSocket.SendAsync(sendArgs)) + { + ThreadPool.UnsafeQueueUserWorkItem(_=> Sent(null, sendArgs),null); + } + } + else + { + Interlocked.Exchange(ref msgAvailable, 1); + } + } + } + private void Sent(object sender, SocketAsyncEventArgs e) + { + if (IsSessionClosing()) + return; + + if (e.SocketError != SocketError.Success) + { + HandleError(e, "while recieving from "); + CloseChannel(); + return; + } + + else if (e.BytesTransferred == 0) + { + CloseChannel(); + return; + } + bool send = false; + lock (sendMtex) + { + if(Interlocked.CompareExchange(ref msgAvailable, 0, 1) == 1) + { + lock (bufferMutex) + { + var temp = sendStream; + sendStream = flushStream; + flushStream = temp; + } + send = true; + } + else + { + Interlocked.Exchange(ref sendActive, 0); + } + } + + if (send) + { + sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); + flushStream.Position32 = 0; + + if (!connectedSocket.SendAsync(sendArgs)) + { + Sent(null, sendArgs); + } + } + + } + + public void Start() + { + Receive(); + } + private void StartReceiver() + { + receiveArgs = new SocketAsyncEventArgs(); + var buff = new byte[1280000]; + receiveArgs.SetBuffer(buff, 0, buff.Length); + receiveArgs.Completed += Received; + + } + + private void Receive() + { + if (!connectedSocket.ReceiveAsync(receiveArgs)) + { + ThreadPool.UnsafeQueueUserWorkItem(_ => Received(null, receiveArgs), null); + } + } + + private void Received(object sender, SocketAsyncEventArgs e) + { + if (e.SocketError != SocketError.Success) + { + HandleError(e, "while recieving from "); + CloseChannel(); + return; + } + else if (e.BytesTransferred == 0) + { + CloseChannel(); + return; + } + totalBytesReceived += e.BytesTransferred; + try + { + HandleReceived(e.Buffer, e.Offset, e.BytesTransferred); + } + catch (Exception ex) + { + Log( ex.Message + "\n" + ex.StackTrace); + CloseChannel(); + return; + } + + Receive(); + } + + public void CloseChannel() + { + if(Interlocked.CompareExchange(ref Closing,1,0) == 0) + { + try + { + connectedSocket.Shutdown(SocketShutdown.Both); + } + catch { } + + } + } + + private void Log(string v) + { + + } + + private void HandleReceived(byte[] buffer, int offset, int bytesTransferred) + { + reader.ParseBytes(buffer, offset, bytesTransferred); + } + + + + private void HandleError(SocketAsyncEventArgs e, string v) + { + + } + + + + private bool IsSessionClosing() + { + return Interlocked.CompareExchange(ref Closing, 0, 0) == 1; + } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs index 0d165d2..4d3f773 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs @@ -1,125 +1,150 @@ -using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.Components.Crypto.Algorithms; +using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.UDP.Jumbo; +using NetworkLibrary.UDP.Reliable.Components; +using NetworkLibrary.Utils; using System; -using System.Collections.Generic; using System.Net; using System.Net.Sockets; -using System.Text; -using System.Threading; namespace NetworkLibrary.DistributedP2P.Channels { - public class UdpChannel:IChannel,IDisposable + public class UdpChannel : IChannel { - private Socket udpSocket; - private SocketAsyncEventArgs receiveArgs; - private readonly IPEndPoint associatedEndpoint; - public ChannelInfo Info { get; private set; } - public event Action OnMessageReceived; + private UdpChannelBase innerchannel; + + public event Action OnMessageReceived; + + protected JumboModule JumboUdp = new JumboModule(0); + internal ReliableModule ReliableUdp; public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) { - this.udpSocket = udpSocket; - this.associatedEndpoint = receiveEp; + Info = info; + innerchannel = new UdpChannelBase(udpSocket, receiveEp, info); + JumboUdp.SendToSocket = SendJumboSegment; + JumboUdp.MessageReceived = HandleMessage; + + SenderModule sender = new SenderModule(); + + sender.MaxSegmentSize = 1280; + sender.MinWindowSize = 1280*2; + + ReliableUdp = new ReliableModule(receiveEp,sender); + + ReliableUdp.OnReceived += (e, b, o, c) => HandleMessage(b, o, c); + ReliableUdp.OnSend += SendRudpSegment; + } public void Start() { - StartReceiver(); + innerchannel.OnMessageReceived += BytesReceived; + innerchannel.Start(); } - public void Send(byte[] data, int offset, int count) + protected virtual void BytesReceived(byte[] buffer, int offset, int count) { - udpSocket.SendTo(data, offset, count, SocketFlags.None, associatedEndpoint); + // filter flags + var flag = (UdpFlags)buffer[offset++]; + count--; + + switch (flag) + { + case UdpFlags.StandardMessage: + HandleMessage(buffer, offset, count); + break; + case UdpFlags.JumboMessage: + HandleJumboSegment(buffer, offset, count); + break; + case UdpFlags.ReliableMessage: + HandleRudpSegment(buffer, offset, count); + break; + case UdpFlags.KeepAliveMessage: + break; + + case UdpFlags.HP: + case UdpFlags.HPAck: + break; + } } - private void StartReceiver() + + + protected void HandleMessage(byte[] buffer, int offset, int count) { - var buff = BufferPool.RentBuffer(65536); + OnMessageReceived?.Invoke(buffer, offset, count); + } - receiveArgs = new SocketAsyncEventArgs(); - receiveArgs.SetBuffer(buff,0,buff.Length); - receiveArgs.Completed += OnReceiveCompleted; - receiveArgs.RemoteEndPoint = associatedEndpoint; - Receive(); - + protected void HandleJumboSegment(byte[] buffer, int offset, int count) + { + JumboUdp.HandleReceivedSegment(buffer, offset, count); } - private void Receive() + protected virtual void SendJumboSegment(byte[] arg1, int arg2, int arg3) { - if (!udpSocket.ReceiveFromAsync(receiveArgs)) - { - ThreadPool.UnsafeQueueUserWorkItem((s)=> OnReceiveCompleted(null, receiveArgs),null); - } + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)UdpFlags.JumboMessage); + stream.Write(arg1, arg2, arg3); + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } - private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) + protected void HandleRudpSegment(byte[] buffer, int offset, int count) { - try + ReliableUdp.HandleBytes(buffer, offset, count); + } + internal virtual void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)UdpFlags.ReliableMessage); + stream.Write(buffer, offset, count); + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + + + public virtual void Send(byte[] buffer, int offset, int count) + { + + if (count > 64000) { - if (e.SocketError != SocketError.Success) - { - HandleSocketError(e.SocketError); - return; - } - - if (e.BytesTransferred > 0) - { - - try - { - ProcessReceivedData(e.Buffer, e.Offset, e.BytesTransferred, e.RemoteEndPoint); - } - catch (Exception ex) - { - Console.WriteLine($"Error processing received data: {ex}"); - } - } - - Receive(); + JumboUdp.Send(buffer, offset, count); } - catch (Exception ex) + else { - Console.WriteLine($"Error in receive completion: {ex}"); + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)UdpFlags.StandardMessage); + stream.Write(buffer, offset, count); + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } - } - private void ProcessReceivedData(byte[] buffer, int offset, int bytesTransferred, EndPoint remoteEndPoint) - { - OnMessageReceived?.Invoke(buffer, offset, bytesTransferred); } - private void HandleSocketError(SocketError error) + public void SendReliable(byte[] buffer, int offset, int count) { - Console.WriteLine($"Socket error occurred: {error}"); + ReliableUdp.Send(buffer, offset, count); } - - public void Dispose() + protected void SendInternal(byte[] bytes, int offset, int count) { try { - if (receiveArgs != null) - { - - BufferPool.ReturnBuffer(receiveArgs.Buffer); - receiveArgs.Dispose(); - receiveArgs = null; - } - - if (udpSocket != null) - { - udpSocket.Close(); - udpSocket.Dispose(); - udpSocket = null; - } + innerchannel.Send(bytes, offset, count); } - catch { } - - } - + catch (Exception e) + { + } + + } + public void Dispose() + { + } } } diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannelBase.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannelBase.cs new file mode 100644 index 0000000..b34f45f --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannelBase.cs @@ -0,0 +1,125 @@ +using NetworkLibrary.DistributedP2P.Client; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; + +namespace NetworkLibrary.DistributedP2P.Channels +{ + internal class UdpChannelBase:IChannel,IDisposable + { + private Socket udpSocket; + private SocketAsyncEventArgs receiveArgs; + private readonly IPEndPoint associatedEndpoint; + + public ChannelInfo Info { get; private set; } + + public event Action OnMessageReceived; + + public UdpChannelBase(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) + { + this.udpSocket = udpSocket; + this.associatedEndpoint = receiveEp; + Info = info; + } + + public void Start() + { + StartReceiver(); + } + + public void Send(byte[] data, int offset, int count) + { + udpSocket.SendTo(data, offset, count, SocketFlags.None, associatedEndpoint); + } + + private void StartReceiver() + { + var buff = BufferPool.RentBuffer(65536); + + receiveArgs = new SocketAsyncEventArgs(); + receiveArgs.SetBuffer(buff,0,buff.Length); + receiveArgs.Completed += OnReceiveCompleted; + receiveArgs.RemoteEndPoint = associatedEndpoint; + Receive(); + + } + + private void Receive() + { + if (!udpSocket.ReceiveFromAsync(receiveArgs)) + { + ThreadPool.UnsafeQueueUserWorkItem((s)=> OnReceiveCompleted(null, receiveArgs),null); + } + } + + private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) + { + try + { + if (e.SocketError != SocketError.Success) + { + HandleSocketError(e.SocketError); + return; + } + + if (e.BytesTransferred > 0) + { + + try + { + ProcessReceivedData(e.Buffer, e.Offset, e.BytesTransferred, e.RemoteEndPoint); + } + catch (Exception ex) + { + Console.WriteLine($"Error processing received data: {ex}"); + } + } + + Receive(); + } + catch (Exception ex) + { + Console.WriteLine($"Error in receive completion: {ex}"); + } + } + + private void ProcessReceivedData(byte[] buffer, int offset, int bytesTransferred, EndPoint remoteEndPoint) + { + OnMessageReceived?.Invoke(buffer, offset, bytesTransferred); + } + + private void HandleSocketError(SocketError error) + { + Console.WriteLine($"Socket error occurred: {error}"); + } + + + + public void Dispose() + { + try + { + if (receiveArgs != null) + { + + BufferPool.ReturnBuffer(receiveArgs.Buffer); + receiveArgs.Dispose(); + receiveArgs = null; + } + + if (udpSocket != null) + { + udpSocket.Close(); + udpSocket.Dispose(); + udpSocket = null; + } + } + catch { } + + } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs deleted file mode 100644 index eab9a5a..0000000 --- a/NetworkLibrary/DistributedP2P/Channels/UdpMessageChannel.cs +++ /dev/null @@ -1,150 +0,0 @@ -using NetworkLibrary.Components.Crypto.Algorithms; -using NetworkLibrary.DistributedP2P.Client; -using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.UDP.Jumbo; -using NetworkLibrary.UDP.Reliable.Components; -using NetworkLibrary.Utils; -using System; -using System.Net; -using System.Net.Sockets; - -namespace NetworkLibrary.DistributedP2P.Channels -{ - public class UdpMessageChannel : IChannel - { - public ChannelInfo Info { get; private set; } - - private UdpChannel innerchannel; - - public event Action OnMessageReceived; - - protected JumboModule JumboUdp = new JumboModule(0); - internal ReliableModule ReliableUdp; - - public UdpMessageChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) - { - - Info = info; - innerchannel = new UdpChannel(udpSocket, receiveEp, info); - JumboUdp.SendToSocket = SendJumboSegment; - JumboUdp.MessageReceived = HandleMessage; - - SenderModule sender = new SenderModule(); - - sender.MaxSegmentSize = 1280; - sender.MinWindowSize = 1280*2; - - ReliableUdp = new ReliableModule(receiveEp,sender); - - ReliableUdp.OnReceived += (e, b, o, c) => HandleMessage(b, o, c); - ReliableUdp.OnSend += SendRudpSegment; - - } - - public void Start() - { - innerchannel.OnMessageReceived += BytesReceived; - innerchannel.Start(); - } - - protected virtual void BytesReceived(byte[] buffer, int offset, int count) - { - // filter flags - var flag = (UdpFlags)buffer[offset++]; - count--; - - switch (flag) - { - case UdpFlags.StandardMessage: - HandleMessage(buffer, offset, count); - break; - case UdpFlags.JumboMessage: - HandleJumboSegment(buffer, offset, count); - break; - case UdpFlags.ReliableMessage: - HandleRudpSegment(buffer, offset, count); - break; - case UdpFlags.KeepAliveMessage: - break; - - case UdpFlags.HP: - case UdpFlags.HPAck: - break; - } - } - - - - protected void HandleMessage(byte[] buffer, int offset, int count) - { - OnMessageReceived?.Invoke(buffer, offset, count); - } - - protected void HandleJumboSegment(byte[] buffer, int offset, int count) - { - JumboUdp.HandleReceivedSegment(buffer, offset, count); - } - - protected virtual void SendJumboSegment(byte[] arg1, int arg2, int arg3) - { - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.WriteByte((byte)UdpFlags.JumboMessage); - stream.Write(arg1, arg2, arg3); - SendInternal(stream.GetBuffer(), 0, stream.Position32); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); - } - - protected void HandleRudpSegment(byte[] buffer, int offset, int count) - { - ReliableUdp.HandleBytes(buffer, offset, count); - } - internal virtual void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) - { - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.WriteByte((byte)UdpFlags.ReliableMessage); - stream.Write(buffer, offset, count); - SendInternal(stream.GetBuffer(), 0, stream.Position32); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); - } - - - public virtual void Send(byte[] buffer, int offset, int count) - { - - if (count > 64000) - { - JumboUdp.Send(buffer, offset, count); - } - else - { - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.WriteByte((byte)UdpFlags.StandardMessage); - stream.Write(buffer, offset, count); - SendInternal(stream.GetBuffer(), 0, stream.Position32); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); - } - - } - - public void SendReliable(byte[] buffer, int offset, int count) - { - ReliableUdp.Send(buffer, offset, count); - } - - - protected void SendInternal(byte[] bytes, int offset, int count) - { - try - { - innerchannel.Send(bytes, offset, count); - } - catch (Exception e) - { - } - - } - public void Dispose() - { - } - } -} diff --git a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs index 35bd48d..6735d37 100644 --- a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs +++ b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs @@ -7,7 +7,7 @@ namespace NetworkLibrary.DistributedP2P.Client { public enum ChannelType { - RawTcp,RawUdp,ByteMessage,SecureByteMessage,UdpMessage,SecureUdpMessage + Tcp,SecureTcp,Udp,SecureUdp } public class ChannelInfo { @@ -27,8 +27,8 @@ public ChannelInfo(ChannelType channelType, string channelName) internal bool RequiresKeyExchange() { - if(ChannelType == ChannelType.SecureByteMessage || - ChannelType == ChannelType.SecureUdpMessage) + if(ChannelType == ChannelType.SecureTcp || + ChannelType == ChannelType.SecureUdp) return true; return false; } diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 4f121a7..9c39bb3 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -16,6 +16,7 @@ using System.Threading; using System.Threading.Tasks; using NetworkLibrary.Components; +using System.Net; namespace NetworkLibrary.DistributedP2P.Client { @@ -115,129 +116,6 @@ public Task SendMessageAndWaitResponse(Guid a, MessageEnvelope } #endregion - // we dont need this socket methods afeterall - public async Task OpenTcpSocket(Guid destinationPeer,string socketName) - { - var Info = new ChannelInfo(); - Info.ChannelName = socketName; - Info.ChannelType = ChannelType.RawTcp; - - var pipeState = new ClientPipeState(Guid.NewGuid(), this, Info); - stateManager.RegisterState(pipeState); - pipeState.Start(destinationPeer, tcp: true); - - await pipeState.WaitCompletion(); - - if (pipeState.IsSuccesful) - { - Console.WriteLine("PipeSuccesfull"); - return pipeState.ConnectedSocket; - } - return null; - } - - public async Task OpenUdpSocket(Guid destinationPeer, string socketName) - { - var Info = new ChannelInfo(); - Info.ChannelName = socketName; - Info.ChannelType = ChannelType.RawUdp; - - var pipeState = new ClientPipeState(Guid.NewGuid(), this,Info); - stateManager.RegisterState(pipeState); - pipeState.Start(destinationPeer, tcp:false); - - await pipeState.WaitCompletion(); - - if (pipeState.IsSuccesful) - { - Console.WriteLine("PipeSuccesfull"); - return pipeState.ConnectedSocket; - } - return null; - } - - - public async Task OpenTcpChannel(Guid destinationPeer, ChannelInfo Info) - { - var pipeState = new ClientPipeState(Guid.NewGuid(), this, Info); - stateManager.RegisterState(pipeState); - pipeState.Start(destinationPeer,tcp: true); - - await pipeState.WaitCompletion(); - - if (pipeState.IsSuccesful) - { - IChannel channel = CreateChannel(pipeState); - return channel; - } - return null; - } - - private void HandlePipeCreated(IConversationState state) - { - if (state.IsSuccesful) - { - var pipeState = (ClientPipeState)state; - IChannel channel = CreateChannel(pipeState); - - if (channel != null) - PeerConnected?.Invoke(channel); - - Console.WriteLine("DestPeer Conn Succesfull"); - // notify that a connection is opened, like socket accept - } - } - - private static IChannel CreateChannel(ClientPipeState pipeState) - { - IChannel channel = null; - switch (pipeState.ChannelInfo.ChannelType) - { - case ChannelType.RawTcp: - channel = new RawTcpSocket(pipeState.ChannelInfo, pipeState.ConnectedSocket); - break; - case ChannelType.RawUdp: - channel = new RawUdpSocket(pipeState.ChannelInfo, pipeState.ConnectedSocket); - break; - case ChannelType.ByteMessage: - channel = new ByteMessageChannel(pipeState.ChannelInfo, pipeState.ConnectedSocket); - break; - case ChannelType.SecureByteMessage: - var symetricKey = HKDFLite.DeriveKey(pipeState.sharedSecret, outputLength: 16); - var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey, AesMode.GCM); - AesTcpClient client = new AesTcpClient(algo, pipeState.ConnectedSocket); - channel = new SecureByteMessageChannel(client, pipeState.ChannelInfo); - break; - - } - - return channel; - } - - private async Task PerformDHWithPeer(Guid destinationPeer) - { - - DiffieHellman df = new DiffieHellman(); - byte[] publicKey = df.GetPublicKey(); - - MessageEnvelope envelope = new MessageEnvelope(); - envelope.Header = "DH"; - envelope.To = destinationPeer; - envelope.Payload = publicKey; - - var response = await SendMessageAndWaitResponse(envelope); - if (response.Header != MessageEnvelope.RequestTimeout) - { - response.LockBytes(); - byte[] dstPublic = response.Payload; - - var secret = df.CalculateSharedSecret(dstPublic); - var symetricKey = HKDFLite.DeriveKey(secret, outputLength: 16); - return symetricKey; - } - return null; - } - private void HandleServerMsg(MessageEnvelope envelope) { @@ -251,7 +129,7 @@ private void HandleServerMsg(MessageEnvelope envelope) case InternalConstants.PipeRequestTcp: case InternalConstants.PipeRequestUdp: - var pipeState = new ClientPipeState(envelope, this); + var pipeState = new ClientPipeState(envelope, this, serverEndpoint); pipeState.OnComplete += HandlePipeCreated; stateManager.RegisterState(pipeState); pipeState.HandleMessage(envelope); @@ -272,6 +150,10 @@ private void HandleServerMsg(MessageEnvelope envelope) ManageUdpHolepunchRequest(envelope); break; + case InternalConstants.RequestHolepunchTcp: + ManageTcpHolepunchRequest(envelope); + break; + } } else @@ -282,7 +164,8 @@ private void HandleServerMsg(MessageEnvelope envelope) } - + + private void PublishPeerStatusEvents(PeerStatusList statusList) { foreach (var offlineKV in statusList.WentOffline) @@ -313,31 +196,114 @@ public Dictionary GetPeerList() return copy; } - public async Task TryUdpHolePunch(Guid destination, ChannelInfo info) + + + + public async Task OpenRelayChannel(Guid destinationPeer, ChannelInfo Info) { - var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this,serverEndpoint,info); - stateManager.RegisterState(state); - state.Start(); + var pipeState = new ClientPipeState(Guid.NewGuid(), this, serverEndpoint, Info); + stateManager.RegisterState(pipeState); + pipeState.Start(destinationPeer); - await state.WaitCompletion(); + await pipeState.WaitCompletion(); + if (pipeState.IsSuccesful) + { + IChannel channel = CreateChannel(pipeState); + return channel; + } + return null; + } + + private void HandlePipeCreated(IConversationState state) + { if (state.IsSuccesful) { - if(info.ChannelType == ChannelType.SecureUdpMessage) + var pipeState = (ClientPipeState)state; + IChannel channel = CreateChannel(pipeState); + + if (channel != null) + PeerConnected?.Invoke(channel); + + Console.WriteLine("DestPeer Conn Succesfull"); + // notify that a connection is opened, like socket accept + } + } + + private static IChannel CreateChannel(ClientPipeState pipeState) + { + ChannelInfo info = pipeState.ChannelInfo; + Socket connectedSocket = pipeState.ConnectedSocket; + byte[] sharedSecret = pipeState.sharedSecret; + IPEndPoint endpoint = pipeState.SuccesfullEndpoint.ToIpEndpoint(); + ChannelType channelType = pipeState.ChannelInfo.ChannelType; + + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType); + } + + private static IChannel CreateChannel(ChannelInfo info, Socket connectedSocket, byte[] sharedSecret, IPEndPoint endpoint, ChannelType channelType) + { + IChannel channel = null; + + switch (channelType) + { + + case ChannelType.Tcp: + channel = new TcpChannel(info, connectedSocket); + break; + case ChannelType.SecureTcp: + var symetricKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); + var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey, AesMode.GCM); + AesTcpClient client = new AesTcpClient(algo, connectedSocket); + channel = new SecureTcpChannel(client, info); + break; + case ChannelType.Udp: + channel = new UdpChannel(connectedSocket, endpoint, info); + + break; + case ChannelType.SecureUdp: + var symetricKey2 = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); + var algo2 = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey2, AesMode.GCM); + channel = new SecureUdpChannel(connectedSocket, endpoint, algo2, info); + + break; + } + + return channel; + } + + public async Task TryHolePunch(Guid destination, ChannelInfo info) + { + + if(info.ChannelType == ChannelType.Udp || info.ChannelType == ChannelType.SecureUdp) + { + var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, info); + stateManager.RegisterState(state); + state.Start(); + + await state.WaitCompletion(); + + if (state.IsSuccesful) { - var key = HKDFLite.DeriveKey(state.SharedSecret, outputLength: 16); - var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(key, AesMode.GCM); - IChannel ch = new SecureUdpMessageChannel(state.Socket, state.SuccesfulEndpoint, algo, info); - return ch; + return CreateChannel(state); } - else + return null; + } + else + { + var state = new ClientTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, info); + stateManager.RegisterState(state); + state.Start(); + + await state.WaitCompletion(); + + if (state.IsSuccesful) { - IChannel ch = new UdpMessageChannel(state.Socket, state.SuccesfulEndpoint,info); - return ch; + return CreateChannel(state); } - + return null; } - return null; + } private void ManageUdpHolepunchRequest(MessageEnvelope envelope) @@ -349,27 +315,47 @@ private void ManageUdpHolepunchRequest(MessageEnvelope envelope) void State_OnComplete(IConversationState obj) { - if (state.IsSuccesful) - { - if(state.info.ChannelType == ChannelType.SecureUdpMessage) - { - var key = HKDFLite.DeriveKey(state.SharedSecret, outputLength: 16); - var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(key, AesMode.GCM); - IChannel ch = new SecureUdpMessageChannel(state.Socket, state.SuccesfulEndpoint, algo, state.info); - PeerConnected?.Invoke(ch); - } - else - { - IChannel ch = new UdpMessageChannel(state.Socket, state.SuccesfulEndpoint, state.info); - PeerConnected?.Invoke(ch); - } - - } + var ch = CreateChannel(state); + PeerConnected?.Invoke(ch); } } - + private static IChannel CreateChannel(ClientUdpHolepunchState udpHpstate) + { + ChannelInfo info = udpHpstate.ChannelInfo; + Socket connectedSocket = udpHpstate.Socket; + byte[] sharedSecret = udpHpstate.SharedSecret; + IPEndPoint endpoint = udpHpstate.SuccesfulEndpoint; + ChannelType channelType = udpHpstate.ChannelInfo.ChannelType; + + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType); + } + + private void ManageTcpHolepunchRequest(MessageEnvelope envelope) + { + var state = new ClientTcpHolepunchState(envelope.MessageId, envelope.From, this, serverEndpoint, null); + stateManager.RegisterState(state); + state.OnComplete += State_OnComplete; + state.HandleMessage(envelope); + + void State_OnComplete(IConversationState obj) + { + var ch = CreateChannel(state); + PeerConnected?.Invoke(ch); + } + } + + private static IChannel CreateChannel(ClientTcpHolepunchState TcpHpstate) + { + ChannelInfo info = TcpHpstate.ChannelInfo; + Socket connectedSocket = TcpHpstate.Socket; + byte[] sharedSecret = TcpHpstate.SharedSecret; + IPEndPoint endpoint = TcpHpstate.SuccesfulEndpoint; + ChannelType channelType = TcpHpstate.ChannelInfo.ChannelType; + + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType); + } public double GetTime() { diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index 0a7052d..0726f00 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -14,6 +14,7 @@ namespace NetworkLibrary.DistributedP2P.Client.StateManagement internal class ClientPipeState : ConversationStateBase { private readonly IDistributedConnection connection; + private readonly EndpointData serverEndpoint; private Guid destinationPeer; public Socket ConnectedSocket { get; private set; } @@ -22,20 +23,22 @@ internal class ClientPipeState : ConversationStateBase public ChannelInfo ChannelInfo; private DiffieHellman df; - public ClientPipeState(Guid stateId, IDistributedConnection connection,ChannelInfo info) : base(stateId, 20000) + public ClientPipeState(Guid stateId, IDistributedConnection connection,EndpointData serverEndpoint,ChannelInfo info) : base(stateId, 20000) { this.connection = connection; + this.serverEndpoint = serverEndpoint; this.ChannelInfo = info; } - public ClientPipeState(MessageEnvelope message,IDistributedConnection connection) : base(message.MessageId) + public ClientPipeState(MessageEnvelope message,IDistributedConnection connection, EndpointData serverEndpoint) : base(message.MessageId) { this.connection = connection; } - public void Start(Guid destinationPeer, bool tcp) + public void Start(Guid destinationPeer) { + bool tcp = ChannelInfo.ChannelType == ChannelType.Tcp || ChannelInfo.ChannelType == ChannelType.SecureTcp; this.destinationPeer = destinationPeer; var msg = CreateEnvelope(); @@ -118,6 +121,9 @@ private async void HandlePipeTokenTcp(MessageEnvelope message) foreach (EndpointData endpoint in pipeData.PipeEndpoints) { + if (IPHelper.IsZero(endpoint.Ip)) + endpoint.Ip = serverEndpoint.Ip; + Socket connected = await TryConnectWithTimeout(endpoint); if (connected != null) { @@ -261,7 +267,9 @@ private async void HandlePipeTokenUdp(MessageEnvelope message) foreach (EndpointData endpoint in pipeData.PipeEndpoints) { - + if (IPHelper.IsZero(endpoint.Ip)) + endpoint.Ip = serverEndpoint.Ip; + bool success = await UdpTokenExchange(connected, pipeData.Token, endpoint.ToIpEndpoint()); if (success) { diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs new file mode 100644 index 0000000..5c9a251 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs @@ -0,0 +1,321 @@ +using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Server; +using NetworkLibrary.P2P.Components.HolePunch; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Client.StateManagement +{ + //Todo 0.0.0.0 means server ip! + internal class ClientTcpHolepunchState : ConversationStateBase + { + private readonly Guid destId; + private readonly IDistributedConnection connection; + private readonly EndpointData serverEndpoint; + private bool isInitiator; + public Socket Socket; + public IPEndPoint SuccesfulEndpoint; + + private DiffieHellman df = new DiffieHellman(); + private byte[] otherPublicKey; + public byte[] SharedSecret; + public ChannelInfo ChannelInfo; + + private int localPort; + private Socket listeningSocket; + private Socket acceptedSocket; + private Socket connectedSocket; + private int established = 0; + private bool IsEstablished => Interlocked.CompareExchange(ref established, 0, 0) == 1; + public ClientTcpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 5000) + { + this.destId = destId; + this.connection = connection; + this.serverEndpoint = serverEndpoint; + this.ChannelInfo = info; + } + + //the initiator + public void Start() + { + isInitiator = true; + Log(StateId.ToString()); + + localPort = StartTcpSocket(); + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.RequestHolepunchTcp; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Port"] = localPort.ToString(); + msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); + msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; + + if (ChannelInfo.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + + msg.To = destId; + + connection.SendAsyncMessage(msg); + } + + + + public override void HandleMessage(MessageEnvelope message) + { + switch (message.Header) + { + case InternalConstants.RequestHolepunchTcp: + HandleRemoteHpRequest(message); + break; + + case InternalConstants.StartHP: + message.LockBytes(); + ThreadPool.UnsafeQueueUserWorkItem((s) => StartHolepunchRoutine(message), null); + break; + + case InternalConstants.PunchSuccesAck: + HandleRemoteSucces(message); + break; + case InternalConstants.PunchFailAck: + HandleFailure(); + break; + } + } + + // the destination peer of hp + private void HandleRemoteHpRequest(MessageEnvelope message) + { + Log(StateId.ToString()); + + ChannelInfo = new ChannelInfo(); + ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); + ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; + + int port = StartTcpSocket(); + var msg = CreateEnvelope(); + msg.Header = InternalConstants.AckRequestHolepunchTcp; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Port"] = port.ToString(); + + if (ChannelInfo.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + + msg.To = destId; + + connection.SendAsyncMessage(msg); + } + + private void StartHolepunchRoutine(MessageEnvelope message) + { + var epMsg = KnownTypeSerializer.DeserializeEndpointTransferMessage(message.Payload, message.PayloadOffset); + var time = double.Parse(message.KeyValuePairs["Time"]); + + if (ChannelInfo.RequiresKeyExchange()) + otherPublicKey = Convert.FromBase64String(message.KeyValuePairs["DH"]); + + // if there are local endpoints to test + if (epMsg.LocalEndpoints.Count > 0) + { + foreach (EndpointData localEp in epMsg.LocalEndpoints) + { + TryConnect(localEp, 1000); + if (IsEstablished) return; + } + } + + if (IsEstablished) return; + + // use server ip, peer is on same network as server + bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); + EndpointData publicEp = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; + + var now = connection.GetTime(); + var delay = time - now; + + Log("Delay: " + delay.ToString() + "ms"); + + PreciseTimeAwaiter.Wait(delay); + if (IsEstablished) return; + + var nextTryTime = connection.GetTime()+1000; + + for (int i = 0; i < 2; i++) + { + TryConnect(publicEp, (2000)); + PreciseTimeAwaiter.Wait(nextTryTime - connection.GetTime()); + if (IsEstablished) return; + } + + } + + + private async void TryConnect(EndpointData endpoint, int timeoutMs = 600) + { + Socket connectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + try + { + connectSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + connectSocket.Bind(new IPEndPoint(IPAddress.Any, localPort)); + + var connectTask = connectSocket.ConnectAsync(endpoint.ToIpEndpoint()); + var timeoutTask = Task.Delay(timeoutMs); + + if (await Task.WhenAny(connectTask, timeoutTask) == connectTask) + { + connectSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); + HandleConnectedSocket(connectSocket); + + } + else + { + Log($"Connection to {endpoint.ToIpEndpoint()} timed out after {timeoutMs}ms"); + connectSocket.Close(); + } + } + catch (Exception ex) + { + Log($"Connect attempt to {endpoint.ToIpEndpoint()} failed: {ex.Message}"); + connectSocket.Close(); + } + } + + + private int StartTcpSocket() + { + listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + listeningSocket.SendBufferSize = 12800000; + listeningSocket.ReceiveBufferSize = 12800000; + listeningSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + + listeningSocket.Bind(new IPEndPoint(IPAddress.Any, 0)); + + Listen(); + + return ((IPEndPoint)listeningSocket.LocalEndPoint).Port; + } + + private void Listen() + { + listeningSocket.Listen(1); + listeningSocket.BeginAccept(new AsyncCallback(AcceptCallback), listeningSocket); + } + + private void AcceptCallback(IAsyncResult ar) + { + try + { + Socket listener = (Socket)ar.AsyncState; + Socket handler = listener.EndAccept(ar); + + HandleAcceptedSocket(handler); + } + catch (Exception e) + { + Log("Failed Accept" + e.Message); + } + + } + + private void HandleConnectedSocket(Socket socket) + { + Interlocked.Exchange(ref established, 1); + + Log($"Successfully connected to {(IPEndPoint)socket.RemoteEndPoint}"); + connectedSocket = socket; + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSucces; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Status"] = "Connected"; + connection.SendAsyncMessage(msg); + } + + private void HandleAcceptedSocket(Socket socket) + { + Interlocked.Exchange(ref established, 1); + + Log($"Successfully accepted {(IPEndPoint)socket.RemoteEndPoint}"); + acceptedSocket = socket; + + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSucces; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Status"] = "Accepted"; + connection.SendAsyncMessage(msg); + } + + private void TimedOut() + { + Log("Timed out"); + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchFail; + connection.SendAsyncMessage(msg); + Cancel(); + } + + private void HandleFailure() + { + Log("Failed Punch"); + Completed(false); + } + + private void HandleRemoteSucces(MessageEnvelope message) + { + string use = message.KeyValuePairs["Use"]; + if (ChannelInfo.RequiresKeyExchange()) + SharedSecret = df.CalculateSharedSecret(otherPublicKey); + + if (use == "Connected") + { + Socket = connectedSocket; + } + else + { + Socket = acceptedSocket; + } + + SuccesfulEndpoint = (IPEndPoint)Socket.RemoteEndPoint; + Log("Punched"); + Completed(true); + } + + protected override void Completed(bool succes) + { + base.Completed(succes); + + try + { + if (!IsSuccesful) + { + acceptedSocket?.Close(); + acceptedSocket?.Dispose(); + connectedSocket?.Close(); + connectedSocket?.Dispose(); + } + + listeningSocket?.Close(); + listeningSocket?.Dispose(); + } + catch { } + + } + + private void Log(string log) + { + //return; + string prefix = isInitiator ? "A: " : "B: "; + Console.WriteLine(prefix + log); + } + + + } + + +} diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 50c554c..98e3542 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -29,13 +29,13 @@ internal class ClientUdpHolepunchState : ConversationStateBase private DiffieHellman df = new DiffieHellman(); private byte[] otherPublicKey; public byte[] SharedSecret; - public ChannelInfo info; + public ChannelInfo ChannelInfo; public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 20000) { this.destId = destId; this.connection = connection; this.serverEndpoint = serverEndpoint; - this.info = info; + this.ChannelInfo = info; } //the initiator @@ -50,10 +50,10 @@ public void Start() msg.Header = InternalConstants.RequestHolepunchUdp; msg.KeyValuePairs = new Dictionary(); msg.KeyValuePairs["Port"] = port.ToString(); - msg.KeyValuePairs["Type"] = ((int)info.ChannelType).ToString(); - msg.KeyValuePairs["Name"] = info.ChannelName; + msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); + msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; - if (info.RequiresKeyExchange()) + if (ChannelInfo.RequiresKeyExchange()) msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); msg.To = destId; @@ -71,7 +71,7 @@ public override void HandleMessage(MessageEnvelope message) HandleRemoteHpRequest(message); break; - case InternalConstants.StartHPUdp: + case InternalConstants.StartHP: message.LockBytes(); ThreadPool.UnsafeQueueUserWorkItem((s) => StartHolepunchRoutine(message), null); break; @@ -90,9 +90,9 @@ private void HandleRemoteHpRequest(MessageEnvelope message) { Log(StateId.ToString()); - info = new ChannelInfo(); - info.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); - info.ChannelName = message.KeyValuePairs["Name"]; + ChannelInfo = new ChannelInfo(); + ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); + ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; int port = StartUdpSocket(); var msg = CreateEnvelope(); @@ -100,7 +100,7 @@ private void HandleRemoteHpRequest(MessageEnvelope message) msg.KeyValuePairs = new Dictionary(); msg.KeyValuePairs["Port"] = port.ToString(); - if (info.RequiresKeyExchange()) + if (ChannelInfo.RequiresKeyExchange()) msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); msg.To = destId; @@ -108,13 +108,12 @@ private void HandleRemoteHpRequest(MessageEnvelope message) connection.SendAsyncMessage(msg); } - byte[] zeros = new byte[4]; private void StartHolepunchRoutine(MessageEnvelope message) { var epMsg = KnownTypeSerializer.DeserializeEndpointTransferMessage(message.Payload, message.PayloadOffset); var time = double.Parse(message.KeyValuePairs["Time"]); - if (info.RequiresKeyExchange()) + if (ChannelInfo.RequiresKeyExchange()) otherPublicKey = Convert.FromBase64String(message.KeyValuePairs["DH"]); // if there are local endpoints to test @@ -138,9 +137,8 @@ private void StartHolepunchRoutine(MessageEnvelope message) if (IsCompleted()) return; - // use server ip, peer is on same network as server - bool useServerIp = epMsg.IpRemote.SequenceEqual(zeros); + bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); EndpointData publicEp = new EndpointData() { Ip = useServerIp?serverEndpoint.Ip: epMsg.IpRemote, Port = epMsg.PortRemote }; @@ -148,6 +146,7 @@ private void StartHolepunchRoutine(MessageEnvelope message) var delay = time - now; if (delay > 500) delay = 0; + Log("Delay: " + delay.ToString() + "ms"); PreciseTimeAwaiter.Wait(delay); if (IsCompleted()) return; @@ -298,7 +297,7 @@ private void HandleFailure() private void HandleRemoteSucces(MessageEnvelope message) { - if (info.RequiresKeyExchange()) + if (ChannelInfo.RequiresKeyExchange()) SharedSecret = df.CalculateSharedSecret(otherPublicKey); Log("Punched"); diff --git a/NetworkLibrary/DistributedP2P/Components/IPHelper.cs b/NetworkLibrary/DistributedP2P/Components/IPHelper.cs index 5bd8832..16cd4a3 100644 --- a/NetworkLibrary/DistributedP2P/Components/IPHelper.cs +++ b/NetworkLibrary/DistributedP2P/Components/IPHelper.cs @@ -1,9 +1,11 @@ -using System; +using NetworkLibrary.DistributedP2P.Server; +using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Text; +using NetworkLibrary.P2P.Components.HolePunch; namespace NetworkLibrary.DistributedP2P.Components { @@ -39,7 +41,7 @@ public static List GetLocalIPAddresses4() public static bool IsPrivateIPAddress(IPEndPoint endpont) { IPAddress address = endpont.Address; - byte[] bytes = address.MapToIPv4().GetAddressBytes(); + byte[] bytes = address.GetAddressBytes(); // 10.0.0.0 - 10.255.255.255 (10/8 prefix) @@ -74,7 +76,7 @@ public static bool IsPrivateIPAddress(string ipAddress) if (address.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) return false; - byte[] bytes = address.MapToIPv4().GetAddressBytes(); + byte[] bytes = address.GetAddressBytes(); // 10.0.0.0 - 10.255.255.255 (10/8 prefix) @@ -164,7 +166,81 @@ private static string GetSubnet(string ip) return ip; // fallback for invalid IPs (though shouldn't happen with valid IPs) } + public static bool IsZero(byte[] ipRemote) + { + if (ipRemote.Length != 4) + throw new InvalidOperationException("Ip must be 4 bytes"); + + unsafe + { + fixed (byte* ptr = ipRemote) + { + return *((int*)ptr) == 0; + } + } + } + + public static void ObtainIpEndpoints(int fromPort, int toPort, ServerSession sesFrom, ServerSession sesTo, out EndpointTransferMessage FromNeedsToKnow, out EndpointTransferMessage ToNeedsToKnow) + { + FromNeedsToKnow = new EndpointTransferMessage(); + ToNeedsToKnow = new EndpointTransferMessage(); + + IPHelper.ExtractLocalIpsWithMatchingSubnet(sesFrom.ClientLocalIps, + sesTo.ClientLocalIps, + out List Locals_From_NeedsToKnow, + out List Locals_To_NeedsToKnow); + + + //They need to know locals when both peers have public IPs same(coming from same NAT) + // or peers and server inside LAN, means both peers have private IPs on public ip. + + if ((sesFrom.ClientPublicIp.Address.Equals(sesTo.ClientPublicIp.Address)) || + (IPHelper.IsPrivateIPAddress(sesFrom.ClientPublicIp) && IPHelper.IsPrivateIPAddress(sesTo.ClientPublicIp))) + { + foreach (var local in Locals_From_NeedsToKnow) + { + EndpointData data = new EndpointData(local, toPort); + FromNeedsToKnow.LocalEndpoints.Add(data); + } + foreach (var local in Locals_To_NeedsToKnow) + { + EndpointData data = new EndpointData(local, fromPort); + ToNeedsToKnow.LocalEndpoints.Add(data); + } + } + + + // "From" is same network as the server. + if (IPHelper.IsPrivateIPAddress(sesFrom.ClientPublicIp)) + { + // "To" needs to get server adress to connect + // 0.0.0.0:0 means serverIp + ToNeedsToKnow.IpRemote = new byte[4]; + ToNeedsToKnow.PortRemote = fromPort; + + } + else + { + // send just the publicIp of "From" to "To" + ToNeedsToKnow.IpRemote = sesFrom.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + ToNeedsToKnow.PortRemote = fromPort; + } + + // "To" is same network as the server. + if (IPHelper.IsPrivateIPAddress(sesTo.ClientPublicIp)) + { + //"From" needs to get Server adress + FromNeedsToKnow.IpRemote = new byte[4]; + FromNeedsToKnow.PortRemote = toPort; + } + else + { + // send just the public to "From" + FromNeedsToKnow.IpRemote = sesTo.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + FromNeedsToKnow.PortRemote = toPort; + } + } } } diff --git a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs index e54b1dd..d598ac4 100644 --- a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs +++ b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs @@ -22,10 +22,12 @@ internal class InternalConstants public const string SyncTime = "c"; public const string RequestHolepunchUdp = "d"; public const string AckRequestHolepunchUdp = "e"; - public const string StartHPUdp = "f"; + public const string StartHP = "f"; public const string PunchSucces = "g"; public const string PunchFail = "h"; public const string PunchSuccesAck = "i"; public const string PunchFailAck = "j"; + public const string RequestHolepunchTcp = "k"; + public const string AckRequestHolepunchTcp = "l"; } } diff --git a/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs b/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs index 143d680..c5f838f 100644 --- a/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs +++ b/NetworkLibrary/DistributedP2P/Components/PreciseTimeAwaiter.cs @@ -18,7 +18,10 @@ public static void Wait(double miliseconds) while (until > sw.Elapsed.TotalMilliseconds) { - Thread.SpinWait(10); + if((until - sw.Elapsed.TotalMilliseconds)>32) + Thread.Sleep(10);//~16 ms + else + Thread.SpinWait(20); } } diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 9d30a9a..180f993 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -213,6 +213,12 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) state.HandleMessage(message); break; + case InternalConstants.RequestHolepunchTcp: + var state2 = new ServerTcpHolepunchState(message.MessageId, this, sessionManager); + stateManager.RegisterState(state2); + state2.HandleMessage(message); + break; + case Constants.TimeSync: byte[] time = new byte[8]; diff --git a/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs b/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs index 680ed23..983ee1b 100644 --- a/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs +++ b/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net; using System.Text; using System.Threading.Tasks; using NetworkLibrary.DistributedP2P.Components; @@ -8,7 +9,6 @@ namespace NetworkLibrary.DistributedP2P.Server { internal interface IDistributedConnection : ITimeProvider { - void SendAsyncMessage(MessageEnvelope msgs); void SendAsyncMessage(Guid a, MessageEnvelope msgs); Task SendMessageAndWaitResponse(Guid a, MessageEnvelope msg); diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs new file mode 100644 index 0000000..6d504a5 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs @@ -0,0 +1,187 @@ +using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.Utils; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; + +namespace NetworkLibrary.DistributedP2P.Server.StateManagement +{ + internal class ServerTcpHolepunchState : ConversationStateBase + { + private readonly IDistributedConnection connection; + private readonly SessionManager sessionManager; + Guid From; + Guid To; + int fromPort; + string fromPublicKey; + int toPort; + string toPublicKey; + ChannelInfo info; + private int succesCount; + + public ServerTcpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) + { + this.connection = connection; + this.sessionManager = sessionManager; + } + + public override void HandleMessage(MessageEnvelope message) + { + switch (message.Header) + { + case InternalConstants.RequestHolepunchTcp: + HandleHolepunchRequest(message); + break; + case InternalConstants.AckRequestHolepunchTcp: + HandleHolepunchRequestAck(message); + break; + case InternalConstants.PunchSucces: + HandleSucces(message); + break; + case InternalConstants.PunchFail: + HandleFailure(message); + break; + } + } + + // obtain port from destination endpoint + private void HandleHolepunchRequest(MessageEnvelope message) + { + info = new ChannelInfo(); + info.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); + info.ChannelName = message.KeyValuePairs["Name"]; + + From = message.From; + To = message.To; + fromPort = int.Parse(message.KeyValuePairs["Port"]); + if (info.RequiresKeyExchange()) + fromPublicKey = message.KeyValuePairs["DH"]; + connection.SendAsyncMessage(message); + } + + private void HandleHolepunchRequestAck(MessageEnvelope message) + { + toPort = int.Parse(message.KeyValuePairs["Port"]); + if (info.RequiresKeyExchange()) + toPublicKey = message.KeyValuePairs["DH"]; + + //signal + double startTime = connection.GetTime(); + startTime += 500; + + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.StartHP; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Time"] = startTime.ToString(CultureInfo.InvariantCulture); + + sessionManager.GetSessionData(From, out ServerSession sesFrom); + sessionManager.GetSessionData(To, out ServerSession sesTo); + + if (sesFrom != null && sesTo != null) + { + IPHelper.ObtainIpEndpoints(fromPort,toPort,sesFrom, sesTo, out var FromNeedsToKnow, out var ToNeedsToKnow); + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.Position32 = 0; + + KnownTypeSerializer.SerializeEndpointTransferMessage(stream, FromNeedsToKnow); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + msg.To = From; + if (info.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = toPublicKey; + connection.SendAsyncMessage(msg); + + stream.Position32 = 0; + + KnownTypeSerializer.SerializeEndpointTransferMessage(stream, ToNeedsToKnow); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + msg.To = To; + if (info.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = fromPublicKey; + connection.SendAsyncMessage(msg); + } + else + { + Cancel(); + } + + } + + + + + private void HandleFailure(MessageEnvelope message) + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchFailAck; + connection.SendAsyncMessage(From, msg); + connection.SendAsyncMessage(To, msg); + Completed(false); + + } + int succCounter = 0; + private void HandleSucces(MessageEnvelope message) + { + if(Interlocked.Increment(ref succCounter) == 1) + { + var sts = message.KeyValuePairs["Status"]; + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSuccesAck; + msg.KeyValuePairs = new Dictionary(); + msg.SetPayload(message.Payload, message.PayloadOffset, message.PayloadCount); + + if (sts == "Accepted") + { + + if (message.From == From) + { + msg.KeyValuePairs["Use"] = "Connected"; + connection.SendAsyncMessage(To, msg); + + msg.KeyValuePairs["Use"] = "Accepted"; + connection.SendAsyncMessage(From, msg); + } + else if (message.From == To) + { + msg.KeyValuePairs["Use"] = "Connected"; + connection.SendAsyncMessage(From, msg); + + msg.KeyValuePairs["Use"] = "Accepted"; + connection.SendAsyncMessage(To, msg); + } + + } + else // connected + { + + if (message.From == From) + { + msg.KeyValuePairs["Use"] = "Accepted"; + connection.SendAsyncMessage(To, msg); + + msg.KeyValuePairs["Use"] = "Connected"; + connection.SendAsyncMessage(From, msg); + } + else if (message.From == To) + { + msg.KeyValuePairs["Use"] = "Accepted"; + connection.SendAsyncMessage(From, msg); + + msg.KeyValuePairs["Use"] = "Connected"; + connection.SendAsyncMessage(To, msg); + } + } + Completed(true); + + } + + } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs index 42a786f..20aed95 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs @@ -75,7 +75,7 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) var msg = CreateEnvelope(); - msg.Header = InternalConstants.StartHPUdp; + msg.Header = InternalConstants.StartHP; msg.KeyValuePairs = new Dictionary(); msg.KeyValuePairs["Time"] = startTime.ToString(CultureInfo.InvariantCulture); @@ -84,7 +84,7 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) if (sesFrom != null && sesTo != null) { - ObtainIpEndpoints(sesFrom, sesTo, out var FromNeedsToKnow, out var ToNeedsToKnow); + IPHelper.ObtainIpEndpoints(fromPort, toPort, sesFrom, sesTo, out var FromNeedsToKnow, out var ToNeedsToKnow); var stream = SharerdMemoryStreamPool.RentStreamStatic(); stream.Position32 = 0; @@ -113,69 +113,6 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) } - private void ObtainIpEndpoints(ServerSession sesFrom, ServerSession sesTo, out EndpointTransferMessage FromNeedsToKnow, out EndpointTransferMessage ToNeedsToKnow) - { - FromNeedsToKnow = new EndpointTransferMessage(); - ToNeedsToKnow = new EndpointTransferMessage(); - - IPHelper.ExtractLocalIpsWithMatchingSubnet(sesFrom.ClientLocalIps, - sesTo.ClientLocalIps, - out List Locals_From_NeedsToKnow, - out List Locals_To_NeedsToKnow); - - - //They need to know locals when both peers have public IPs same(coming from same NAT) - // or peers and server inside LAN, means both peers have private IPs on public ip. - - if ((sesFrom.ClientPublicIp.Address.Equals( sesTo.ClientPublicIp.Address)) || - (IPHelper.IsPrivateIPAddress(sesFrom.ClientPublicIp) && IPHelper.IsPrivateIPAddress(sesTo.ClientPublicIp))) - { - foreach (var local in Locals_From_NeedsToKnow) - { - EndpointData data = new EndpointData(local, toPort); - FromNeedsToKnow.LocalEndpoints.Add(data); - } - - foreach (var local in Locals_To_NeedsToKnow) - { - EndpointData data = new EndpointData(local, fromPort); - ToNeedsToKnow.LocalEndpoints.Add(data); - } - } - - - // "From" is same network as the server. - if (IPHelper.IsPrivateIPAddress(sesFrom.ClientPublicIp)) - { - // "To" needs to get server adress to connect - // 0.0.0.0:0 means serverIp - ToNeedsToKnow.IpRemote = new byte[4]; - ToNeedsToKnow.PortRemote = fromPort; - - } - else - { - // send just the publicIp of "From" to "To" - ToNeedsToKnow.IpRemote = sesFrom.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); - ToNeedsToKnow.PortRemote = fromPort; - } - - // "To" is same network as the server. - if (IPHelper.IsPrivateIPAddress(sesTo.ClientPublicIp)) - { - //"From" needs to get Server adress - FromNeedsToKnow.IpRemote = new byte[4]; - FromNeedsToKnow.PortRemote = toPort; - } - else - { - // send just the public to "From" - FromNeedsToKnow.IpRemote = sesTo.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); - FromNeedsToKnow.PortRemote = toPort; - } - - } - private void HandleFailure(MessageEnvelope message) { var msg = CreateEnvelope(); diff --git a/NetworkLibrary/TCP/Base/TcpSession.cs b/NetworkLibrary/TCP/Base/TcpSession.cs index 7879135..5b7d006 100644 --- a/NetworkLibrary/TCP/Base/TcpSession.cs +++ b/NetworkLibrary/TCP/Base/TcpSession.cs @@ -355,10 +355,7 @@ void SendOrEnqueue(byte[] bytes) [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void FlushSendBuffer(int offset, int count) { -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER - //ThreadPool.UnsafeQueueUserWorkItem((e) => SendModern(offset,count),null); - //return; -//#endif + try { totalBytesSend += count; @@ -371,70 +368,6 @@ protected void FlushSendBuffer(int offset, int count) } catch { EndSession(); ReleaseSendResourcesIdempotent(); } } -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// private async void SendModern(int offset, int count) -// { -// try -// { - - -// Top: -// await sessionSocket.SendAsync(new ReadOnlyMemory(sendBuffer, offset, count), -// SocketFlags.None).ConfigureAwait(false); - -// if (IsSessionClosing()) -// { -// SendSemaphore.Release(); -// ReleaseSendResourcesIdempotent(); -// return; -// } - -// if (messageBuffer.TryFlushQueue(ref sendBuffer, 0, out int amountWritten)) -// { -// //FlushSendBuffer(0, amountWritten); -// offset = 0; -// count = amountWritten; -// goto Top; -// } -// else -// { -// bool flushAgain = false; -// // here it means queue was empty and there was nothing to flush. -// // but this check is clearly not atomic, if during the couple cycles in between something is enqueued, -// // i have to flush that part ,or it will stuck at queue since consumer is exiting. - -// enqueueLock.Take(); -// if (!messageBuffer.IsEmpty()) -// { -// flushAgain = true; -// enqueueLock.Release(); -// } -// else -// { -// messageBuffer.Flush(); - -// SendSemaphore.Release(); -// enqueueLock.Release(); -// if (IsSessionClosing()) -// { -// ReleaseSendResourcesIdempotent(); -// } -// return; -// } - -// if (flushAgain && messageBuffer.TryFlushQueue(ref sendBuffer, 0, out amountWritten)) -// { -// // FlushSendBuffer(0, amountWritten); -// offset = 0; -// count = amountWritten; -// goto Top; -// } -// } -// } -// catch { EndSession(); ReleaseSendResourcesIdempotent(); } - -// } -//#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SendComplete(object ignored, SocketAsyncEventArgs e) diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index da4e1c1..b0520ae 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -128,8 +128,8 @@ public void PipeTest() var info = new ChannelInfo(); info.ChannelName = "Test"; - info.ChannelType = ChannelType.ByteMessage; - var channel1 = (ByteMessageChannel)distributedLobbyClient.OpenTcpChannel(distributedLobbyClient2.SessionId, info).Result; + info.ChannelType = ChannelType.Tcp; + var channel1 = (TcpChannel)distributedLobbyClient.OpenRelayChannel(distributedLobbyClient2.SessionId, info).Result; Assert.IsNotNull(channel1); @@ -145,7 +145,7 @@ public void PipeTest() void PeerConnected(IChannel channel_) { mre.WaitOne();//emulate bad syncronisation - var channel = (ByteMessageChannel)channel_; + var channel = (TcpChannel)channel_; channel.BytesReceived += Channel_BytesReceived; channel.Disconnected += Disconnected; channel.Start(); @@ -187,8 +187,8 @@ public void SecurePipeTest() var info = new ChannelInfo(); info.ChannelName = "Test"; - info.ChannelType = ChannelType.SecureByteMessage; - var channel1 = (SecureByteMessageChannel)distributedLobbyClient.OpenTcpChannel(distributedLobbyClient2.SessionId, info).Result; + info.ChannelType = ChannelType.SecureTcp; + var channel1 = (SecureTcpChannel)distributedLobbyClient.OpenRelayChannel(distributedLobbyClient2.SessionId, info).Result; Assert.IsNotNull(channel1); @@ -204,7 +204,7 @@ public void SecurePipeTest() void PeerConnected(IChannel channel_) { mre.WaitOne();//emulate bad syncronisation - var channel = (SecureByteMessageChannel)channel_; + var channel = (SecureTcpChannel)channel_; channel.BytesReceived += Channel_BytesReceived; channel.Disconnected += Disconnected; channel.Start(); @@ -232,10 +232,48 @@ void Channel_BytesReceived(byte[] buff, int offset, int count) [TestMethod] public void PipeTestUdp() { - + TaskCompletionSource tcs = new TaskCompletionSource(); + + int received = 0; + + using var server = ArrangeServer(); + var cl1 = GetClient(); + var cl2 = GetClient(); + + var res = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + cl2.PeerConnected += Cl2_PeerConnected; + + var info = new ChannelInfo(); + info.ChannelName = "Test"; + info.ChannelType = ChannelType.Udp; + var channel1 = (UdpChannel)cl1.OpenRelayChannel(cl2.SessionId, info).Result; + Assert.IsNotNull(channel1); + channel1.Start(); + + byte[] data = new byte[12800000]; + channel1.Send(data,0,data.Length); + Thread.Sleep(100); + + void Cl2_PeerConnected(IChannel obj) + { + var udpChannel = (UdpChannel)obj; + udpChannel.OnMessageReceived += (b,o,c) => + { + received = c; tcs.SetResult(true); }; + udpChannel.Start(); + } + + var ss = tcs.Task.Result; + Assert.AreEqual(received, data.Length); + } + + [TestMethod] + public void PipeTestUdpSecure() + { TaskCompletionSource tcs = new TaskCompletionSource(); - ManualResetEvent mre = new ManualResetEvent(false); int received = 0; @@ -249,27 +287,28 @@ public void PipeTestUdp() cl2.PeerConnected += Cl2_PeerConnected; var info = new ChannelInfo(); - var channel1 = cl1.OpenUdpSocket(cl2.SessionId, "Test").Result; + info.ChannelName = "Test"; + info.ChannelType = ChannelType.SecureUdp; + var channel1 = (SecureUdpChannel)cl1.OpenRelayChannel(cl2.SessionId, info).Result; Assert.IsNotNull(channel1); + channel1.Start(); - channel1.Send(new byte[1337]); + byte[] data = new byte[12800000]; + channel1.SendReliable(data, 0, data.Length); Thread.Sleep(100); - mre.Set(); void Cl2_PeerConnected(IChannel obj) { - mre.WaitOne(); - var udpSocket = (RawUdpSocket)obj; - byte[] buffer = new byte[2048]; - udpSocket.socket.ReceiveAsync(new ArraySegment(buffer), System.Net.Sockets.SocketFlags.None).ContinueWith( - t => { - received = t.Result; - tcs.SetResult(true); - }); + var udpChannel = (SecureUdpChannel)obj; + udpChannel.OnMessageReceived += (b, o, c) => + { + received = c; tcs.SetResult(true); + }; + udpChannel.Start(); } var ss = tcs.Task.Result; - Assert.AreEqual(received, 1337); + Assert.AreEqual(received, data.Length); } @@ -470,20 +509,20 @@ public void UdpHolepunch() cl2.PeerConnected += Cl2_PeerConnected; var info = new ChannelInfo(); - info.ChannelType = ChannelType.UdpMessage; + info.ChannelType = ChannelType.Udp; info.ChannelName = "Test"; - var channel1 = (UdpMessageChannel)cl1.TryUdpHolePunch(cl2.SessionId, info).Result; + var channel1 = (UdpChannel)cl1.TryHolePunch(cl2.SessionId, info).Result; Assert.IsNotNull(channel1); channel1.Start(); - var data = new byte[1280000]; + var data = new byte[12800000]; data[0] = 1; channel1.SendReliable(data, 0, data.Length); Thread.Sleep(100); void Cl2_PeerConnected(IChannel obj) { - var ch = (UdpMessageChannel)obj; + var ch = (UdpChannel)obj; ch.OnMessageReceived += Ch_OnMessageReceived; ch.Start(); } @@ -519,9 +558,9 @@ public void UdpHolepunchSecure() cl2.PeerConnected += Cl2_PeerConnected; var info = new ChannelInfo(); - info.ChannelType = ChannelType.SecureUdpMessage; + info.ChannelType = ChannelType.SecureUdp; info.ChannelName = "Test"; - var channel1 = (SecureUdpMessageChannel)cl1.TryUdpHolePunch(cl2.SessionId, info).Result; + var channel1 = (SecureUdpChannel)cl1.TryHolePunch(cl2.SessionId, info).Result; Assert.IsNotNull(channel1); channel1.Start(); @@ -532,7 +571,7 @@ public void UdpHolepunchSecure() void Cl2_PeerConnected(IChannel obj) { - var ch = (SecureUdpMessageChannel)obj; + var ch = (SecureUdpChannel)obj; ch.OnMessageReceived += Ch_OnMessageReceived; ch.Start(); } @@ -549,7 +588,104 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) } + [TestMethod] + public void TcpHolepunch() + { + + TaskCompletionSource tcs = new TaskCompletionSource(); + ManualResetEvent mre = new ManualResetEvent(false); + int received = 0; + + var cl1 = GetClient(); + var cl2 = GetClient(); + using var server = ArrangeServer(); + + var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + cl2.PeerConnected += Cl2_PeerConnected; + + var info = new ChannelInfo(); + info.ChannelType = ChannelType.Tcp; + info.ChannelName = "Test"; + var channel1 = (TcpChannel)cl1.TryHolePunch(cl2.SessionId, info).Result; + Assert.IsNotNull(channel1); + channel1.Start(); + + var data = new byte[1280]; + data[0] = 1; + channel1.SendAsync(data, 0, data.Length); + Thread.Sleep(100); + + void Cl2_PeerConnected(IChannel obj) + { + var ch = (TcpChannel)obj; + ch.BytesReceived += Ch_OnMessageReceived; + ch.Start(); + } + + void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) + { + received = arg3; + tcs.TrySetResult(true); + } + + var ss = tcs.Task.Result; + Assert.AreEqual(data.Length, received); + + + + } + + [TestMethod] + public void TcpHolepunchSecure() + { + + TaskCompletionSource tcs = new TaskCompletionSource(); + ManualResetEvent mre = new ManualResetEvent(false); + int received = 0; + + var cl1 = GetClient(); + var cl2 = GetClient(); + using var server = ArrangeServer(); + + var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + cl2.PeerConnected += Cl2_PeerConnected; + + var info = new ChannelInfo(); + info.ChannelType = ChannelType.SecureTcp; + info.ChannelName = "Test"; + var channel1 = (SecureTcpChannel)cl1.TryHolePunch(cl2.SessionId, info).Result; + Assert.IsNotNull(channel1); + channel1.Start(); + + var data = new byte[12800000]; + data[0] = 1; + channel1.SendAsync(data, 0, data.Length); + Thread.Sleep(100); + + void Cl2_PeerConnected(IChannel obj) + { + var ch = (SecureTcpChannel)obj; + ch.BytesReceived += Ch_OnMessageReceived; + ch.Start(); + } + + void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) + { + received = arg3; + tcs.TrySetResult(true); + } + + var ss = tcs.Task.Result; + Assert.AreEqual(data.Length, received); + + + + } + - } } From 4b194cf4c9181a33daebe64803abd3d9c709da19 Mon Sep 17 00:00:00 2001 From: dogancan ozturk Date: Mon, 7 Apr 2025 23:30:03 +0200 Subject: [PATCH 15/27] holepunch changes --- .../Client/DistributedLobbyClient.cs | 15 ++++++--- .../ClientTcpHolepunchState.cs | 33 +++++++++++++------ .../ServerTcpHolepunchState.cs | 11 ++++--- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 9c39bb3..7a23a78 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -315,8 +315,12 @@ private void ManageUdpHolepunchRequest(MessageEnvelope envelope) void State_OnComplete(IConversationState obj) { - var ch = CreateChannel(state); - PeerConnected?.Invoke(ch); + if (obj.IsSuccesful) + { + var ch = CreateChannel(state); + PeerConnected?.Invoke(ch); + } + } } @@ -341,8 +345,11 @@ private void ManageTcpHolepunchRequest(MessageEnvelope envelope) void State_OnComplete(IConversationState obj) { - var ch = CreateChannel(state); - PeerConnected?.Invoke(ch); + if (obj.IsSuccesful) + { + var ch = CreateChannel(state); + PeerConnected?.Invoke(ch); + } } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs index 5c9a251..ca4a2f1 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs @@ -31,6 +31,8 @@ internal class ClientTcpHolepunchState : ConversationStateBase private Socket acceptedSocket; private Socket connectedSocket; private int established = 0; + private int connected = 0; + private int accepted = 0; private bool IsEstablished => Interlocked.CompareExchange(ref established, 0, 0) == 1; public ClientTcpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 5000) { @@ -123,7 +125,8 @@ private void StartHolepunchRoutine(MessageEnvelope message) { foreach (EndpointData localEp in epMsg.LocalEndpoints) { - TryConnect(localEp, 1000); + if (TryConnect(localEp, 500)) + return; if (IsEstablished) return; } } @@ -142,46 +145,53 @@ private void StartHolepunchRoutine(MessageEnvelope message) PreciseTimeAwaiter.Wait(delay); if (IsEstablished) return; - var nextTryTime = connection.GetTime()+1000; + var nextTryTime = connection.GetTime()+1500; - for (int i = 0; i < 2; i++) + for (int i = 0; i < 4; i++) { - TryConnect(publicEp, (2000)); - PreciseTimeAwaiter.Wait(nextTryTime - connection.GetTime()); + if (TryConnect(publicEp, (1000))) + return; + //PreciseTimeAwaiter.Wait(nextTryTime - connection.GetTime()); if (IsEstablished) return; } } - private async void TryConnect(EndpointData endpoint, int timeoutMs = 600) + private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) { Socket connectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { + Log("Connecting to " + endpoint.ToIpEndpoint().ToString()); connectSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); connectSocket.Bind(new IPEndPoint(IPAddress.Any, localPort)); var connectTask = connectSocket.ConnectAsync(endpoint.ToIpEndpoint()); var timeoutTask = Task.Delay(timeoutMs); - if (await Task.WhenAny(connectTask, timeoutTask) == connectTask) + if (Task.WhenAny(connectTask, timeoutTask).GetAwaiter().GetResult() == connectTask) { + connectSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); HandleConnectedSocket(connectSocket); - + Log($"Successfully connected to {endpoint.ToIpEndpoint()}"); + return true; + } else { Log($"Connection to {endpoint.ToIpEndpoint()} timed out after {timeoutMs}ms"); connectSocket.Close(); + return false; } } catch (Exception ex) { Log($"Connect attempt to {endpoint.ToIpEndpoint()} failed: {ex.Message}"); connectSocket.Close(); + return false; } } @@ -217,7 +227,7 @@ private void AcceptCallback(IAsyncResult ar) } catch (Exception e) { - Log("Failed Accept" + e.Message); + Log("Failed Accept: " + e.Message); } } @@ -225,8 +235,9 @@ private void AcceptCallback(IAsyncResult ar) private void HandleConnectedSocket(Socket socket) { Interlocked.Exchange(ref established, 1); + if (Interlocked.Exchange(ref connected, 1) == 1) + return; - Log($"Successfully connected to {(IPEndPoint)socket.RemoteEndPoint}"); connectedSocket = socket; var msg = CreateEnvelope(); @@ -239,6 +250,8 @@ private void HandleConnectedSocket(Socket socket) private void HandleAcceptedSocket(Socket socket) { Interlocked.Exchange(ref established, 1); + if (Interlocked.Exchange(ref accepted, 1) == 1) + return; Log($"Successfully accepted {(IPEndPoint)socket.RemoteEndPoint}"); acceptedSocket = socket; diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs index 6d504a5..e9b57c7 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; namespace NetworkLibrary.DistributedP2P.Server.StateManagement @@ -69,15 +70,12 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) if (info.RequiresKeyExchange()) toPublicKey = message.KeyValuePairs["DH"]; - //signal - double startTime = connection.GetTime(); - startTime += 500; + var msg = CreateEnvelope(); msg.Header = InternalConstants.StartHP; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Time"] = startTime.ToString(CultureInfo.InvariantCulture); sessionManager.GetSessionData(From, out ServerSession sesFrom); sessionManager.GetSessionData(To, out ServerSession sesTo); @@ -86,6 +84,11 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) { IPHelper.ObtainIpEndpoints(fromPort,toPort,sesFrom, sesTo, out var FromNeedsToKnow, out var ToNeedsToKnow); + // coordination signal + double startTime = connection.GetTime(); + startTime += 1000*(1+Math.Max(FromNeedsToKnow.LocalEndpoints.Count,ToNeedsToKnow.LocalEndpoints.Count)); + msg.KeyValuePairs["Time"] = startTime.ToString(CultureInfo.InvariantCulture); + var stream = SharerdMemoryStreamPool.RentStreamStatic(); stream.Position32 = 0; From 4f0391d3e36b6cb059f93661f71a8a5aaef9f31e Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Tue, 8 Apr 2025 17:02:34 +0200 Subject: [PATCH 16/27] sequential tcp holepunch, periodic DH key exchange on secure udp channel. --- .../Crypto/DiffieHellman/DiffieHellman.cs | 2 + .../Channels/Components/ChannelFactory.cs | 91 ++++ .../Components/EphemeralKeyManager.cs | 119 +++++ .../Channels/Components/Flags.cs | 25 + .../Channels/Components/IChannel.cs | 14 + .../Channels/Components/KeepAlive.cs | 79 +++ .../{ => Components}/UdpChannelBase.cs | 24 +- .../Channels/SecureTcpChannel.cs | 1 + .../Channels/SecureUdpChannel.cs | 105 ++-- .../DistributedP2P/Channels/TcpChannel.cs | 3 +- .../DistributedP2P/Channels/TcpChannel2.cs | 106 ++-- .../DistributedP2P/Channels/UdpChannel.cs | 84 +++- .../DistributedP2P/Client/ChannelInfo.cs | 5 + .../Client/DistributedLobbyClient.cs | 127 ++--- .../DistributedP2P/Client/IChannel.cs | 11 - .../ClientTcpHolepunchState.cs | 5 +- .../ClientTcpHolepunchState2.cs | 472 ++++++++++++++++++ .../ClientUdpHolepunchState.cs | 17 +- .../Components/ConversationStateBase.cs | 2 +- .../Components/InternalConstants.cs | 5 +- .../DistributedP2P/Components/UdpFlags.cs | 19 - .../Server/DistributedLobbyServer.cs | 8 +- .../ServerTcpHolepunchState.cs | 2 +- .../ServerTcpHolepunchState2.cs | 208 ++++++++ NetworkLibrary/TCP/Base/TcpSession.cs | 4 +- .../DistributedP2P/DistP2PServerclientTest.cs | 138 ++++- 26 files changed, 1435 insertions(+), 241 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs create mode 100644 NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs create mode 100644 NetworkLibrary/DistributedP2P/Channels/Components/Flags.cs create mode 100644 NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs create mode 100644 NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs rename NetworkLibrary/DistributedP2P/Channels/{ => Components}/UdpChannelBase.cs (85%) delete mode 100644 NetworkLibrary/DistributedP2P/Client/IChannel.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs delete mode 100644 NetworkLibrary/DistributedP2P/Components/UdpFlags.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs diff --git a/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs b/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs index 6424e6e..a09a924 100644 --- a/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs +++ b/NetworkLibrary/Components/Crypto/DiffieHellman/DiffieHellman.cs @@ -40,6 +40,8 @@ public byte[] CalculateSharedSecret(byte[] otherPublicKeyBytes) return sharedSecret.ToByteArray(); } + + private BigInteger GenerateRandomPrivateKey() { // Recommended key size for security (at least 256 bits) diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs new file mode 100644 index 0000000..15ce17f --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs @@ -0,0 +1,91 @@ +using NetworkLibrary.DistributedP2P.Client.StateManagement; +using NetworkLibrary.DistributedP2P.Client; +using System; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; +using NetworkLibrary.Components.Crypto.KeyDerivation; +using NetworkLibrary.Components.Crypto; +using NetworkLibrary.TCP.AES; + +namespace NetworkLibrary.DistributedP2P.Channels.Components +{ + internal class ChannelFactory + { + public static IChannel CreateChannel(ClientTcpHolepunchState TcpHpstate, bool isInitiator) + { + ChannelInfo info = TcpHpstate.ChannelInfo; + Socket connectedSocket = TcpHpstate.Socket; + byte[] sharedSecret = TcpHpstate.SharedSecret; + IPEndPoint endpoint = TcpHpstate.SuccesfulEndpoint; + ChannelType channelType = TcpHpstate.ChannelInfo.ChannelType; + + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator); + } + + public static IChannel CreateChannel(ClientTcpHolepunchState2 TcpHpstate, bool isInitiator) + { + ChannelInfo info = TcpHpstate.ChannelInfo; + Socket connectedSocket = TcpHpstate.Socket; + byte[] sharedSecret = TcpHpstate.SharedSecret; + IPEndPoint endpoint = TcpHpstate.SuccesfulEndpoint; + ChannelType channelType = TcpHpstate.ChannelInfo.ChannelType; + + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator); + } + + public static IChannel CreateChannel(ClientUdpHolepunchState udpHpstate, bool isInitiator) + { + ChannelInfo info = udpHpstate.ChannelInfo; + Socket connectedSocket = udpHpstate.Socket; + byte[] sharedSecret = udpHpstate.SharedSecret; + IPEndPoint endpoint = udpHpstate.SuccesfulEndpoint; + ChannelType channelType = udpHpstate.ChannelInfo.ChannelType; + + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator); + } + + public static IChannel CreateChannel(ClientPipeState pipeState, bool isInitiator) + { + ChannelInfo info = pipeState.ChannelInfo; + Socket connectedSocket = pipeState.ConnectedSocket; + byte[] sharedSecret = pipeState.sharedSecret; + IPEndPoint endpoint = pipeState.SuccesfullEndpoint.ToIpEndpoint(); + ChannelType channelType = pipeState.ChannelInfo.ChannelType; + + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator); + } + + public static IChannel CreateChannel(ChannelInfo info, Socket connectedSocket, byte[] sharedSecret, IPEndPoint endpoint, ChannelType channelType, bool isInitiator) + { + IChannel channel = null; + + switch (channelType) + { + + case ChannelType.Tcp: + channel = new TcpChannel(info, connectedSocket); + break; + case ChannelType.SecureTcp: + var symetricKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); + var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey, AesMode.GCM); + AesTcpClient client = new AesTcpClient(algo, connectedSocket); + channel = new SecureTcpChannel(client, info); + break; + case ChannelType.Udp: + channel = new UdpChannel(connectedSocket, endpoint, info); + + break; + case ChannelType.SecureUdp: + var symetricKey2 = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); + var algo2 = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey2, AesMode.GCM); + channel = new SecureUdpChannel(connectedSocket, endpoint, algo2, info, isInitiator); + + break; + } + + return channel; + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs new file mode 100644 index 0000000..81e3458 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs @@ -0,0 +1,119 @@ +using NetworkLibrary.Components; +using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.Components.Crypto; +using NetworkLibrary.Utils; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using NetworkLibrary.Components.Crypto.KeyDerivation; +using NetworkLibrary.DistributedP2P.Components; + +namespace NetworkLibrary.DistributedP2P.Channels.Components +{ + internal class EphemeralKeyManager + { + public Action SendData; + + DiffieHellman df = new DiffieHellman(); + + internal ConcurrentDictionary keyStore = new ConcurrentDictionary(); + + byte[] innerBuffer = new byte[1024]; + + byte currKeyNumber = 0; + internal byte CurrKeyNumber = 0; + private readonly int rotateEveryMs; + + public EphemeralKeyManager(ConcurrentAesAlgorithm initialKey, int rotateEveryMs = 1000 ) + { + keyStore[0] = initialKey; + this.rotateEveryMs = rotateEveryMs; + TimedKeyExchange(); + } + + private void TimedKeyExchange() + { + if (rotateEveryMs < 0) + return; + TimerService.RegisterTimer(Guid.NewGuid(), rotateEveryMs, () => + { + RequestKeyExchange(); + }); + } + + public void HandleMessage(MessageFlags flag, byte[] buffer, int offset, int count ) + { + + switch (flag) + { + case MessageFlags.KeyExchange: + HandleKeyExchangeReq(buffer, offset, count); + break; + + case MessageFlags.KeyExchangeAck: + HandleKeyExchangeAck(buffer, offset, count); + break; + + case MessageFlags.KeyExchangeFin: + HandleFinalize(); + break; + } + } + + + //[Alice] + private void RequestKeyExchange() + { + df = new DiffieHellman(); + byte[] myPublic = df.GetPublicKey(); + SendData?.Invoke(MessageFlags.KeyExchange, myPublic, 0, myPublic.Length); + TimedKeyExchange(); + } + + //[Bob] + private void HandleKeyExchangeReq(byte[] buffer, int offset, int count) + { + currKeyNumber++; + + df = new DiffieHellman(); + var sharedSecret = df.CalculateSharedSecret(ByteCopy.ToArray(buffer, offset, count)); + var privKey = HKDFLite.DeriveKey(sharedSecret,outputLength:16); + var algo = new ConcurrentAesAlgorithm(privKey, AesMode.GCM); + keyStore[currKeyNumber] = algo; + + byte[] myPublic = df.GetPublicKey(); + SendData?.Invoke(MessageFlags.KeyExchangeAck, myPublic, 0, myPublic.Length); + } + + //[Alice] + private void HandleKeyExchangeAck(byte[] buffer, int offset, int count) + { + + currKeyNumber++; + + var sharedSecret = df.CalculateSharedSecret(ByteCopy.ToArray(buffer, offset, count)); + var privKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); + var algo = new ConcurrentAesAlgorithm(privKey, AesMode.GCM); + keyStore[currKeyNumber] = algo; + + SendData?.Invoke(MessageFlags.KeyExchangeFin, innerBuffer,0,1); + + CurrKeyNumber = currKeyNumber; + } + + //[Bob] + private void HandleFinalize() + { + CurrKeyNumber = currKeyNumber; + } + + + public bool GetAlgorithm(byte keyNum, out ConcurrentAesAlgorithm algo) + { + return keyStore.TryGetValue(keyNum, out algo); + } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/Flags.cs b/NetworkLibrary/DistributedP2P/Channels/Components/Flags.cs new file mode 100644 index 0000000..37bfa3f --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/Components/Flags.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Channels.Components +{ + public enum MessageFlags : byte + { + StandardMessage, + JumboMessage, + ReliableMessage, + InternalReliableMessage, + KeepAliveMessage, + HP, + HPAck, + Ping, + KeyExchange, + KeyExchangeAck, + KeyExchangeFin, + + } + + + +} diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs b/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs new file mode 100644 index 0000000..9e395e7 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs @@ -0,0 +1,14 @@ +using System; +using NetworkLibrary.DistributedP2P.Client; + +namespace NetworkLibrary.DistributedP2P.Channels.Components +{ + + public interface IChannel + { + ChannelInfo Info { get; } + + void Start(); + + } +} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs new file mode 100644 index 0000000..0363360 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Channels.Components +{ +/* + * Send keep alive every 5 seconds + * + * if reply is not received within 1 sec resend up to 5 times + * + * otherwise keep alive again 5 seconds later + * + * + */ + internal class KeepAlive + { + public Action SendData; + + DateTime lastReceived = DateTime.Now; + bool stop = false; + int maxRetry = 10; + byte[] innerBuff = new byte[1]; + public KeepAlive() + { + StartSendRoutine(); + } + + private async void StartSendRoutine() + { + while (!stop) + { + await Task.Delay(5000); + SendKeepAlive(); + + await Task.Delay(1000); + int retires = 0; + while((DateTime.Now - lastReceived).TotalMilliseconds > 1000) + { + if(retires > maxRetry) + { + DisconnectDetected(); + return; + } + + SendKeepAlive(); + await Task.Delay(1000); + } + } + } + + private void DisconnectDetected() + { + // event of DC + } + + public void HandleMessage(MessageFlags flag, byte[] buffer, int offset, int count) + { + + switch (flag) + { + case MessageFlags.KeepAliveMessage: + HandleKeepAlive(buffer, offset, count); + break; + } + } + + private void SendKeepAlive() + { + SendData?.Invoke(MessageFlags.KeepAliveMessage, innerBuff, 0, 1); + } + + private void HandleKeepAlive(byte[] buffer, int offset, int count) + { + lastReceived = DateTime.Now; + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannelBase.cs b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs similarity index 85% rename from NetworkLibrary/DistributedP2P/Channels/UdpChannelBase.cs rename to NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs index b34f45f..331fa7f 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpChannelBase.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs @@ -6,9 +6,9 @@ using System.Text; using System.Threading; -namespace NetworkLibrary.DistributedP2P.Channels +namespace NetworkLibrary.DistributedP2P.Channels.Components { - internal class UdpChannelBase:IChannel,IDisposable + internal class UdpChannelBase : IChannel, IDisposable { private Socket udpSocket; private SocketAsyncEventArgs receiveArgs; @@ -16,12 +16,12 @@ internal class UdpChannelBase:IChannel,IDisposable public ChannelInfo Info { get; private set; } - public event Action OnMessageReceived; + public event Action OnMessageReceived; public UdpChannelBase(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) { this.udpSocket = udpSocket; - this.associatedEndpoint = receiveEp; + associatedEndpoint = receiveEp; Info = info; } @@ -30,7 +30,7 @@ public void Start() StartReceiver(); } - public void Send(byte[] data, int offset, int count) + public void Send(byte[] data, int offset, int count) { udpSocket.SendTo(data, offset, count, SocketFlags.None, associatedEndpoint); } @@ -40,18 +40,18 @@ private void StartReceiver() var buff = BufferPool.RentBuffer(65536); receiveArgs = new SocketAsyncEventArgs(); - receiveArgs.SetBuffer(buff,0,buff.Length); + receiveArgs.SetBuffer(buff, 0, buff.Length); receiveArgs.Completed += OnReceiveCompleted; receiveArgs.RemoteEndPoint = associatedEndpoint; Receive(); - + } private void Receive() { if (!udpSocket.ReceiveFromAsync(receiveArgs)) { - ThreadPool.UnsafeQueueUserWorkItem((s)=> OnReceiveCompleted(null, receiveArgs),null); + ThreadPool.UnsafeQueueUserWorkItem((s) => OnReceiveCompleted(null, receiveArgs), null); } } @@ -67,7 +67,7 @@ private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) if (e.BytesTransferred > 0) { - + try { ProcessReceivedData(e.Buffer, e.Offset, e.BytesTransferred, e.RemoteEndPoint); @@ -118,8 +118,8 @@ public void Dispose() } } catch { } - - } - + + } + } } diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs index 0bea54b..3d9dd2e 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.TCP.AES; diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs index 8b2412d..963625c 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs @@ -3,99 +3,122 @@ using System.Net.Sockets; using System.Text; using NetworkLibrary.DistributedP2P.Client; -using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.UDP.Jumbo; using NetworkLibrary.UDP.Reliable.Components; using NetworkLibrary.Utils; using System.Net; using NetworkLibrary.Components; +using NetworkLibrary.DistributedP2P.Channels.Components; +using System.Drawing; +using System.Reflection; namespace NetworkLibrary.DistributedP2P.Channels { public class SecureUdpChannel:UdpChannel { - private readonly ConcurrentAesAlgorithm algo; + EphemeralKeyManager keyStore; byte[] decryptBuff = new byte[65555]; - public SecureUdpChannel(Socket udpSocket, IPEndPoint receiveEp, ConcurrentAesAlgorithm algo, ChannelInfo info) : base(udpSocket, receiveEp, info) + private int keyRotateTimeMs = 60000;//every minute + + public SecureUdpChannel(Socket udpSocket, IPEndPoint receiveEp, ConcurrentAesAlgorithm algo, ChannelInfo info,bool isInitiator) : base(udpSocket, receiveEp, info) + { + keyStore = new EphemeralKeyManager(algo, isInitiator? keyRotateTimeMs : -1); + keyStore.SendData += SendKeyMsg; + + + SenderModule sender = new SenderModule(); + sender.MaxSegmentSize = 1280; + sender.MinWindowSize = 1280 * 2; + + } + + private void SendKeyMsg(MessageFlags flag, byte[] buffer, int offset, int count) { - this.algo = algo; + SendInternalReliable(flag, buffer, offset, count); } protected override void BytesReceived(byte[] buffer, int offset, int count) { - var flag = (UdpFlags)buffer[offset++]; + var flag = (MessageFlags)buffer[offset++]; count--; - if (flag == UdpFlags.HP || flag == UdpFlags.HPAck) + if (flag == MessageFlags.HP || flag == MessageFlags.HPAck) return; + var keyNo = buffer[offset++]; + count--; + + keyStore.GetAlgorithm(keyNo, out var algo); + count = algo.DecryptInto(buffer, offset, count, decryptBuff, 0); buffer = decryptBuff; offset = 0; switch (flag) { - case UdpFlags.StandardMessage: + case MessageFlags.StandardMessage: HandleMessage(buffer, offset, count); break; - case UdpFlags.JumboMessage: + + case MessageFlags.JumboMessage: HandleJumboSegment(buffer, offset, count); break; - case UdpFlags.ReliableMessage: + + case MessageFlags.ReliableMessage: HandleRudpSegment(buffer, offset, count); break; - case UdpFlags.KeepAliveMessage: + + case MessageFlags.InternalReliableMessage: + HandleIncomingInternalRudpSegment(buffer, offset, count); + break; + + case MessageFlags.KeepAliveMessage: + break; + + case MessageFlags.HP: + case MessageFlags.HPAck: + break; + case MessageFlags.Ping: break; - case UdpFlags.HP: - case UdpFlags.HPAck: + case MessageFlags.KeyExchange: + case MessageFlags.KeyExchangeAck: + case MessageFlags.KeyExchangeFin: + keyStore.HandleMessage(flag, buffer, offset, count); break; } } - public override void Send(byte[] buffer, int offset, int count) + protected override void HandleInternalReliableMessage(byte[] buffer, int offset, int count) { - if (count > 64000) - { - JumboUdp.Send(buffer, offset, count); - } - else - { - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.WriteByte((byte)UdpFlags.StandardMessage); - - stream.Reserve(count + 128); - stream.Position32 += algo.EncryptInto(buffer, offset, count, stream.GetBuffer(), 1); - + var flag = (MessageFlags)buffer[offset++]; + count--; - SendInternal(stream.GetBuffer(), 0, stream.Position32); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); + switch (flag) + { + case MessageFlags.KeyExchange: + case MessageFlags.KeyExchangeAck: + case MessageFlags.KeyExchangeFin: + keyStore.HandleMessage(flag, buffer, offset, count); + break; } } - protected override void SendJumboSegment(byte[] buffer, int offset, int count) + + protected override void SendWithFlag (MessageFlags flag, byte[] buffer, int offset, int count) { var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.WriteByte((byte)UdpFlags.JumboMessage); + stream.WriteByte((byte)flag); + stream.WriteByte(keyStore.CurrKeyNumber); stream.Reserve(count + 128); - stream.Position32 += algo.EncryptInto(buffer, offset, count, stream.GetBuffer(), 1); + keyStore.GetAlgorithm(keyStore.CurrKeyNumber, out var algo); + stream.Position32 += algo.EncryptInto(buffer, offset, count, stream.GetBuffer(), 2); SendInternal(stream.GetBuffer(), 0, stream.Position32); SharerdMemoryStreamPool.ReturnStreamStatic(stream); } - internal override void SendRudpSegment(ReliableModule module,byte[] buffer, int offset, int count) - { - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.WriteByte((byte)UdpFlags.ReliableMessage); - - stream.Reserve(count+128); - stream.Position32 += algo.EncryptInto(buffer, offset, count, stream.GetBuffer(),1); - - SendInternal(stream.GetBuffer(), 0, stream.Position32); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); - } } } diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs index 5c11e08..90a4db0 100644 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs @@ -1,4 +1,5 @@ -using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Channels.Components; +using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.TCP.ByteMessage; using System; using System.Collections.Generic; diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs index 6c6bf44..78be85b 100644 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs @@ -7,9 +7,17 @@ using System.Text; using System.Threading; using NetworkLibrary.Components.MessageBuffer; +using System.IO; +using NetworkLibrary.DistributedP2P.Channels.Components; namespace NetworkLibrary.DistributedP2P.Channels { + /* + * Features: + Keep ALive + Key Exchange + + */ public class TcpChannel:IChannel { public ChannelInfo Info { get; private set; } @@ -45,7 +53,7 @@ public TcpChannel(ChannelInfo info, Socket connectedSocket) reader.OnMessageReady += (b, o, c) => BytesReceived?.Invoke(b,o,c); } - + public void SendAsync(byte[] buffer, int offset, int count) { if (IsSessionClosing()) @@ -53,13 +61,27 @@ public void SendAsync(byte[] buffer, int offset, int count) lock (bufferMutex) { - sendStream.WriteInt(count);// also write flag - sendStream.Write(buffer, offset, count); + int lenghtPos = sendStream.Position32; + sendStream.Position32 += 4; + + int amountWritten = WriteData(buffer, offset, count); + int lastPos = sendStream.Position32; + + sendStream.Position32 = lenghtPos; + sendStream.WriteInt(amountWritten); + sendStream.Position32 = lastPos; + } SignalSend(); } + protected virtual int WriteData(byte[] buffer, int offset, int count) + { + // Write Flag here + sendStream.Write(buffer, offset, count); + return count; + } private void SignalSend() { lock (sendMtex) @@ -71,8 +93,11 @@ private void SignalSend() var temp = sendStream; sendStream = flushStream; flushStream = temp; + + sendStream.Position32 = 0; } sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); + if (!connectedSocket.SendAsync(sendArgs)) { ThreadPool.UnsafeQueueUserWorkItem(_=> Sent(null, sendArgs),null); @@ -86,50 +111,59 @@ private void SignalSend() } private void Sent(object sender, SocketAsyncEventArgs e) { - if (IsSessionClosing()) - return; - - if (e.SocketError != SocketError.Success) + try { - HandleError(e, "while recieving from "); - CloseChannel(); - return; - } + if (IsSessionClosing()) + return; - else if (e.BytesTransferred == 0) - { - CloseChannel(); - return; - } - bool send = false; - lock (sendMtex) - { - if(Interlocked.CompareExchange(ref msgAvailable, 0, 1) == 1) + if (e.SocketError != SocketError.Success) { - lock (bufferMutex) - { - var temp = sendStream; - sendStream = flushStream; - flushStream = temp; - } - send = true; + HandleError(e, "while recieving from "); + CloseChannel(); + return; } - else + + else if (e.BytesTransferred == 0) { - Interlocked.Exchange(ref sendActive, 0); + CloseChannel(); + return; } - } + bool send = false; + lock (sendMtex) + { + if (Interlocked.CompareExchange(ref msgAvailable, 0, 1) == 1) + { + lock (bufferMutex) + { + var temp = sendStream; + sendStream = flushStream; + flushStream = temp; - if (send) - { - sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); - flushStream.Position32 = 0; + sendStream.Position32 = 0; + } + send = true; + } + else + { + Interlocked.Exchange(ref sendActive, 0); + } + } - if (!connectedSocket.SendAsync(sendArgs)) + if (send) { - Sent(null, sendArgs); + sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); + + if (!connectedSocket.SendAsync(sendArgs)) + { + Sent(null, sendArgs); + } } } + catch(Exception ex) + { + CloseChannel(); + } + } diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs index 4d3f773..8119f5a 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs @@ -1,6 +1,6 @@ using NetworkLibrary.Components.Crypto.Algorithms; +using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; -using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.UDP.Jumbo; using NetworkLibrary.UDP.Reliable.Components; using NetworkLibrary.Utils; @@ -20,6 +20,8 @@ public class UdpChannel : IChannel protected JumboModule JumboUdp = new JumboModule(0); internal ReliableModule ReliableUdp; + ReliableModule internalReliableModule; + public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) { @@ -39,6 +41,14 @@ public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) ReliableUdp.OnReceived += (e, b, o, c) => HandleMessage(b, o, c); ReliableUdp.OnSend += SendRudpSegment; + SenderModule sender2 = new SenderModule(); + + sender.MaxSegmentSize = 1280; + sender.MinWindowSize = 1280 * 2; + internalReliableModule = new ReliableModule(receiveEp, sender2); + internalReliableModule.OnReceived += (e, b, o, c) => HandleInternalReliableMessage(b, o, c); + internalReliableModule.OnSend += SendInternalRudpSegment; + } public void Start() @@ -50,32 +60,42 @@ public void Start() protected virtual void BytesReceived(byte[] buffer, int offset, int count) { // filter flags - var flag = (UdpFlags)buffer[offset++]; + var flag = (MessageFlags)buffer[offset++]; count--; switch (flag) { - case UdpFlags.StandardMessage: + case MessageFlags.StandardMessage: HandleMessage(buffer, offset, count); break; - case UdpFlags.JumboMessage: + case MessageFlags.JumboMessage: HandleJumboSegment(buffer, offset, count); break; - case UdpFlags.ReliableMessage: + case MessageFlags.ReliableMessage: HandleRudpSegment(buffer, offset, count); break; - case UdpFlags.KeepAliveMessage: + case MessageFlags.KeepAliveMessage: break; - case UdpFlags.HP: - case UdpFlags.HPAck: + case MessageFlags.HP: + case MessageFlags.HPAck: + break; + case MessageFlags.InternalReliableMessage: + HandleIncomingInternalRudpSegment(buffer, offset, count); break; + case MessageFlags.Ping: + break; + } } + protected virtual void HandleInternalReliableMessage(byte[] buffer, int offset, int count) + { + + } - protected void HandleMessage(byte[] buffer, int offset, int count) + protected virtual void HandleMessage(byte[] buffer, int offset, int count) { OnMessageReceived?.Invoke(buffer, offset, count); } @@ -87,26 +107,27 @@ protected void HandleJumboSegment(byte[] buffer, int offset, int count) protected virtual void SendJumboSegment(byte[] arg1, int arg2, int arg3) { - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.WriteByte((byte)UdpFlags.JumboMessage); - stream.Write(arg1, arg2, arg3); - SendInternal(stream.GetBuffer(), 0, stream.Position32); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); + SendWithFlag(MessageFlags.JumboMessage, arg1, arg2, arg3); } protected void HandleRudpSegment(byte[] buffer, int offset, int count) { ReliableUdp.HandleBytes(buffer, offset, count); } + + protected void HandleIncomingInternalRudpSegment(byte[] buffer, int offset, int count) + { + internalReliableModule.HandleBytes(buffer, offset, count); + } internal virtual void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) { - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.WriteByte((byte)UdpFlags.ReliableMessage); - stream.Write(buffer, offset, count); - SendInternal(stream.GetBuffer(), 0, stream.Position32); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); + SendWithFlag(MessageFlags.ReliableMessage, buffer, offset, count); } + internal virtual void SendInternalRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) + { + SendWithFlag(MessageFlags.InternalReliableMessage, buffer, offset, count); + } public virtual void Send(byte[] buffer, int offset, int count) { @@ -117,11 +138,7 @@ public virtual void Send(byte[] buffer, int offset, int count) } else { - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.WriteByte((byte)UdpFlags.StandardMessage); - stream.Write(buffer, offset, count); - SendInternal(stream.GetBuffer(), 0, stream.Position32); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); + SendWithFlag(MessageFlags.StandardMessage, buffer, offset, count); } } @@ -132,6 +149,25 @@ public void SendReliable(byte[] buffer, int offset, int count) } + protected virtual void SendWithFlag(MessageFlags flag, byte[] arg1, int arg2, int arg3) + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)flag); + stream.Write(arg1, arg2, arg3); + SendInternal(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + + protected void SendInternalReliable(MessageFlags flag, byte[] buffer, int offset, int count) + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte((byte)flag); + stream.Write(buffer, offset, count); + + internalReliableModule.Send(stream.GetBuffer(), 0, stream.Position32); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + protected void SendInternal(byte[] bytes, int offset, int count) { try diff --git a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs index 6735d37..accb9b5 100644 --- a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs +++ b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs @@ -9,6 +9,11 @@ public enum ChannelType { Tcp,SecureTcp,Udp,SecureUdp } + + public enum TcpHolePunchStrategy + { + Sequential, Simultaneous + } public class ChannelInfo { public ChannelInfo() diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 7a23a78..227d3e2 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -1,7 +1,6 @@ using NetworkLibrary.Components.Crypto; using NetworkLibrary.Components.Crypto.DiffieHellman; using NetworkLibrary.Components.Crypto.KeyDerivation; -using NetworkLibrary.DistributedP2P.Channels; using NetworkLibrary.DistributedP2P.Client.StateManagement; using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Server; @@ -17,6 +16,7 @@ using System.Threading.Tasks; using NetworkLibrary.Components; using System.Net; +using NetworkLibrary.DistributedP2P.Channels.Components; namespace NetworkLibrary.DistributedP2P.Client { @@ -59,8 +59,6 @@ public DistributedLobbyClient(IClientDbConnection clientDbConnector, timeSync = new TimeSync(this); } - - public async Task ConnectAsync(string ip, int port) { if (IsConnected) @@ -150,10 +148,14 @@ private void HandleServerMsg(MessageEnvelope envelope) ManageUdpHolepunchRequest(envelope); break; - case InternalConstants.RequestHolepunchTcp: + case InternalConstants.RequestSequentialHolepunchTcp: ManageTcpHolepunchRequest(envelope); break; + case InternalConstants.RequestSimultaneousHolepunchTcp: + ManageTcpHolepunchRequest2(envelope); + break; + } } else @@ -209,7 +211,7 @@ public async Task OpenRelayChannel(Guid destinationPeer, ChannelInfo I if (pipeState.IsSuccesful) { - IChannel channel = CreateChannel(pipeState); + IChannel channel = ChannelFactory.CreateChannel(pipeState, true); return channel; } return null; @@ -220,7 +222,7 @@ private void HandlePipeCreated(IConversationState state) if (state.IsSuccesful) { var pipeState = (ClientPipeState)state; - IChannel channel = CreateChannel(pipeState); + IChannel channel = ChannelFactory.CreateChannel(pipeState, false); if (channel != null) PeerConnected?.Invoke(channel); @@ -230,49 +232,7 @@ private void HandlePipeCreated(IConversationState state) } } - private static IChannel CreateChannel(ClientPipeState pipeState) - { - ChannelInfo info = pipeState.ChannelInfo; - Socket connectedSocket = pipeState.ConnectedSocket; - byte[] sharedSecret = pipeState.sharedSecret; - IPEndPoint endpoint = pipeState.SuccesfullEndpoint.ToIpEndpoint(); - ChannelType channelType = pipeState.ChannelInfo.ChannelType; - - return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType); - } - - private static IChannel CreateChannel(ChannelInfo info, Socket connectedSocket, byte[] sharedSecret, IPEndPoint endpoint, ChannelType channelType) - { - IChannel channel = null; - - switch (channelType) - { - - case ChannelType.Tcp: - channel = new TcpChannel(info, connectedSocket); - break; - case ChannelType.SecureTcp: - var symetricKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); - var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey, AesMode.GCM); - AesTcpClient client = new AesTcpClient(algo, connectedSocket); - channel = new SecureTcpChannel(client, info); - break; - case ChannelType.Udp: - channel = new UdpChannel(connectedSocket, endpoint, info); - - break; - case ChannelType.SecureUdp: - var symetricKey2 = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); - var algo2 = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey2, AesMode.GCM); - channel = new SecureUdpChannel(connectedSocket, endpoint, algo2, info); - - break; - } - - return channel; - } - - public async Task TryHolePunch(Guid destination, ChannelInfo info) + public async Task TryHolePunch(Guid destination, ChannelInfo info, TcpHolePunchStrategy strategy = TcpHolePunchStrategy.Sequential) { if(info.ChannelType == ChannelType.Udp || info.ChannelType == ChannelType.SecureUdp) @@ -285,23 +245,41 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info) if (state.IsSuccesful) { - return CreateChannel(state); + return ChannelFactory.CreateChannel(state, true); } return null; } else { - var state = new ClientTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, info); - stateManager.RegisterState(state); - state.Start(); + if (strategy == TcpHolePunchStrategy.Sequential) + { + var state = new ClientTcpHolepunchState2(Guid.NewGuid(), destination, this, serverEndpoint, info); + stateManager.RegisterState(state); + state.Start(); - await state.WaitCompletion(); + await state.WaitCompletion(); - if (state.IsSuccesful) + if (state.IsSuccesful) + { + return ChannelFactory.CreateChannel(state, true); + } + return null; + } + else { - return CreateChannel(state); + var state = new ClientTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, info); + stateManager.RegisterState(state); + state.Start(); + + await state.WaitCompletion(); + + if (state.IsSuccesful) + { + return ChannelFactory.CreateChannel(state, true); + } + return null; } - return null; + } } @@ -317,7 +295,7 @@ void State_OnComplete(IConversationState obj) { if (obj.IsSuccesful) { - var ch = CreateChannel(state); + var ch = ChannelFactory.CreateChannel(state, false); PeerConnected?.Invoke(ch); } @@ -325,17 +303,6 @@ void State_OnComplete(IConversationState obj) } - private static IChannel CreateChannel(ClientUdpHolepunchState udpHpstate) - { - ChannelInfo info = udpHpstate.ChannelInfo; - Socket connectedSocket = udpHpstate.Socket; - byte[] sharedSecret = udpHpstate.SharedSecret; - IPEndPoint endpoint = udpHpstate.SuccesfulEndpoint; - ChannelType channelType = udpHpstate.ChannelInfo.ChannelType; - - return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType); - } - private void ManageTcpHolepunchRequest(MessageEnvelope envelope) { var state = new ClientTcpHolepunchState(envelope.MessageId, envelope.From, this, serverEndpoint, null); @@ -347,21 +314,27 @@ void State_OnComplete(IConversationState obj) { if (obj.IsSuccesful) { - var ch = CreateChannel(state); + var ch = ChannelFactory.CreateChannel(state, false); PeerConnected?.Invoke(ch); } } } - private static IChannel CreateChannel(ClientTcpHolepunchState TcpHpstate) + private void ManageTcpHolepunchRequest2(MessageEnvelope envelope) { - ChannelInfo info = TcpHpstate.ChannelInfo; - Socket connectedSocket = TcpHpstate.Socket; - byte[] sharedSecret = TcpHpstate.SharedSecret; - IPEndPoint endpoint = TcpHpstate.SuccesfulEndpoint; - ChannelType channelType = TcpHpstate.ChannelInfo.ChannelType; + var state = new ClientTcpHolepunchState2(envelope.MessageId, envelope.From, this, serverEndpoint, null); + stateManager.RegisterState(state); + state.OnComplete += State_OnComplete; + state.HandleMessage(envelope); - return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType); + void State_OnComplete(IConversationState obj) + { + if (obj.IsSuccesful) + { + var ch = ChannelFactory.CreateChannel(state, false); + PeerConnected?.Invoke(ch); + } + } } public double GetTime() diff --git a/NetworkLibrary/DistributedP2P/Client/IChannel.cs b/NetworkLibrary/DistributedP2P/Client/IChannel.cs deleted file mode 100644 index 24e3ea1..0000000 --- a/NetworkLibrary/DistributedP2P/Client/IChannel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace NetworkLibrary.DistributedP2P.Client -{ - - public interface IChannel - { - ChannelInfo Info { get; } - - } -} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs index ca4a2f1..92fe25e 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs @@ -11,7 +11,6 @@ namespace NetworkLibrary.DistributedP2P.Client.StateManagement { - //Todo 0.0.0.0 means server ip! internal class ClientTcpHolepunchState : ConversationStateBase { private readonly Guid destId; @@ -51,7 +50,7 @@ public void Start() localPort = StartTcpSocket(); var msg = CreateEnvelope(); - msg.Header = InternalConstants.RequestHolepunchTcp; + msg.Header = InternalConstants.RequestSequentialHolepunchTcp; msg.KeyValuePairs = new Dictionary(); msg.KeyValuePairs["Port"] = localPort.ToString(); msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); @@ -71,7 +70,7 @@ public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - case InternalConstants.RequestHolepunchTcp: + case InternalConstants.RequestSequentialHolepunchTcp: HandleRemoteHpRequest(message); break; diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs new file mode 100644 index 0000000..2139d46 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs @@ -0,0 +1,472 @@ +using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Server; +using NetworkLibrary.P2P.Components.HolePunch; +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Client.StateManagement +{ + //Todo 0.0.0.0 means server ip! + internal class ClientTcpHolepunchState2 : ConversationStateBase + { + private readonly Guid destId; + private readonly IDistributedConnection connection; + private readonly EndpointData serverEndpoint; + private bool isInitiator; + public Socket Socket; + public IPEndPoint SuccesfulEndpoint; + + private DiffieHellman df = new DiffieHellman(); + private byte[] otherPublicKey; + public byte[] SharedSecret; + public ChannelInfo ChannelInfo; + + private int localPort; + private Socket listeningSocket; + private Socket acceptedSocket; + private Socket connectedSocket; + private int established = 0; + private int connected = 0; + private int accepted = 0; + + IPEndPoint selfEndpoint = new IPEndPoint(IPAddress.Any,0); + int swapCnt = 0; + + bool isListening = false; + List localEndpoints = new List(); + EndpointData publicEndpoint; + + private bool IsEstablished => Interlocked.CompareExchange(ref established, 0, 0) == 1; + public ClientTcpHolepunchState2(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 10000) + { + this.destId = destId; + this.connection = connection; + this.serverEndpoint = serverEndpoint; + this.ChannelInfo = info; + } + + //the initiator + public void Start() + { + isInitiator = true; + Log(StateId.ToString()); + + localPort = BindPort(); + + Log("Bound on port " + localPort); + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.RequestSimultaneousHolepunchTcp; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Port"] = localPort.ToString(); + msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); + msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; + + if (ChannelInfo.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + + msg.To = destId; + + connection.SendAsyncMessage(msg); + } + + public override void HandleMessage(MessageEnvelope message) + { + switch (message.Header) + { + case InternalConstants.RequestSimultaneousHolepunchTcp: + HandleRemoteHpRequest(message); + break; + + case InternalConstants.StartHP: + message.LockBytes(); + ThreadPool.UnsafeQueueUserWorkItem((s) => StartHolepunchRoutine(message), null); + break; + + case InternalConstants.PunchSuccesAck: + HandleRemoteSucces(message); + break; + case InternalConstants.PunchFailAck: + HandleFailure(); + break; + case InternalConstants.PunchSwap: + Swap(); + break; + } + } + + // the destination peer of hp + private void HandleRemoteHpRequest(MessageEnvelope message) + { + Log(StateId.ToString()); + + ChannelInfo = new ChannelInfo(); + ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); + ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; + + localPort = StartTcpListener(); + Log("listening on port " + localPort); + + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.AckRequestHolepunchTcp; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Port"] = localPort.ToString(); + + if (ChannelInfo.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + + msg.To = destId; + + connection.SendAsyncMessage(msg); + } + + private void StartHolepunchRoutine(MessageEnvelope message) + { + if (IsCompleted()) return; + + var epMsg = KnownTypeSerializer.DeserializeEndpointTransferMessage(message.Payload, message.PayloadOffset); + localEndpoints = epMsg.LocalEndpoints; + + bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); + publicEndpoint = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; + + if (ChannelInfo.RequiresKeyExchange()) + otherPublicKey = Convert.FromBase64String(message.KeyValuePairs["DH"]); + + ////213.243.208.162 + //foreach (var ep in localEndpoints) + //{ + // ep.Ip[0] = 213; + // ep.Ip[1] = 243; + // ep.Ip[2] = 208; + // ep.Ip[3] = 162; + + // publicEndpoint.Ip[0] = 213; + // publicEndpoint.Ip[1] = 243; + // publicEndpoint.Ip[2] = 208; + // publicEndpoint.Ip[3] = 162; + //} + + if (!isListening) + { + TryPunch(); + } + + } + + private void TryPunch() + { + try + { + // if there are local endpoints to test + if (localEndpoints.Count > 0) + { + foreach (EndpointData localEp in localEndpoints) + { + if (TryConnect(localEp, 500)) + return; + + if (IsCompleted()) return; + + } + } + + for (int i = 0; i < 2; i++) + { + if (TryConnect(publicEndpoint, (600))) + return; + + if (IsCompleted()) return; + + } + } + catch { } + finally + { + if(Interlocked.CompareExchange(ref established,0,0) == 0) + SwapAndNotify(); + } + } + + //this one only when we tried to connect and failed + // so we will listen only + private void SwapAndNotify() + { + if (IsCompleted()) return; + + + int cnt = 0; + while (!isListening) + { + try + { + if (IsCompleted()) return; + Swap(); + if (IsCompleted()) return; + } + catch { + Thread.Sleep(200); + } + + cnt++; + if (cnt > 10) + break; + + } + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSwap; + connection.SendAsyncMessage(msg); + } + + private void Swap() + { + if (swapCnt++ > 3) + { + Log("Max attempts reached"); + Cancel(); + return; + } + + if (IsCompleted()) return; + + if (isListening) + { + Log("Swapping to Sender"); + StopListener(); + ThreadPool.UnsafeQueueUserWorkItem( _ => TryPunch(), null); + } + else + { + Log("Swapping to Listener"); + StartTcpListener(); + } + } + + private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) + { + Socket connectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + try + { + Log("Connecting to " + endpoint.ToIpEndpoint().ToString()); + connectSocket.Bind(selfEndpoint); + + var connectTask = connectSocket.ConnectAsync(endpoint.ToIpEndpoint()); + var timeoutTask = Task.Delay(timeoutMs); + + if (Task.WhenAny(connectTask, timeoutTask).GetAwaiter().GetResult() == connectTask) + { + if (connectTask.IsFaulted) + { + throw connectTask.Exception; + } + HandleConnectedSocket(connectSocket); + Log($"Successfully connected to {endpoint.ToIpEndpoint()}"); + return true; + + } + else + { + Log($"Connection to {endpoint.ToIpEndpoint()} timed out after {timeoutMs}ms"); + connectSocket.Close(); + connectSocket.Dispose(); + return false; + } + } + catch (Exception ex) + { + Log($"Connect attempt to {endpoint.ToIpEndpoint()} failed: {ex.Message}"); + connectSocket.Close(); + return false; + } + } + + private int BindPort() + { + var clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + clientSocket.Bind(selfEndpoint); + selfEndpoint = (IPEndPoint)clientSocket.LocalEndPoint; + + try + { + clientSocket?.Close(); + clientSocket?.Dispose(); + clientSocket = null; + } + catch { } + + return selfEndpoint.Port; + + } + + private int StartTcpListener() + { + listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + listeningSocket.SendBufferSize = 12800000; + listeningSocket.ReceiveBufferSize = 12800000; + + listeningSocket.Bind(selfEndpoint); + + selfEndpoint = (IPEndPoint)listeningSocket.LocalEndPoint; + + + Listen(); + isListening = true; + return ((IPEndPoint)listeningSocket.LocalEndPoint).Port; + } + + private void Listen() + { + listeningSocket.Listen(1); + listeningSocket.BeginAccept(new AsyncCallback(AcceptCallback), listeningSocket); + } + + private void AcceptCallback(IAsyncResult ar) + { + try + { + Socket listener = (Socket)ar.AsyncState; + Socket handler = listener.EndAccept(ar); + + HandleAcceptedSocket(handler); + } + catch (Exception e) + { + Log("Failed Accept: " + e.Message); + } + + } + + private void StopListener() + { + try + { + isListening = false; + listeningSocket?.Close(); + listeningSocket?.Dispose(); + listeningSocket = null; + } + catch { } + } + + + private void HandleConnectedSocket(Socket socket) + { + if (IsCompleted()) return; + + Interlocked.Exchange(ref established, 1); + if (Interlocked.Exchange(ref connected, 1) == 1) + return; + + connectedSocket = socket; + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSucces; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Status"] = "Connected"; + connection.SendAsyncMessage(msg); + } + + private void HandleAcceptedSocket(Socket socket) + { + if (IsCompleted()) return; + + + Interlocked.Exchange(ref established, 1); + if (Interlocked.Exchange(ref accepted, 1) == 1) + return; + + Log($"Successfully accepted {(IPEndPoint)socket.RemoteEndPoint}"); + acceptedSocket = socket; + + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSucces; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Status"] = "Accepted"; + connection.SendAsyncMessage(msg); + } + + + private void HandleFailure() + { + Log("Failed Punch"); + Completed(false); + } + + private void HandleRemoteSucces(MessageEnvelope message) + { + string use = message.KeyValuePairs["Use"]; + if (ChannelInfo.RequiresKeyExchange()) + SharedSecret = df.CalculateSharedSecret(otherPublicKey); + + if (use == "Connected") + { + Socket = connectedSocket; + } + else + { + Socket = acceptedSocket; + } + + SuccesfulEndpoint = (IPEndPoint)Socket.RemoteEndPoint; + Log("Punched"); + Completed(true); + } + + public override void Cancel() + { + lock(cancellationMutex) + { + if (!IsCompleted()) + { + Log("Cancelled"); + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchFail; + connection.SendAsyncMessage(msg); + Completed(false); + } + } + + } + + protected override void Completed(bool succes) + { + base.Completed(succes); + + try + { + if (!IsSuccesful) + { + acceptedSocket?.Close(); + acceptedSocket?.Dispose(); + connectedSocket?.Close(); + connectedSocket?.Dispose(); + } + + listeningSocket?.Close(); + listeningSocket?.Dispose(); + } + catch { } + + } + + private void Log(string log) + { + //return; + string prefix = isInitiator ? "A: " : "B: "; + Console.WriteLine(prefix + log); + } + + + } + + +} diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 98e3542..4acf324 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -1,5 +1,6 @@ using NetworkLibrary.Components; using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; @@ -127,7 +128,7 @@ private void StartHolepunchRoutine(MessageEnvelope message) { for (int i = 0; i < 2; i++) { - TryPunch(localEp, UdpFlags.HP); + TryPunch(localEp, MessageFlags.HP); PreciseTimeAwaiter.Wait(20); if (IsCompleted()) return; } @@ -153,21 +154,21 @@ private void StartHolepunchRoutine(MessageEnvelope message) for (int i = 0; i < 5; i++) { - TryPunch(publicEp, UdpFlags.HP); + TryPunch(publicEp, MessageFlags.HP); PreciseTimeAwaiter.Wait(20 * i * i); if (IsCompleted()) return; } } - private void TryPunch(EndpointData ep, UdpFlags flag) + private void TryPunch(EndpointData ep, MessageFlags flag) { var ipep = ep.ToIpEndpoint(); TryPunch(ipep, flag); } private object m = new object(); PooledMemoryStream stream = new PooledMemoryStream(); - private void TryPunch(IPEndPoint ep, UdpFlags flag) + private void TryPunch(IPEndPoint ep, MessageFlags flag) { lock (m) { @@ -214,24 +215,24 @@ private async void Receive() { SocketReceiveFromResult received = receiveTask.Result; - if (buffer[0] == (byte)UdpFlags.HP) + if (buffer[0] == (byte)MessageFlags.HP) { // this must be only once if (Interlocked.CompareExchange(ref receivedOnce, 1, 0) == 0) { var ipep = (IPEndPoint)received.RemoteEndPoint; Log("[-]Received 0xFF from " + ipep.ToString()); - TryPunch((IPEndPoint)received.RemoteEndPoint, UdpFlags.HPAck); + TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); } } - else if (buffer[0] == (byte)UdpFlags.HPAck) + else if (buffer[0] == (byte)MessageFlags.HPAck) { Log("[+]Received 0x0F From " + ((IPEndPoint)received.RemoteEndPoint).ToString()); if (Interlocked.CompareExchange(ref receivedAck, 1, 0) == 0) { - TryPunch((IPEndPoint)received.RemoteEndPoint, UdpFlags.HPAck); + TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); ReceivedBidirectional(received.RemoteEndPoint); } return; diff --git a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs index a9c091a..6e0078c 100644 --- a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs +++ b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs @@ -46,7 +46,7 @@ public Task WaitCompletion() return Completion.Task; } - public void Cancel() + public virtual void Cancel() { lock (cancellationMutex) { diff --git a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs index d598ac4..a78aad5 100644 --- a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs +++ b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs @@ -27,7 +27,10 @@ internal class InternalConstants public const string PunchFail = "h"; public const string PunchSuccesAck = "i"; public const string PunchFailAck = "j"; - public const string RequestHolepunchTcp = "k"; + public const string RequestSequentialHolepunchTcp = "k"; public const string AckRequestHolepunchTcp = "l"; + public const string PunchSwap = "m"; + public const string RequestSimultaneousHolepunchTcp = "n"; + } } diff --git a/NetworkLibrary/DistributedP2P/Components/UdpFlags.cs b/NetworkLibrary/DistributedP2P/Components/UdpFlags.cs deleted file mode 100644 index 8438905..0000000 --- a/NetworkLibrary/DistributedP2P/Components/UdpFlags.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace NetworkLibrary.DistributedP2P.Components -{ - [Flags] - public enum UdpFlags:byte - { - StandardMessage = 1, - JumboMessage = 2, - ReliableMessage = 4, - KeepAliveMessage = 8, - HP = 16, - HPAck = 32, - - } - -} diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 180f993..ce30331 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -213,12 +213,18 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) state.HandleMessage(message); break; - case InternalConstants.RequestHolepunchTcp: + case InternalConstants.RequestSequentialHolepunchTcp: var state2 = new ServerTcpHolepunchState(message.MessageId, this, sessionManager); stateManager.RegisterState(state2); state2.HandleMessage(message); break; + case InternalConstants.RequestSimultaneousHolepunchTcp: + var state3 = new ServerTcpHolepunchState2(message.MessageId, this, sessionManager); + stateManager.RegisterState(state3); + state3.HandleMessage(message); + break; + case Constants.TimeSync: byte[] time = new byte[8]; diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs index e9b57c7..b2ab238 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs @@ -34,7 +34,7 @@ public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - case InternalConstants.RequestHolepunchTcp: + case InternalConstants.RequestSequentialHolepunchTcp: HandleHolepunchRequest(message); break; case InternalConstants.AckRequestHolepunchTcp: diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs new file mode 100644 index 0000000..881ab19 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs @@ -0,0 +1,208 @@ +using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.Utils; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; + +namespace NetworkLibrary.DistributedP2P.Server.StateManagement +{ + internal class ServerTcpHolepunchState2 : ConversationStateBase + { + private readonly IDistributedConnection connection; + private readonly SessionManager sessionManager; + Guid From; + Guid To; + int fromPort; + string fromPublicKey; + int toPort; + string toPublicKey; + ChannelInfo info; + private int succesCount; + + public ServerTcpHolepunchState2(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) + { + this.connection = connection; + this.sessionManager = sessionManager; + } + + public override void HandleMessage(MessageEnvelope message) + { + switch (message.Header) + { + case InternalConstants.RequestSimultaneousHolepunchTcp: + HandleHolepunchRequest(message); + break; + case InternalConstants.AckRequestHolepunchTcp: + HandleHolepunchRequestAck(message); + break; + case InternalConstants.PunchSucces: + HandleSucces(message); + break; + + case InternalConstants.PunchSwap: + RelaySwapMsg(message); + break; + case InternalConstants.PunchFail: + HandleFailure(message); + break; + } + } + + private void RelaySwapMsg(MessageEnvelope message) + { + if (message.From == From) + { + connection.SendAsyncMessage(To, message); + } + else + { + connection.SendAsyncMessage(From, message); + } + } + + // obtain port from destination endpoint + private void HandleHolepunchRequest(MessageEnvelope message) + { + info = new ChannelInfo(); + info.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); + info.ChannelName = message.KeyValuePairs["Name"]; + + From = message.From; + To = message.To; + fromPort = int.Parse(message.KeyValuePairs["Port"]); + if (info.RequiresKeyExchange()) + fromPublicKey = message.KeyValuePairs["DH"]; + connection.SendAsyncMessage(message); + } + + private void HandleHolepunchRequestAck(MessageEnvelope message) + { + toPort = int.Parse(message.KeyValuePairs["Port"]); + if (info.RequiresKeyExchange()) + toPublicKey = message.KeyValuePairs["DH"]; + + + + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.StartHP; + msg.KeyValuePairs = new Dictionary(); + + sessionManager.GetSessionData(From, out ServerSession sesFrom); + sessionManager.GetSessionData(To, out ServerSession sesTo); + + if (sesFrom != null && sesTo != null) + { + IPHelper.ObtainIpEndpoints(fromPort, toPort, sesFrom, sesTo, out var FromNeedsToKnow, out var ToNeedsToKnow); + + // coordination signal + double startTime = connection.GetTime(); + startTime += 1000 * (1 + Math.Max(FromNeedsToKnow.LocalEndpoints.Count, ToNeedsToKnow.LocalEndpoints.Count)); + msg.KeyValuePairs["Time"] = startTime.ToString(CultureInfo.InvariantCulture); + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.Position32 = 0; + + KnownTypeSerializer.SerializeEndpointTransferMessage(stream, FromNeedsToKnow); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + msg.To = From; + if (info.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = toPublicKey; + connection.SendAsyncMessage(msg); + + stream.Position32 = 0; + + KnownTypeSerializer.SerializeEndpointTransferMessage(stream, ToNeedsToKnow); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + msg.To = To; + if (info.RequiresKeyExchange()) + msg.KeyValuePairs["DH"] = fromPublicKey; + connection.SendAsyncMessage(msg); + } + else + { + Cancel(); + } + + } + + protected override void Completed(bool succes) + { + Console.WriteLine("Server Finalized"); + base.Completed(succes); + } + + + private void HandleFailure(MessageEnvelope message) + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchFailAck; + connection.SendAsyncMessage(From, msg); + connection.SendAsyncMessage(To, msg); + Completed(false); + + } + int succCounter = 0; + private void HandleSucces(MessageEnvelope message) + { + if (Interlocked.Increment(ref succCounter) == 1) + { + var sts = message.KeyValuePairs["Status"]; + + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchSuccesAck; + msg.KeyValuePairs = new Dictionary(); + msg.SetPayload(message.Payload, message.PayloadOffset, message.PayloadCount); + + if (sts == "Accepted") + { + + if (message.From == From) + { + msg.KeyValuePairs["Use"] = "Connected"; + connection.SendAsyncMessage(To, msg); + + msg.KeyValuePairs["Use"] = "Accepted"; + connection.SendAsyncMessage(From, msg); + } + else if (message.From == To) + { + msg.KeyValuePairs["Use"] = "Connected"; + connection.SendAsyncMessage(From, msg); + + msg.KeyValuePairs["Use"] = "Accepted"; + connection.SendAsyncMessage(To, msg); + } + + } + else // connected + { + + if (message.From == From) + { + msg.KeyValuePairs["Use"] = "Accepted"; + connection.SendAsyncMessage(To, msg); + + msg.KeyValuePairs["Use"] = "Connected"; + connection.SendAsyncMessage(From, msg); + } + else if (message.From == To) + { + msg.KeyValuePairs["Use"] = "Accepted"; + connection.SendAsyncMessage(From, msg); + + msg.KeyValuePairs["Use"] = "Connected"; + connection.SendAsyncMessage(To, msg); + } + } + Completed(true); + + } + + } + + } +} diff --git a/NetworkLibrary/TCP/Base/TcpSession.cs b/NetworkLibrary/TCP/Base/TcpSession.cs index 5b7d006..277e5a3 100644 --- a/NetworkLibrary/TCP/Base/TcpSession.cs +++ b/NetworkLibrary/TCP/Base/TcpSession.cs @@ -84,9 +84,7 @@ protected virtual void ConfigureSocket() protected virtual void ConfigureBuffers() { recieveBuffer = BufferPool.RentBuffer(socketSendBufferSize); -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// receiveMemory= new Memory(recieveBuffer); -//#endif + if (UseQueue) sendBuffer = BufferPool.RentBuffer(SocketRecieveBufferSize); } diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index b0520ae..ffe6881 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using NetworkLibrary; using NetworkLibrary.DistributedP2P.Channels; +using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.DistributedP2P.Server; using Protobuff.Components.Serialiser; @@ -276,6 +277,7 @@ public void PipeTestUdpSecure() TaskCompletionSource tcs = new TaskCompletionSource(); int received = 0; + int cnt = 0; using var server = ArrangeServer(); var cl1 = GetClient(); @@ -293,8 +295,12 @@ public void PipeTestUdpSecure() Assert.IsNotNull(channel1); channel1.Start(); + Thread.Sleep(5000); + byte[] data = new byte[12800000]; channel1.SendReliable(data, 0, data.Length); + Thread.Sleep(5000); + channel1.SendReliable(data, 0, data.Length); Thread.Sleep(100); void Cl2_PeerConnected(IChannel obj) @@ -302,7 +308,9 @@ void Cl2_PeerConnected(IChannel obj) var udpChannel = (SecureUdpChannel)obj; udpChannel.OnMessageReceived += (b, o, c) => { - received = c; tcs.SetResult(true); + received = c; + if(++cnt == 2) + tcs.SetResult(true); }; udpChannel.Start(); } @@ -608,7 +616,7 @@ public void TcpHolepunch() var info = new ChannelInfo(); info.ChannelType = ChannelType.Tcp; info.ChannelName = "Test"; - var channel1 = (TcpChannel)cl1.TryHolePunch(cl2.SessionId, info).Result; + var channel1 = (TcpChannel)cl1.TryHolePunch(cl2.SessionId, info, TcpHolePunchStrategy.Sequential).Result; Assert.IsNotNull(channel1); channel1.Start(); @@ -686,6 +694,132 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) } + [TestMethod] + public void TestTcpChannelParallelSend() + { + + TaskCompletionSource tcs = new TaskCompletionSource(); + ManualResetEvent mre = new ManualResetEvent(false); + int numReceived = 0; + + var cl1 = GetClient(); + var cl2 = GetClient(); + using var server = ArrangeServer(); + + var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + var data = new byte[12800000]; + data[0] = 1; + int iter = 100; + + + cl2.PeerConnected += Cl2_PeerConnected; + + var info = new ChannelInfo(); + info.ChannelType = ChannelType.Tcp; + info.ChannelName = "Test"; + var channel1 = (TcpChannel)cl1.TryHolePunch(cl2.SessionId, info, TcpHolePunchStrategy.Sequential).Result; + Assert.IsNotNull(channel1); + channel1.Start(); + + + Parallel.For(0, iter, (i) => + { + channel1.SendAsync(data, 0, data.Length); + }); + Thread.Sleep(100); + + void Cl2_PeerConnected(IChannel obj) + { + var ch = (TcpChannel)obj; + ch.BytesReceived += Ch_OnMessageReceived; + ch.Start(); + } + + void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) + { + if (arg3 != data.Length) + throw new Exception(); + + if(Interlocked.Increment(ref numReceived) == 100) + tcs.TrySetResult(true); + } + + var ss = tcs.Task.Result; + Assert.AreEqual(iter, numReceived); + + + + } + + [TestMethod] + public void TestTcpChannelOrder() + { + + TaskCompletionSource tcs = new TaskCompletionSource(); + ManualResetEvent mre = new ManualResetEvent(false); + int numReceived = 0; + + var cl1 = GetClient(); + var cl2 = GetClient(); + using var server = ArrangeServer(); + + var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; + var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + + var data = new byte[1280]; + data[0] = 1; + int iter = 100; + List values = new List(); + + + cl2.PeerConnected += Cl2_PeerConnected; + + var info = new ChannelInfo(); + info.ChannelType = ChannelType.Tcp; + info.ChannelName = "Test"; + var channel1 = (TcpChannel)cl1.TryHolePunch(cl2.SessionId, info, TcpHolePunchStrategy.Sequential).Result; + Assert.IsNotNull(channel1); + channel1.Start(); + + + for (int i = 0; i < iter; i++) + { + data[0] = (byte)i; + channel1.SendAsync(data, 0, data.Length); + if(i%10 == 0) + Thread.Sleep(1); + }; + Thread.Sleep(100); + + void Cl2_PeerConnected(IChannel obj) + { + var ch = (TcpChannel)obj; + ch.BytesReceived += Ch_OnMessageReceived; + ch.Start(); + } + void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) + { + if (arg3 != data.Length) + throw new Exception(); + + values.Add(arg1[arg2]); + + if (Interlocked.Increment(ref numReceived) == 100) + tcs.TrySetResult(true); + } + + var ss = tcs.Task.Result; + Assert.AreEqual(iter, numReceived); + + for (int i = 1; i < values.Count; i++) + { + Assert.IsTrue(values[i - 1] + 1 == values[i]); + } + + } + } } From e506bdf2e7268bf924f76c7c006a5769509926bd Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Wed, 9 Apr 2025 17:04:42 +0200 Subject: [PATCH 17/27] implemented channel flags and tcp key rotation --- .../Channels/Components/ChannelFactory.cs | 3 +- .../Components/EphemeralKeyManager.cs | 69 ++-- .../Channels/Components/IChannel.cs | 2 + .../Channels/Components/KeepAlive.cs | 63 ++-- .../Components/{Flags.cs => MessageFlags.cs} | 3 +- .../Channels/Components/Pinger.cs | 69 ++++ .../Channels/Components/UdpChannelBase.cs | 8 +- .../Channels/SecureTcpChannel.cs | 114 +++++-- .../Channels/SecureUdpChannel.cs | 101 +++--- .../DistributedP2P/Channels/TcpChannel.cs | 311 +++++++++++++++++- .../DistributedP2P/Channels/TcpChannel2.cs | 257 --------------- .../DistributedP2P/Channels/UdpChannel.cs | 108 +++++- .../Client/DistributedLobbyClient.cs | 6 +- .../StateManagement/ClientConnectionState.cs | 3 +- .../ClientUdpHolepunchState.cs | 80 +++-- .../Components/EndpointDiscoveryClient.cs | 77 +++++ .../Components/EndpointDiscoveryServer.cs | 74 +++++ .../DistributedP2P/Components/StateManager.cs | 10 +- .../DistributedP2P/Components/TimerService.cs | 7 +- .../Server/DistributedLobbyServer.cs | 16 +- .../StateManagement/ServerConnectionState.cs | 6 +- .../DistributedP2P/DistP2PServerclientTest.cs | 40 ++- 22 files changed, 933 insertions(+), 494 deletions(-) rename NetworkLibrary/DistributedP2P/Channels/Components/{Flags.cs => MessageFlags.cs} (93%) create mode 100644 NetworkLibrary/DistributedP2P/Channels/Components/Pinger.cs delete mode 100644 NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryServer.cs diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs index 15ce17f..054ad9f 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs @@ -70,8 +70,7 @@ public static IChannel CreateChannel(ChannelInfo info, Socket connectedSocket, b case ChannelType.SecureTcp: var symetricKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey, AesMode.GCM); - AesTcpClient client = new AesTcpClient(algo, connectedSocket); - channel = new SecureTcpChannel(client, info); + channel = new SecureTcpChannel(algo, info, connectedSocket, isInitiator); break; case ChannelType.Udp: channel = new UdpChannel(connectedSocket, endpoint, info); diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs index 81e3458..f152eb6 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs @@ -1,51 +1,53 @@ using NetworkLibrary.Components; -using NetworkLibrary.Components.Crypto.DiffieHellman; using NetworkLibrary.Components.Crypto; +using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.Components.Crypto.KeyDerivation; +using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.Utils; using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Security.Cryptography; -using System.Text; -using NetworkLibrary.Components.Crypto.KeyDerivation; -using NetworkLibrary.DistributedP2P.Components; namespace NetworkLibrary.DistributedP2P.Channels.Components { internal class EphemeralKeyManager { - public Action SendData; + public Action SendData; - DiffieHellman df = new DiffieHellman(); + DiffieHellman df = new DiffieHellman(); - internal ConcurrentDictionary keyStore = new ConcurrentDictionary(); + internal ConcurrentDictionary keyStore = new ConcurrentDictionary(); byte[] innerBuffer = new byte[1024]; - byte currKeyNumber = 0; + byte currKeyNumber = 0; internal byte CurrKeyNumber = 0; - private readonly int rotateEveryMs; - - public EphemeralKeyManager(ConcurrentAesAlgorithm initialKey, int rotateEveryMs = 1000 ) + private int keyRotationPeriod; + private bool closed = false; + RandomNumberGenerator rng = RandomNumberGenerator.Create(); + Guid timerGuid = Guid.NewGuid(); + public EphemeralKeyManager(ConcurrentAesAlgorithm initialKey, int keyRotationPeriod = 1000) { keyStore[0] = initialKey; - this.rotateEveryMs = rotateEveryMs; + this.keyRotationPeriod = keyRotationPeriod; TimedKeyExchange(); } private void TimedKeyExchange() { - if (rotateEveryMs < 0) + if (keyRotationPeriod < 0) return; - TimerService.RegisterTimer(Guid.NewGuid(), rotateEveryMs, () => + TimerService.RegisterTimer(timerGuid, keyRotationPeriod, () => { RequestKeyExchange(); }); } - public void HandleMessage(MessageFlags flag, byte[] buffer, int offset, int count ) + public void HandleMessage(MessageFlags flag, byte[] buffer, int offset, int count) { - + + if (closed) return; + switch (flag) { case MessageFlags.KeyExchange: @@ -66,22 +68,26 @@ public void HandleMessage(MessageFlags flag, byte[] buffer, int offset, int coun //[Alice] private void RequestKeyExchange() { - df = new DiffieHellman(); + if (closed) return; + + df = new DiffieHellman(); byte[] myPublic = df.GetPublicKey(); SendData?.Invoke(MessageFlags.KeyExchange, myPublic, 0, myPublic.Length); - TimedKeyExchange(); + } //[Bob] private void HandleKeyExchangeReq(byte[] buffer, int offset, int count) { + if (closed) return; + currKeyNumber++; df = new DiffieHellman(); var sharedSecret = df.CalculateSharedSecret(ByteCopy.ToArray(buffer, offset, count)); - var privKey = HKDFLite.DeriveKey(sharedSecret,outputLength:16); + var privKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); var algo = new ConcurrentAesAlgorithm(privKey, AesMode.GCM); - keyStore[currKeyNumber] = algo; + keyStore[currKeyNumber] = algo; byte[] myPublic = df.GetPublicKey(); SendData?.Invoke(MessageFlags.KeyExchangeAck, myPublic, 0, myPublic.Length); @@ -90,6 +96,7 @@ private void HandleKeyExchangeReq(byte[] buffer, int offset, int count) //[Alice] private void HandleKeyExchangeAck(byte[] buffer, int offset, int count) { + if (closed) return; currKeyNumber++; @@ -98,22 +105,38 @@ private void HandleKeyExchangeAck(byte[] buffer, int offset, int count) var algo = new ConcurrentAesAlgorithm(privKey, AesMode.GCM); keyStore[currKeyNumber] = algo; - SendData?.Invoke(MessageFlags.KeyExchangeFin, innerBuffer,0,1); + rng.GetBytes(innerBuffer, 0, 32); + SendData?.Invoke(MessageFlags.KeyExchangeFin, innerBuffer, 0, 32); CurrKeyNumber = currKeyNumber; + TimedKeyExchange(); } //[Bob] private void HandleFinalize() { + Console.WriteLine("KeyExchanged"); + if (closed) return; + CurrKeyNumber = currKeyNumber; } - + public bool GetAlgorithm(byte keyNum, out ConcurrentAesAlgorithm algo) { return keyStore.TryGetValue(keyNum, out algo); } + internal void Close() + { + closed = true; + } + + internal void SetKeyRotationTime(int timeMs) + { + TimerService.CancelTimeout(timerGuid); + keyRotationPeriod = timeMs; + TimedKeyExchange(); + } } } diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs b/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs index 9e395e7..e180cc4 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs @@ -10,5 +10,7 @@ public interface IChannel void Start(); + void CloseChannel(); + } } \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs index 0363360..dfc18da 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs @@ -1,27 +1,18 @@ using System; -using System.Collections.Generic; -using System.Text; +using System.Security.Cryptography; using System.Threading.Tasks; namespace NetworkLibrary.DistributedP2P.Channels.Components { -/* - * Send keep alive every 5 seconds - * - * if reply is not received within 1 sec resend up to 5 times - * - * otherwise keep alive again 5 seconds later - * - * - */ - internal class KeepAlive + + public class KeepAlive { public Action SendData; + public Action NotAlive; - DateTime lastReceived = DateTime.Now; - bool stop = false; - int maxRetry = 10; - byte[] innerBuff = new byte[1]; + private DateTime lastReceived = DateTime.Now; + private bool stop = false; + private byte[] innerBuff = new byte[32]; public KeepAlive() { StartSendRoutine(); @@ -31,49 +22,45 @@ private async void StartSendRoutine() { while (!stop) { - await Task.Delay(5000); + await Task.Delay(4000); SendKeepAlive(); - - await Task.Delay(1000); - int retires = 0; - while((DateTime.Now - lastReceived).TotalMilliseconds > 1000) + + if ((DateTime.Now - lastReceived).TotalMilliseconds > 10000) { - if(retires > maxRetry) - { - DisconnectDetected(); - return; - } - - SendKeepAlive(); - await Task.Delay(1000); + DisconnectDetected(); } } } private void DisconnectDetected() { - // event of DC + NotAlive?.Invoke(); } public void HandleMessage(MessageFlags flag, byte[] buffer, int offset, int count) { - - switch (flag) - { - case MessageFlags.KeepAliveMessage: - HandleKeepAlive(buffer, offset, count); - break; - } + HandleKeepAlive(buffer, offset, count); } + RandomNumberGenerator r = RandomNumberGenerator.Create(); private void SendKeepAlive() { - SendData?.Invoke(MessageFlags.KeepAliveMessage, innerBuff, 0, 1); + r.GetBytes(innerBuff, 0, 16); + SendData?.Invoke(MessageFlags.KeepAliveMessage, innerBuff, 0, 16); + Console.WriteLine("Keep alive sent"); } private void HandleKeepAlive(byte[] buffer, int offset, int count) { lastReceived = DateTime.Now; + Console.WriteLine("Keep alive received"); + } + + internal void Close() + { + stop = true; + NotAlive = null; + SendData = null; } } } diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/Flags.cs b/NetworkLibrary/DistributedP2P/Channels/Components/MessageFlags.cs similarity index 93% rename from NetworkLibrary/DistributedP2P/Channels/Components/Flags.cs rename to NetworkLibrary/DistributedP2P/Channels/Components/MessageFlags.cs index 37bfa3f..7fe897d 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/Flags.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/MessageFlags.cs @@ -14,10 +14,11 @@ public enum MessageFlags : byte HP, HPAck, Ping, + Pong, KeyExchange, KeyExchangeAck, KeyExchangeFin, - + Kill, } diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/Pinger.cs b/NetworkLibrary/DistributedP2P/Channels/Components/Pinger.cs new file mode 100644 index 0000000..e0f6e24 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Channels/Components/Pinger.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Channels.Components +{ + + public class Pinger + { + + public Action SendData; + byte[] innerBuffer = new byte[16]; + ConcurrentDictionary> dispatched = new ConcurrentDictionary>(); + public void HandleMessage(MessageFlags flag, byte[] buffer, int offset, int count) + { + switch (flag) + { + case MessageFlags.Ping: + HandlePing(buffer, offset, count); + break; + case MessageFlags.Pong: + HandlePong(buffer, offset); + break; + } + } + + //[A] + public async Task Ping() + { + Guid guid = Guid.NewGuid(); + var pong = new TaskCompletionSource(); + int offset = 0; + PrimitiveEncoder.WriteGuid(innerBuffer, ref offset, guid); + dispatched[guid] = pong; + + var sendTime = DateTime.Now; + SendData?.Invoke(MessageFlags.Ping, innerBuffer, 0, 16); + + var result = await Task.WhenAny(pong.Task, Task.Delay(10000)); + if (result == pong.Task) + { + var replyTime = await pong.Task; + return (replyTime - sendTime).TotalMilliseconds; + } + else + { + dispatched.TryRemove(guid, out _); + return -1; + } + } + + //[B] + private void HandlePing(byte[] buffer, int offset, int count) + { + SendData?.Invoke(MessageFlags.Pong, buffer, offset, count); + } + + //[A] + private void HandlePong(byte[] buffer, int offset) + { + var guid = PrimitiveEncoder.ReadGuid(buffer, ref offset); + if (dispatched.TryRemove(guid, out var tcs)) + { + tcs.TrySetResult(DateTime.Now); + } + } + + } +} diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs index 331fa7f..ad863f2 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs @@ -17,7 +17,6 @@ internal class UdpChannelBase : IChannel, IDisposable public ChannelInfo Info { get; private set; } public event Action OnMessageReceived; - public UdpChannelBase(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) { this.udpSocket = udpSocket; @@ -97,8 +96,11 @@ private void HandleSocketError(SocketError error) } - - public void Dispose() + public virtual void CloseChannel() + { + Dispose(); + } + public virtual void Dispose() { try { diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs index 3d9dd2e..827d501 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs @@ -1,72 +1,118 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; +using System.Net.Sockets; using System.Text; +using NetworkLibrary.Components; +using NetworkLibrary.Components.Crypto; using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.TCP.AES; namespace NetworkLibrary.DistributedP2P.Channels { - public class SecureTcpChannel : IChannel + public class SecureTcpChannel : TcpChannel { - AesTcpClient client; - AesTcpServer server; - private Guid clientId; - bool clientMode = false; + private readonly bool isInitiator; + private EphemeralKeyManager keyManager; - public ChannelInfo Info { get; private set; } + private byte[] encBuff; + private byte[] decBuff; + /// + /// -1 means never rotate keys + /// + public int KeyRotationPeriodMs { get; private set; } = 1000; - public event Action BytesReceived; - public event Action Disconnected; - - public SecureTcpChannel(AesTcpClient client, ChannelInfo info) + public SecureTcpChannel(ConcurrentAesAlgorithm algo,ChannelInfo info, Socket connectedSocket, bool isInitiator) : base(info, connectedSocket) { - this.client = client; - Info = info; + this.isInitiator = isInitiator; + encBuff = BufferPool.RentBuffer(128000); + decBuff = BufferPool.RentBuffer(128000); + + + keyManager = new EphemeralKeyManager(algo, isInitiator ? KeyRotationPeriodMs : -1); + keyManager.SendData += SendKeyMsg; - clientMode = true; - client.OnBytesReceived += ClientBytesReceived; - client.OnDisconnected += ClientDisconnected; } - private void ServerBytesReceived(Guid guid, byte[] bytes, int offset, int count) + public void SetKeyRotationPeriod(int timeMs) { - ClientBytesReceived(bytes, offset, count); + if (isInitiator) + { + KeyRotationPeriodMs = timeMs; + keyManager.SetKeyRotationTime(timeMs); + } } - private void ClientBytesReceived(byte[] bytes, int offset, int count) + private void SendKeyMsg(MessageFlags flags, byte[] buffer, int offset, int count) { - BytesReceived?.Invoke(bytes, offset, count); + FlagAndSend(flags, buffer, offset, count); } - - public void SendAsync(byte[] buffer, int offset, int count) + protected override int WritePrefix(PooledMemoryStream sendStream) { - if (clientMode) - client.SendAsync(buffer, offset, count); - else - server.SendBytesToClient(clientId, buffer, offset, count); + sendStream.WriteByte(keyManager.CurrKeyNumber); + return 1; } - - private void ServerClientDisconnected(Guid guid) + protected override int WriteData(byte[] buffer, int offset, int count) { - ClientDisconnected(); + EnsureCapacityEnc(count); + + keyManager.GetAlgorithm(keyManager.CurrKeyNumber, out var algo); + int amountEnc = algo.EncryptInto(buffer, offset, count, encBuff, 0); + + return base.WriteData(encBuff, 0, amountEnc); } + protected override void HandleReceivedBytes(byte[] buffer, int offset, int count) + { + var flag = (MessageFlags)buffer[offset++]; count--; + + if (flag == MessageFlags.HP || flag == MessageFlags.HPAck) return; - private void ClientDisconnected() + var keyNo = buffer[offset++]; count--; + + keyManager.GetAlgorithm(keyNo, out var algo); + + EnsureCapacityDec(count); + count = algo.DecryptInto(buffer, offset, count, decBuff, 0); // not sure if try catch this. + buffer = decBuff; + offset = 0; + + HandleReceivedMessage(buffer, offset, count, flag); + } + + protected override void HandleReceivedMessage(byte[] buffer, int offset, int count, MessageFlags flag) { - Disconnected?.Invoke(); + switch (flag) + { + case MessageFlags.KeyExchange: + case MessageFlags.KeyExchangeAck: + case MessageFlags.KeyExchangeFin: + keyManager.HandleMessage(flag, buffer, offset, count); + break; + } + base.HandleReceivedMessage(buffer, offset, count, flag); } - public void Start() + private void EnsureCapacityEnc(int count) { - if (clientMode) + if(encBuff.Length < count + 256) { - client.Start(); - } + BufferPool.ReturnBuffer(encBuff); + encBuff = BufferPool.RentBuffer(count + 256); + } } + private void EnsureCapacityDec(int count) + { + if (decBuff.Length < count + 256) + { + BufferPool.ReturnBuffer(decBuff); + decBuff = BufferPool.RentBuffer(count + 256); + } + } + } } diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs index 963625c..d56362b 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs @@ -1,35 +1,40 @@ -using System; -using System.Collections.Generic; -using System.Net.Sockets; -using System.Text; +using NetworkLibrary.Components; +using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; -using NetworkLibrary.UDP.Jumbo; using NetworkLibrary.UDP.Reliable.Components; using NetworkLibrary.Utils; using System.Net; -using NetworkLibrary.Components; -using NetworkLibrary.DistributedP2P.Channels.Components; -using System.Drawing; -using System.Reflection; +using System.Net.Sockets; namespace NetworkLibrary.DistributedP2P.Channels { - public class SecureUdpChannel:UdpChannel + public class SecureUdpChannel : UdpChannel { - EphemeralKeyManager keyStore; + EphemeralKeyManager keyManager; byte[] decryptBuff = new byte[65555]; - private int keyRotateTimeMs = 60000;//every minute + private readonly bool isInitiator; + + public int KeyRotationPeriodMs { get; private set; } = 1000;//every minute - public SecureUdpChannel(Socket udpSocket, IPEndPoint receiveEp, ConcurrentAesAlgorithm algo, ChannelInfo info,bool isInitiator) : base(udpSocket, receiveEp, info) + public SecureUdpChannel(Socket udpSocket, IPEndPoint receiveEp, ConcurrentAesAlgorithm algo, ChannelInfo info, bool isInitiator) : base(udpSocket, receiveEp, info) { - keyStore = new EphemeralKeyManager(algo, isInitiator? keyRotateTimeMs : -1); - keyStore.SendData += SendKeyMsg; + keyManager = new EphemeralKeyManager(algo, isInitiator ? KeyRotationPeriodMs : -1); + keyManager.SendData += SendKeyMsg; SenderModule sender = new SenderModule(); sender.MaxSegmentSize = 1280; sender.MinWindowSize = 1280 * 2; - + this.isInitiator = isInitiator; + } + + public void SetKeyRotationPeriod(int timeMs) + { + if (isInitiator) + { + KeyRotationPeriodMs = timeMs; + keyManager.SetKeyRotationTime(timeMs); + } } private void SendKeyMsg(MessageFlags flag, byte[] buffer, int offset, int count) @@ -40,85 +45,57 @@ private void SendKeyMsg(MessageFlags flag, byte[] buffer, int offset, int count) protected override void BytesReceived(byte[] buffer, int offset, int count) { var flag = (MessageFlags)buffer[offset++]; - count--; if (flag == MessageFlags.HP || flag == MessageFlags.HPAck) return; var keyNo = buffer[offset++]; - count--; - - keyStore.GetAlgorithm(keyNo, out var algo); + count -= 2; + + keyManager.GetAlgorithm(keyNo, out var algo); count = algo.DecryptInto(buffer, offset, count, decryptBuff, 0); buffer = decryptBuff; offset = 0; - switch (flag) - { - case MessageFlags.StandardMessage: - HandleMessage(buffer, offset, count); - break; - - case MessageFlags.JumboMessage: - HandleJumboSegment(buffer, offset, count); - break; - - case MessageFlags.ReliableMessage: - HandleRudpSegment(buffer, offset, count); - break; - - case MessageFlags.InternalReliableMessage: - HandleIncomingInternalRudpSegment(buffer, offset, count); - break; - - case MessageFlags.KeepAliveMessage: - break; - - case MessageFlags.HP: - case MessageFlags.HPAck: - break; - case MessageFlags.Ping: - break; - - case MessageFlags.KeyExchange: - case MessageFlags.KeyExchangeAck: - case MessageFlags.KeyExchangeFin: - keyStore.HandleMessage(flag, buffer, offset, count); - break; - } + HandleReivedMessage(buffer, offset, count, flag); } - protected override void HandleInternalReliableMessage(byte[] buffer, int offset, int count) + protected override void HandleReivedMessage(byte[] buffer, int offset, int count, MessageFlags flag) { - var flag = (MessageFlags)buffer[offset++]; - count--; - switch (flag) { case MessageFlags.KeyExchange: case MessageFlags.KeyExchangeAck: case MessageFlags.KeyExchangeFin: - keyStore.HandleMessage(flag, buffer, offset, count); + keyManager.HandleMessage(flag, buffer, offset, count); break; } + + base.HandleReivedMessage(buffer, offset, count, flag); } - - protected override void SendWithFlag (MessageFlags flag, byte[] buffer, int offset, int count) + + protected override void SendWithFlag(MessageFlags flag, byte[] buffer, int offset, int count) { var stream = SharerdMemoryStreamPool.RentStreamStatic(); stream.WriteByte((byte)flag); - stream.WriteByte(keyStore.CurrKeyNumber); + stream.WriteByte(keyManager.CurrKeyNumber); stream.Reserve(count + 128); - keyStore.GetAlgorithm(keyStore.CurrKeyNumber, out var algo); + keyManager.GetAlgorithm(keyManager.CurrKeyNumber, out var algo); stream.Position32 += algo.EncryptInto(buffer, offset, count, stream.GetBuffer(), 2); SendInternal(stream.GetBuffer(), 0, stream.Position32); SharerdMemoryStreamPool.ReturnStreamStatic(stream); } + protected override void ReleseResources() + { + base.ReleseResources(); + keyManager.Close(); + } + } } diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs index 90a4db0..8e5309d 100644 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs @@ -1,45 +1,326 @@ -using NetworkLibrary.DistributedP2P.Channels.Components; +using NetworkLibrary.Components; +using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; -using NetworkLibrary.TCP.ByteMessage; using System; -using System.Collections.Generic; +using System.Drawing; using System.Net.Sockets; -using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace NetworkLibrary.DistributedP2P.Channels { - public class TcpChannel2 : IChannel + + public class TcpChannel : IChannel { public ChannelInfo Info { get; private set; } public event Action BytesReceived; public event Action Disconnected; - private ByteMessageTcpClient client; private readonly Socket connectedSocket; + private int totalBytesReceived; + private SocketAsyncEventArgs receiveArgs; + private int Closing = 0; + + protected PooledMemoryStream sendStream = new PooledMemoryStream(); + protected PooledMemoryStream flushStream = new PooledMemoryStream(); + + private object bufferMutex = new object(); + private object sendMtex = new object(); + private int sendActive = 0; + private int msgAvailable = 0; - public TcpChannel2(ChannelInfo info, Socket connectedSocket) + private SocketAsyncEventArgs sendArgs; + private ByteMessageReader reader = new ByteMessageReader(); + + private KeepAlive keepAlive; + private Pinger pinger; + + public TcpChannel(ChannelInfo info, Socket connectedSocket) { Info = info; this.connectedSocket = connectedSocket; - client = new ByteMessageTcpClient(); - client.GatherConfig = ScatterGatherConfig.UseBuffer; - client.OnDisconnected += () => Disconnected?.Invoke(); - client.OnBytesReceived += (b, o, c) => BytesReceived?.Invoke(b, o, c); + InitializeReceiver(); + + sendArgs = new SocketAsyncEventArgs(); + sendArgs.Completed += Sent; + reader.OnMessageReady += HandleReceivedBytes; + + keepAlive = new KeepAlive(); + keepAlive.SendData += FlagAndSend; + keepAlive.NotAlive += ()=>ErrorAndEnd("Kepp Alive Timed Out"); + + pinger = new Pinger(); + pinger.SendData += FlagAndSend; + } + + + public Task Ping() + { + return pinger.Ping(); + } + + public void SendAsync(byte[] buffer, int offset, int count) + { + FlagAndSend(MessageFlags.StandardMessage, buffer, offset, count); + } + + protected void FlagAndSend(MessageFlags flag, byte[] buffer, int offset, int count) + { + if (IsSessionClosing()) + return; + + lock (bufferMutex) + { + try + { + int lenghtPos = sendStream.Position32; + sendStream.Position32 += 4; + + sendStream.WriteByte((byte)flag); + int prefixLen = WritePrefix(sendStream); + + int amountWritten = WriteData(buffer, offset, count); + int lastPos = sendStream.Position32; + + sendStream.Position32 = lenghtPos; + sendStream.WriteInt(amountWritten + prefixLen + 1); + sendStream.Position32 = lastPos; + } + catch (Exception ex) + { + ErrorAndEnd(ex.Message + "\n" + ex.StackTrace); + return; + } + + } + + SignalSend(); + } + + protected virtual int WritePrefix(PooledMemoryStream sendStream) + { + return 0; + } + + protected virtual int WriteData(byte[] buffer, int offset, int count) + { + sendStream.Write(buffer, offset, count); + return count; + } + + private void SignalSend() + { + lock (sendMtex) + { + if (Interlocked.CompareExchange(ref sendActive, 1, 0) == 0) + { + lock (bufferMutex) + { + var temp = sendStream; + sendStream = flushStream; + flushStream = temp; + + sendStream.Position32 = 0; + } + + sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); + + if (!connectedSocket.SendAsync(sendArgs)) + { + ThreadPool.UnsafeQueueUserWorkItem(_ => Sent(null, sendArgs), null); + } + } + else + { + Interlocked.Exchange(ref msgAvailable, 1); + } + } + } + private void Sent(object sender, SocketAsyncEventArgs e) + { + try + { + if (IsSessionClosing()) + return; + + if (e.SocketError != SocketError.Success) + { + ErrorAndEnd($"While sending a socket error occured {e.SocketError}"); + CloseChannel(); + return; + } + + else if (e.BytesTransferred == 0) + { + Log("0 bytes Sent"); + CloseChannel(); + return; + } + bool send = false; + lock (sendMtex) + { + if (Interlocked.CompareExchange(ref msgAvailable, 0, 1) == 1) + { + lock (bufferMutex) + { + var temp = sendStream; + sendStream = flushStream; + flushStream = temp; + + sendStream.Position32 = 0; + } + send = true; + } + else + { + Interlocked.Exchange(ref sendActive, 0); + } + } + + if (send) + { + + sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); + + if (!connectedSocket.SendAsync(sendArgs)) + { + Sent(null, sendArgs); + } + } + } + catch (Exception ex) + { + Log(ex.StackTrace); + CloseChannel(); + } + + } public void Start() { - client.SetConnectedSocket(connectedSocket, ScatterGatherConfig.UseBuffer); + Receive(); } + private void InitializeReceiver() + { + receiveArgs = new SocketAsyncEventArgs(); + var buff = new byte[1280000]; + receiveArgs.SetBuffer(buff, 0, buff.Length); + receiveArgs.Completed += Received; + } - public void SendAsync(byte[] buffer, int offset, int count) + private void Receive() + { + if (!connectedSocket.ReceiveAsync(receiveArgs)) + { + ThreadPool.UnsafeQueueUserWorkItem(_ => Received(null, receiveArgs), null); + } + } + + private void Received(object sender, SocketAsyncEventArgs e) + { + if (e.SocketError != SocketError.Success) + { + ErrorAndEnd($"While receiving a socket error occured {e.SocketError}"); + CloseChannel(); + return; + } + else if (e.BytesTransferred == 0) + { + Log("0 bytes"); + CloseChannel(); + return; + } + totalBytesReceived += e.BytesTransferred; + try + { + HandleReceived(e.Buffer, e.Offset, e.BytesTransferred); + } + catch (Exception ex) + { + ErrorAndEnd(ex.Message + "\n" + ex.StackTrace); + return; + } + + Receive(); + } + + protected virtual void HandleReceivedBytes(byte[] buffer, int offset, int count) + { + var flag = (MessageFlags)buffer[offset++]; + count--; + HandleReceivedMessage(buffer, offset, count, flag); + + } + + protected virtual void HandleReceivedMessage(byte[] buffer, int offset, int count, MessageFlags flag) + { + switch (flag) + { + case MessageFlags.StandardMessage: + PublishBytes(buffer, offset, count); + break; + + case MessageFlags.KeepAliveMessage: + keepAlive.HandleMessage(flag, buffer, offset, count); + break; + + case MessageFlags.Pong: + case MessageFlags.Ping: + pinger.HandleMessage(flag, buffer, offset, count); + break; + } + } + + protected void PublishBytes(byte[] buffer, int offset, int count) + { + BytesReceived?.Invoke(buffer, offset, count); + } + + public void CloseChannel() + { + Console.WriteLine("Closing channel"); + if (Interlocked.CompareExchange(ref Closing, 1, 0) == 0) + { + try + { + connectedSocket.Shutdown(SocketShutdown.Both); + } + catch { } + + Disconnected?.Invoke(); + } + } + + + + private void HandleReceived(byte[] buffer, int offset, int bytesTransferred) + { + reader.ParseBytes(buffer, offset, bytesTransferred); + } + + + + protected void ErrorAndEnd( string errMsg) + { + Log(errMsg); + CloseChannel(); + } + + private void Log(string v) + { + Console.WriteLine(v); + } + + + + private bool IsSessionClosing() { - client.SendAsync(buffer, offset, count); + return Interlocked.CompareExchange(ref Closing, 0, 0) == 1; } - } } diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs deleted file mode 100644 index 78be85b..0000000 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel2.cs +++ /dev/null @@ -1,257 +0,0 @@ -using NetworkLibrary.Components; -using NetworkLibrary.Components.MessageProcessor.Unmanaged; -using NetworkLibrary.DistributedP2P.Client; -using System; -using System.Collections.Generic; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using NetworkLibrary.Components.MessageBuffer; -using System.IO; -using NetworkLibrary.DistributedP2P.Channels.Components; - -namespace NetworkLibrary.DistributedP2P.Channels -{ - /* - * Features: - Keep ALive - Key Exchange - - */ - public class TcpChannel:IChannel - { - public ChannelInfo Info { get; private set; } - - public event Action BytesReceived; - public event Action Disconnected; - - private readonly Socket connectedSocket; - private int totalBytesReceived; - private SocketAsyncEventArgs receiveArgs; - private int Closing = 0; - - public PooledMemoryStream sendStream = new PooledMemoryStream(); - public PooledMemoryStream flushStream = new PooledMemoryStream(); - - private object bufferMutex = new object(); - private object sendMtex = new object(); - private int sendActive = 0; - private int msgAvailable = 0; - - private SocketAsyncEventArgs sendArgs; - private ByteMessageReader reader = new ByteMessageReader(); - - public TcpChannel(ChannelInfo info, Socket connectedSocket) - { - Info = info; - this.connectedSocket = connectedSocket; - - StartReceiver(); - - sendArgs = new SocketAsyncEventArgs(); - sendArgs.Completed += Sent; - reader.OnMessageReady += (b, o, c) => BytesReceived?.Invoke(b,o,c); - } - - - public void SendAsync(byte[] buffer, int offset, int count) - { - if (IsSessionClosing()) - return; - - lock (bufferMutex) - { - int lenghtPos = sendStream.Position32; - sendStream.Position32 += 4; - - int amountWritten = WriteData(buffer, offset, count); - int lastPos = sendStream.Position32; - - sendStream.Position32 = lenghtPos; - sendStream.WriteInt(amountWritten); - sendStream.Position32 = lastPos; - - } - - SignalSend(); - } - - protected virtual int WriteData(byte[] buffer, int offset, int count) - { - // Write Flag here - sendStream.Write(buffer, offset, count); - return count; - } - private void SignalSend() - { - lock (sendMtex) - { - if(Interlocked.CompareExchange(ref sendActive,1,0) == 0) - { - lock (bufferMutex) - { - var temp = sendStream; - sendStream = flushStream; - flushStream = temp; - - sendStream.Position32 = 0; - } - sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); - - if (!connectedSocket.SendAsync(sendArgs)) - { - ThreadPool.UnsafeQueueUserWorkItem(_=> Sent(null, sendArgs),null); - } - } - else - { - Interlocked.Exchange(ref msgAvailable, 1); - } - } - } - private void Sent(object sender, SocketAsyncEventArgs e) - { - try - { - if (IsSessionClosing()) - return; - - if (e.SocketError != SocketError.Success) - { - HandleError(e, "while recieving from "); - CloseChannel(); - return; - } - - else if (e.BytesTransferred == 0) - { - CloseChannel(); - return; - } - bool send = false; - lock (sendMtex) - { - if (Interlocked.CompareExchange(ref msgAvailable, 0, 1) == 1) - { - lock (bufferMutex) - { - var temp = sendStream; - sendStream = flushStream; - flushStream = temp; - - sendStream.Position32 = 0; - } - send = true; - } - else - { - Interlocked.Exchange(ref sendActive, 0); - } - } - - if (send) - { - sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); - - if (!connectedSocket.SendAsync(sendArgs)) - { - Sent(null, sendArgs); - } - } - } - catch(Exception ex) - { - CloseChannel(); - } - - - } - - public void Start() - { - Receive(); - } - private void StartReceiver() - { - receiveArgs = new SocketAsyncEventArgs(); - var buff = new byte[1280000]; - receiveArgs.SetBuffer(buff, 0, buff.Length); - receiveArgs.Completed += Received; - - } - - private void Receive() - { - if (!connectedSocket.ReceiveAsync(receiveArgs)) - { - ThreadPool.UnsafeQueueUserWorkItem(_ => Received(null, receiveArgs), null); - } - } - - private void Received(object sender, SocketAsyncEventArgs e) - { - if (e.SocketError != SocketError.Success) - { - HandleError(e, "while recieving from "); - CloseChannel(); - return; - } - else if (e.BytesTransferred == 0) - { - CloseChannel(); - return; - } - totalBytesReceived += e.BytesTransferred; - try - { - HandleReceived(e.Buffer, e.Offset, e.BytesTransferred); - } - catch (Exception ex) - { - Log( ex.Message + "\n" + ex.StackTrace); - CloseChannel(); - return; - } - - Receive(); - } - - public void CloseChannel() - { - if(Interlocked.CompareExchange(ref Closing,1,0) == 0) - { - try - { - connectedSocket.Shutdown(SocketShutdown.Both); - } - catch { } - - } - } - - private void Log(string v) - { - - } - - private void HandleReceived(byte[] buffer, int offset, int bytesTransferred) - { - reader.ParseBytes(buffer, offset, bytesTransferred); - } - - - - private void HandleError(SocketAsyncEventArgs e, string v) - { - - } - - - - private bool IsSessionClosing() - { - return Interlocked.CompareExchange(ref Closing, 0, 0) == 1; - } - - } -} diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs index 8119f5a..2e3de0a 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs @@ -1,5 +1,4 @@ -using NetworkLibrary.Components.Crypto.Algorithms; -using NetworkLibrary.DistributedP2P.Channels.Components; +using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.UDP.Jumbo; using NetworkLibrary.UDP.Reliable.Components; @@ -7,6 +6,8 @@ using System; using System.Net; using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; namespace NetworkLibrary.DistributedP2P.Channels { @@ -17,26 +18,32 @@ public class UdpChannel : IChannel private UdpChannelBase innerchannel; public event Action OnMessageReceived; + public event Action Disconnected; protected JumboModule JumboUdp = new JumboModule(0); internal ReliableModule ReliableUdp; - ReliableModule internalReliableModule; + private ReliableModule internalReliableModule; + protected KeepAlive keepAlive; + protected Pinger pinger; + + private int isClosed = 0; + private int isDisposed = 0; public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) { - + Info = info; innerchannel = new UdpChannelBase(udpSocket, receiveEp, info); JumboUdp.SendToSocket = SendJumboSegment; JumboUdp.MessageReceived = HandleMessage; SenderModule sender = new SenderModule(); - + sender.MaxSegmentSize = 1280; - sender.MinWindowSize = 1280*2; + sender.MinWindowSize = 1280 * 2; - ReliableUdp = new ReliableModule(receiveEp,sender); + ReliableUdp = new ReliableModule(receiveEp, sender); ReliableUdp.OnReceived += (e, b, o, c) => HandleMessage(b, o, c); ReliableUdp.OnSend += SendRudpSegment; @@ -49,8 +56,18 @@ public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) internalReliableModule.OnReceived += (e, b, o, c) => HandleInternalReliableMessage(b, o, c); internalReliableModule.OnSend += SendInternalRudpSegment; + keepAlive = new KeepAlive(); + keepAlive.SendData += SendInternalReliable; + keepAlive.NotAlive += HandleDisconnect; + + pinger = new Pinger(); + pinger.SendData += SendInternalReliable; + + } + + public void Start() { innerchannel.OnMessageReceived += BytesReceived; @@ -62,37 +79,54 @@ protected virtual void BytesReceived(byte[] buffer, int offset, int count) // filter flags var flag = (MessageFlags)buffer[offset++]; count--; + HandleReivedMessage(buffer, offset, count, flag); + } + protected virtual void HandleReivedMessage(byte[] buffer, int offset, int count, MessageFlags flag) + { switch (flag) { case MessageFlags.StandardMessage: HandleMessage(buffer, offset, count); break; + case MessageFlags.JumboMessage: HandleJumboSegment(buffer, offset, count); break; + case MessageFlags.ReliableMessage: HandleRudpSegment(buffer, offset, count); break; + case MessageFlags.KeepAliveMessage: + keepAlive.HandleMessage(flag, buffer, offset, count); break; - case MessageFlags.HP: - case MessageFlags.HPAck: + case MessageFlags.Kill: + HandleDisconnect(); break; + case MessageFlags.InternalReliableMessage: HandleIncomingInternalRudpSegment(buffer, offset, count); break; + case MessageFlags.Ping: + case MessageFlags.Pong: + pinger.HandleMessage(flag, buffer, offset, count); break; } + + } protected virtual void HandleInternalReliableMessage(byte[] buffer, int offset, int count) { - + var flag = (MessageFlags)buffer[offset++]; + count--; + + HandleReivedMessage(buffer, offset, count, flag); } protected virtual void HandleMessage(byte[] buffer, int offset, int count) @@ -121,7 +155,7 @@ protected void HandleIncomingInternalRudpSegment(byte[] buffer, int offset, int } internal virtual void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) { - SendWithFlag(MessageFlags.ReliableMessage, buffer, offset, count); + SendWithFlag(MessageFlags.ReliableMessage, buffer, offset, count); } internal virtual void SendInternalRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) @@ -129,6 +163,11 @@ internal virtual void SendInternalRudpSegment(ReliableModule module, byte[] buff SendWithFlag(MessageFlags.InternalReliableMessage, buffer, offset, count); } + public Task Ping() + { + return pinger.Ping(); + } + public virtual void Send(byte[] buffer, int offset, int count) { @@ -138,7 +177,7 @@ public virtual void Send(byte[] buffer, int offset, int count) } else { - SendWithFlag(MessageFlags.StandardMessage, buffer, offset, count); + SendWithFlag(MessageFlags.StandardMessage, buffer, offset, count); } } @@ -177,10 +216,53 @@ protected void SendInternal(byte[] bytes, int offset, int count) catch (Exception e) { } - + + } + + + public void CloseChannel() + { + try + { + SendWithFlag(MessageFlags.Kill, new byte[1], 0, 1); + } + catch { } + + HandleDisconnect(); + } + + protected void HandleDisconnect() + { + if (Interlocked.CompareExchange(ref isClosed, 1, 0) == 0) + { + Disconnected?.Invoke(); + Dispose(); + } + + } + public void Dispose() { + if (Interlocked.CompareExchange(ref isDisposed, 1, 0) == 0) + ReleseResources(); + } + + protected virtual void ReleseResources() + { + ReliableUdp.Close(); + internalReliableModule.Close(); + keepAlive.Close(); + JumboUdp.Release(); + + innerchannel.CloseChannel(); + + Disconnected = null; + OnMessageReceived = null; + + } + + } } diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 227d3e2..c968574 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -39,6 +39,7 @@ namespace NetworkLibrary.DistributedP2P.Client public Guid SessionId { get; private set; } + private EndpointData DiscoveryServerEndpoint; private int connected = 0; public bool IsConnected { @@ -80,6 +81,7 @@ public async Task ConnectAsync(string ip, int port) { serverEndpoint = new EndpointData(ip, port); SessionId = conState.SessionId; + DiscoveryServerEndpoint = new EndpointData(ip, conState.EDSPort); IsConnected = true; timeSync.StartAutoTimeSync(5000); return true; @@ -237,7 +239,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp if(info.ChannelType == ChannelType.Udp || info.ChannelType == ChannelType.SecureUdp) { - var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, info); + var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info); stateManager.RegisterState(state); state.Start(); @@ -286,7 +288,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp private void ManageUdpHolepunchRequest(MessageEnvelope envelope) { - var state = new ClientUdpHolepunchState(envelope.MessageId, envelope.From, this,serverEndpoint, null); + var state = new ClientUdpHolepunchState(envelope.MessageId, envelope.From, this,serverEndpoint,DiscoveryServerEndpoint, null); stateManager.RegisterState(state); state.OnComplete += State_OnComplete; state.HandleMessage(envelope); diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs index 819eb8f..5af5a52 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs @@ -19,7 +19,7 @@ internal class ClientConnectionState : ConversationStateBase private TaskCompletionSource Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public Guid SessionId { get; private set; } - + public int EDSPort { get; private set; } public ClientConnectionState(Guid stateId, IDistributedConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken):base(stateId,20000) { this.connection = connection; @@ -95,6 +95,7 @@ private void SendClientPublicData(MessageEnvelope message) private void HandleConnectionSucces(MessageEnvelope message) { SessionId = message.To; + EDSPort = int.Parse(message.KeyValuePairs["EDSPort"]); Completed(succes: true); } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 4acf324..195ec45 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -22,6 +22,7 @@ internal class ClientUdpHolepunchState : ConversationStateBase private readonly Guid destId; private readonly IDistributedConnection connection; private readonly EndpointData serverEndpoint; + private readonly EndpointData discoveryServerendPoint; public Socket Socket; private bool isInitiator; @@ -31,26 +32,28 @@ internal class ClientUdpHolepunchState : ConversationStateBase private byte[] otherPublicKey; public byte[] SharedSecret; public ChannelInfo ChannelInfo; - public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 20000) + public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerendPoint, ChannelInfo info) : base(stateId, 20000) { this.destId = destId; this.connection = connection; this.serverEndpoint = serverEndpoint; + this.discoveryServerendPoint = discoveryServerendPoint; this.ChannelInfo = info; } //the initiator - public void Start() + public async void Start() { isInitiator = true; Log(StateId.ToString()); - int port = StartUdpSocket(); + EndpointData data = await StartUdpSocket(); + if (data == null) return; var msg = CreateEnvelope(); msg.Header = InternalConstants.RequestHolepunchUdp; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Port"] = port.ToString(); + msg.KeyValuePairs["Port"] = data.Port.ToString(); msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; @@ -66,28 +69,36 @@ public void Start() public override void HandleMessage(MessageEnvelope message) { - switch (message.Header) + try { - case InternalConstants.RequestHolepunchUdp: - HandleRemoteHpRequest(message); - break; - - case InternalConstants.StartHP: - message.LockBytes(); - ThreadPool.UnsafeQueueUserWorkItem((s) => StartHolepunchRoutine(message), null); - break; - - case InternalConstants.PunchSuccesAck: - HandleRemoteSucces(message); - break; - case InternalConstants.PunchFailAck: - HandleFailure(); - break; + switch (message.Header) + { + case InternalConstants.RequestHolepunchUdp: + HandleRemoteHpRequest(message); + break; + + case InternalConstants.StartHP: + message.LockBytes(); + ThreadPool.UnsafeQueueUserWorkItem((s) => StartHolepunchRoutine(message), null); + break; + + case InternalConstants.PunchSuccesAck: + HandleRemoteSucces(message); + break; + case InternalConstants.PunchFailAck: + HandleFailure(); + break; + } + } + catch(Exception ex) + { + OnError(ex.StackTrace); } + } // the destination peer of hp - private void HandleRemoteHpRequest(MessageEnvelope message) + private async void HandleRemoteHpRequest(MessageEnvelope message) { Log(StateId.ToString()); @@ -95,11 +106,13 @@ private void HandleRemoteHpRequest(MessageEnvelope message) ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; - int port = StartUdpSocket(); + EndpointData data = await StartUdpSocket(); + if(data == null) return; + var msg = CreateEnvelope(); msg.Header = InternalConstants.AckRequestHolepunchUdp; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Port"] = port.ToString(); + msg.KeyValuePairs["Port"] = data.Port.ToString(); if (ChannelInfo.RequiresKeyExchange()) msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); @@ -185,8 +198,9 @@ private void TryPunch(IPEndPoint ep, MessageFlags flag) } - private int StartUdpSocket() + private async Task StartUdpSocket() { + Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); Socket.SendBufferSize = 12800000; Socket.ReceiveBufferSize = 12800000; @@ -194,9 +208,15 @@ private int StartUdpSocket() Socket.Bind(new IPEndPoint(IPAddress.Any, 0)); + EndpointData data = await EndpointDiscoveryClient.GetUdpPublicEndpoint(Socket, discoveryServerendPoint.ToIpEndpoint(),5000); + if(data == null) + { + OnError("Failed to retrieve public endpoint"); + return null; + } Receive(); - return ((IPEndPoint)Socket.LocalEndPoint).Port; + return data; } private async void Receive() { @@ -288,6 +308,16 @@ private void TimedOut() Cancel(); } + private void OnError(string error) + { + Log("Exception :" + error); + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PunchFail; + connection.SendAsyncMessage(msg); + Cancel(); + } + + private void HandleFailure() { Log("Failed Punch"); diff --git a/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs b/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs new file mode 100644 index 0000000..6d6be33 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs @@ -0,0 +1,77 @@ +using NetworkLibrary.P2P.Components.HolePunch; +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class EndpointDiscoveryClient + { + static byte[] dummy = new byte[1]; + + public static async Task GetTcpPublicEndpoint(Socket socket, IPEndPoint whereToAsk, int timeoutMs) + { + var retrieveTask = GetPublicEndpointTcp(socket, whereToAsk); + var result = await Task.WhenAny(retrieveTask, Task.Delay(timeoutMs)).ConfigureAwait(false); + if (result == retrieveTask) + { + if (!retrieveTask.IsFaulted) + return await retrieveTask; + return null; + } + else + { + return null; + } + } + + public static async Task GetUdpPublicEndpoint(Socket socket, IPEndPoint whereToAsk, int timeoutMs) + { + var retrieveTask = GetPublicEndpointUdp(socket, whereToAsk); + var result = await Task.WhenAny(retrieveTask, Task.Delay(timeoutMs)).ConfigureAwait(false); + if (result == retrieveTask) + { + if (!retrieveTask.IsFaulted) + return await retrieveTask; + return null; + } + else + { + return null; + } + } + + + private static async Task GetPublicEndpointTcp(Socket socket, IPEndPoint whereToAsk) + { + + await socket.ConnectAsync(whereToAsk).ConfigureAwait(false); + var buff = BufferPool.RentBuffer(1024); + int received = await socket.ReceiveAsync(new ArraySegment(buff), SocketFlags.None).ConfigureAwait(false); + + + if (received == 0) + { + throw new Exception("No data received"); + } + + BufferPool.ReturnBuffer(buff); + + return KnownTypeSerializer.DeserializeEndpointData(buff, 0); + + } + + private static async Task GetPublicEndpointUdp(Socket socket, IPEndPoint whereToAsk) + { + await socket.SendToAsync(new ArraySegment(dummy), SocketFlags.None, whereToAsk).ConfigureAwait(false); + var buff = BufferPool.RentBuffer(1024); + + await socket.ReceiveFromAsync(new ArraySegment(buff), SocketFlags.None, whereToAsk).ConfigureAwait(false); + BufferPool.ReturnBuffer(buff); + + return KnownTypeSerializer.DeserializeEndpointData(buff, 0); + + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryServer.cs b/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryServer.cs new file mode 100644 index 0000000..e7d1eeb --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryServer.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using NetworkLibrary.DistributedP2P.Channels; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.TCP.Base; +using NetworkLibrary.Utils; +using NetworkLibrary.UDP; + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class EndpointDiscoveryServer + { + + AsyncTcpServer tcpServer; + AsyncUdpServerLite udpServer; + public EndpointDiscoveryServer(int port) + { + tcpServer = new AsyncTcpServer(port); + tcpServer.OnClientAccepted += HandleTcpClient; + udpServer = new AsyncUdpServerLite(port); + udpServer.OnBytesRecieved += HandleUdpClient; + } + + private void HandleUdpClient(IPEndPoint remoteEp, byte[] bytes, int offset, int count) + { + try + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeEndpointData(stream, new EndpointData(remoteEp)); + udpServer.SendBytesToClient(remoteEp, stream.GetBuffer(), 0, stream.Position32); + } + catch { } + + } + + private void HandleTcpClient(Guid guid) + { + try + { + var remoteEp = tcpServer.GetSessionEndpoint(guid); + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeEndpointData(stream, new EndpointData(remoteEp)); + tcpServer.SendBytesToClient(guid, stream.GetBuffer(), 0, stream.Position32); + + TimerService.RegisterTimer(guid, 1000, () => + { + tcpServer?.CloseSession(guid); + }); + } + catch { } + + } + + public void Start() + { + tcpServer.StartServer(); + udpServer.StartServer(); + } + + internal void Dispose() + { + try + { + tcpServer?.ShutdownServer(); + udpServer?.Dispose(); + } + catch { } + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/StateManager.cs b/NetworkLibrary/DistributedP2P/Components/StateManager.cs index 5a5be25..f33230b 100644 --- a/NetworkLibrary/DistributedP2P/Components/StateManager.cs +++ b/NetworkLibrary/DistributedP2P/Components/StateManager.cs @@ -36,7 +36,15 @@ public bool HandleMessage(MessageEnvelope message) if (states.TryGetValue(stateId, out var state)) { - state.HandleMessage(message); + try + { + state.HandleMessage(message); + } + catch + { + state.Cancel(); + UnregisterState(stateId); + } return true; } diff --git a/NetworkLibrary/DistributedP2P/Components/TimerService.cs b/NetworkLibrary/DistributedP2P/Components/TimerService.cs index e43635c..80d1639 100644 --- a/NetworkLibrary/DistributedP2P/Components/TimerService.cs +++ b/NetworkLibrary/DistributedP2P/Components/TimerService.cs @@ -12,7 +12,12 @@ public static void RegisterTimer(Guid timerId,int delay, Action OnTime) { var timer = new Timer(s => { - OnTime?.Invoke(); + try + { + OnTime?.Invoke(); + } + catch { } + CancelTimeout(timerId); }, null, delay, Timeout.Infinite); diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index ce30331..f8ba410 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -36,18 +36,19 @@ public class ServerParameters public int SSlPort; public int TcpPort; public int UdpPort; + public int DiscoveryServerPort; } public class DistributedLobbyServerBase : IDistributedConnection,IDisposable where S : ISerializer, new() { public readonly int SSlPort; public readonly int TcpPort; public readonly int UdpPort; + public readonly int DiscoveryServerPort; private X509Certificate2 serverCertificate; SecureMessageServer sslServer; - AsyncTcpServer tcpServer; - AsyncUdpServer udpServer; + IAuthenticator authenticator; IServerDbConnector dbConnector; @@ -60,6 +61,8 @@ public class ServerParameters PipeManager pipeManager; private byte[] serverKey = new byte[16]; + + EndpointDiscoveryServer discoveryServer; public DistributedLobbyServerBase(Dependencies dependencies, ServerParameters parameters) { authenticator = dependencies.Authenticator; @@ -67,6 +70,7 @@ public DistributedLobbyServerBase(Dependencies dependencies, ServerParameters pa SSlPort = parameters.SSlPort; TcpPort = parameters.TcpPort; UdpPort = parameters.UdpPort; + DiscoveryServerPort = parameters.DiscoveryServerPort; serverCertificate = parameters.certificate ?? CertificateGenerator.GenerateSelfSignedCertificate(); } @@ -93,6 +97,8 @@ public void StartServer() sessionManager = new SessionManager(this); sessionManager.PeerListPublish += PublishPeerList; + discoveryServer = new EndpointDiscoveryServer(DiscoveryServerPort); + discoveryServer.Start(); } @@ -119,7 +125,7 @@ private void HandleConnRequest(MessageEnvelope msg) { Guid stateId = msg.MessageId; - var state = new ServerConnectionState(stateId, msg.From, this, authenticator, dbConnector); + var state = new ServerConnectionState(stateId, msg.From, this, authenticator, dbConnector,DiscoveryServerPort); stateManager.RegisterState(state); state.HandleMessage(msg); @@ -263,8 +269,7 @@ private void SslClientDisconnected(Guid guid) public void ShutDownServer() { sslServer.ShutdownServer(); - tcpServer.ShutdownServer(); - udpServer.Dispose(); + } public DateTime GetDateTime() @@ -285,6 +290,7 @@ public void Dispose() { sslServer.ShutdownServer(); pipeManager.Dispose(); + discoveryServer.Dispose(); } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs index 2a54584..7980201 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs @@ -17,16 +17,18 @@ internal class ServerConnectionState : ConversationStateBase private readonly IDistributedConnection connection; private readonly IAuthenticator authenticator; private readonly IServerDbConnector dbConnector; + private readonly int eDSPort; private IAuthenticationResult tokenResult; public List clientLocalIps; - public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection connection, IAuthenticator authenticator, IServerDbConnector dbConnector):base(stateId,20000) + public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection connection, IAuthenticator authenticator, IServerDbConnector dbConnector,int EDSPort):base(stateId,20000) { this.EphemeralClientId = clientId; this.connection = connection; this.authenticator = authenticator; this.dbConnector = dbConnector; + eDSPort = EDSPort; } @@ -173,6 +175,8 @@ private void SendGood() var msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionAckGood; msg.To = EphemeralClientId; + msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["EDSPort"] = eDSPort.ToString(); lock (cancellationMutex) { if (IsCompleted()) diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index ffe6881..e28369f 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -94,7 +94,9 @@ private static DistributedLobbyServerBase ArrangeServer() certificate = null, SSlPort = 20010, TcpPort = 20011, - UdpPort = 20012 + UdpPort = 20012, + DiscoveryServerPort = 20013, + }; var server = new DistributedLobbyServerBase(dep, param); server.StartServer(); @@ -170,6 +172,7 @@ void Channel_BytesReceived(byte[] buff, int offset, int count) Assert.AreEqual(received, data.Length); } + [TestMethod] public void SecurePipeTest() { @@ -178,6 +181,8 @@ public void SecurePipeTest() TaskCompletionSource tcs = new TaskCompletionSource(); ManualResetEvent mre = new ManualResetEvent(false); + List received = new List(); + int iter = 20; using var server = ArrangeServer(); @@ -196,15 +201,22 @@ public void SecurePipeTest() byte[] data = new byte[12800000]; channel1.Start(); - channel1.SendAsync(data, 0, data.Length); - Thread.Sleep(100); - mre.Set(); - int received = 0; + + var ping = channel1.Ping().Result; + + for (int i = 0; i < iter; i++) + { + data[0] = (byte)i; + channel1.SendAsync(data, 0, data.Length); + if(i%2 ==0) + Thread.Sleep(1000); + } + void PeerConnected(IChannel channel_) { - mre.WaitOne();//emulate bad syncronisation + // mre.WaitOne();//emulate bad syncronisation var channel = (SecureTcpChannel)channel_; channel.BytesReceived += Channel_BytesReceived; channel.Disconnected += Disconnected; @@ -219,14 +231,19 @@ void Disconnected() void Channel_BytesReceived(byte[] buff, int offset, int count) { - received = count; - tcs.TrySetResult(true); + received.Add(buff[offset]); + + if(received.Count == iter) + tcs.TrySetResult(true); } var ss = tcs.Task.Result; Assert.IsTrue(ss); - Assert.AreEqual(received, data.Length); + for (int i = 0; i < received.Count; i++) + { + Assert.IsTrue(received[i] == i); + } } @@ -295,7 +312,10 @@ public void PipeTestUdpSecure() Assert.IsNotNull(channel1); channel1.Start(); + Thread.Sleep(5000); + var ping = channel1.Ping().Result; + Console.WriteLine(ping); byte[] data = new byte[12800000]; channel1.SendReliable(data, 0, data.Length); @@ -709,7 +729,7 @@ public void TestTcpChannelParallelSend() var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; - var data = new byte[12800000]; + var data = new byte[1280000]; data[0] = 1; int iter = 100; From 57bc6274e3f7100f792b2e3f7f0e35028f6ca9d0 Mon Sep 17 00:00:00 2001 From: dogancan ozturk Date: Wed, 9 Apr 2025 23:53:07 +0200 Subject: [PATCH 18/27] fixed sync issue on tcp channel --- .../Channels/Components/KeepAlive.cs | 5 +- .../Channels/SecureTcpChannel.cs | 6 ++ .../DistributedP2P/Channels/TcpChannel.cs | 53 +++++++++------- .../Client/DistributedLobbyClient.cs | 5 +- .../ClientTcpHolepunchState.cs | 2 +- .../ClientTcpHolepunchState2.cs | 62 ++++++++++++++----- .../DistributedP2P/DistP2PServerclientTest.cs | 19 +++--- 7 files changed, 98 insertions(+), 54 deletions(-) diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs index dfc18da..a2eeddb 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs @@ -23,6 +23,7 @@ private async void StartSendRoutine() while (!stop) { await Task.Delay(4000); + if (stop) break; SendKeepAlive(); if ((DateTime.Now - lastReceived).TotalMilliseconds > 10000) @@ -45,8 +46,8 @@ public void HandleMessage(MessageFlags flag, byte[] buffer, int offset, int coun RandomNumberGenerator r = RandomNumberGenerator.Create(); private void SendKeepAlive() { - r.GetBytes(innerBuff, 0, 16); - SendData?.Invoke(MessageFlags.KeepAliveMessage, innerBuff, 0, 16); + r.GetBytes(innerBuff, 0, 32); + SendData?.Invoke(MessageFlags.KeepAliveMessage, innerBuff, 0, 32); Console.WriteLine("Keep alive sent"); } diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs index 827d501..414b1d1 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs @@ -114,5 +114,11 @@ private void EnsureCapacityDec(int count) } } + protected override void ReleaseResources() + { + keyManager.Close(); + base.ReleaseResources(); + } + } } diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs index 8e5309d..3f8bd02 100644 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs @@ -2,7 +2,6 @@ using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; using System; -using System.Drawing; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -49,7 +48,7 @@ public TcpChannel(ChannelInfo info, Socket connectedSocket) keepAlive = new KeepAlive(); keepAlive.SendData += FlagAndSend; - keepAlive.NotAlive += ()=>ErrorAndEnd("Kepp Alive Timed Out"); + keepAlive.NotAlive += () => ErrorAndEnd("Kepp Alive Timed Out"); pinger = new Pinger(); pinger.SendData += FlagAndSend; @@ -68,6 +67,7 @@ public void SendAsync(byte[] buffer, int offset, int count) protected void FlagAndSend(MessageFlags flag, byte[] buffer, int offset, int count) { + if (IsSessionClosing()) return; @@ -87,16 +87,20 @@ protected void FlagAndSend(MessageFlags flag, byte[] buffer, int offset, int cou sendStream.Position32 = lenghtPos; sendStream.WriteInt(amountWritten + prefixLen + 1); sendStream.Position32 = lastPos; + + } catch (Exception ex) { ErrorAndEnd(ex.Message + "\n" + ex.StackTrace); return; } - - } + + } SignalSend(); + + } protected virtual int WritePrefix(PooledMemoryStream sendStream) @@ -118,15 +122,12 @@ private void SignalSend() { lock (bufferMutex) { - var temp = sendStream; - sendStream = flushStream; - flushStream = temp; - + var flush = Interlocked.Exchange(ref sendStream, flushStream); + Interlocked.Exchange(ref flushStream, flush); sendStream.Position32 = 0; } - - sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); + sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); if (!connectedSocket.SendAsync(sendArgs)) { ThreadPool.UnsafeQueueUserWorkItem(_ => Sent(null, sendArgs), null); @@ -134,7 +135,8 @@ private void SignalSend() } else { - Interlocked.Exchange(ref msgAvailable, 1); + if (sendStream.Position32 != 0) + Interlocked.Exchange(ref msgAvailable, 1); } } } @@ -165,9 +167,8 @@ private void Sent(object sender, SocketAsyncEventArgs e) { lock (bufferMutex) { - var temp = sendStream; - sendStream = flushStream; - flushStream = temp; + var flush = Interlocked.Exchange(ref sendStream, flushStream); + Interlocked.Exchange(ref flushStream, flush); sendStream.Position32 = 0; } @@ -181,12 +182,12 @@ private void Sent(object sender, SocketAsyncEventArgs e) if (send) { - + sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); if (!connectedSocket.SendAsync(sendArgs)) { - Sent(null, sendArgs); + ThreadPool.UnsafeQueueUserWorkItem(_ => Sent(null, sendArgs), null); } } } @@ -285,17 +286,21 @@ public void CloseChannel() Console.WriteLine("Closing channel"); if (Interlocked.CompareExchange(ref Closing, 1, 0) == 0) { - try - { - connectedSocket.Shutdown(SocketShutdown.Both); - } - catch { } + ReleaseResources(); + } + } - Disconnected?.Invoke(); + protected virtual void ReleaseResources() + { + try + { + connectedSocket.Shutdown(SocketShutdown.Both); } + catch { } + keepAlive.Close(); + Disconnected?.Invoke(); } - private void HandleReceived(byte[] buffer, int offset, int bytesTransferred) { @@ -304,7 +309,7 @@ private void HandleReceived(byte[] buffer, int offset, int bytesTransferred) - protected void ErrorAndEnd( string errMsg) + protected void ErrorAndEnd(string errMsg) { Log(errMsg); CloseChannel(); diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index c968574..221bce0 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -82,6 +82,7 @@ public async Task ConnectAsync(string ip, int port) serverEndpoint = new EndpointData(ip, port); SessionId = conState.SessionId; DiscoveryServerEndpoint = new EndpointData(ip, conState.EDSPort); + Console.WriteLine($"Connected to server {ip}:{port} with session {SessionId} and discovery port {conState.EDSPort}"); IsConnected = true; timeSync.StartAutoTimeSync(5000); return true; @@ -255,7 +256,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp { if (strategy == TcpHolePunchStrategy.Sequential) { - var state = new ClientTcpHolepunchState2(Guid.NewGuid(), destination, this, serverEndpoint, info); + var state = new ClientTcpHolepunchState2(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info); stateManager.RegisterState(state); state.Start(); @@ -324,7 +325,7 @@ void State_OnComplete(IConversationState obj) private void ManageTcpHolepunchRequest2(MessageEnvelope envelope) { - var state = new ClientTcpHolepunchState2(envelope.MessageId, envelope.From, this, serverEndpoint, null); + var state = new ClientTcpHolepunchState2(envelope.MessageId, envelope.From, this, serverEndpoint, DiscoveryServerEndpoint, null); stateManager.RegisterState(state); state.OnComplete += State_OnComplete; state.HandleMessage(envelope); diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs index 92fe25e..fc37c8f 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs @@ -148,7 +148,7 @@ private void StartHolepunchRoutine(MessageEnvelope message) for (int i = 0; i < 4; i++) { - if (TryConnect(publicEp, (1000))) + if (TryConnect(publicEp, (2000))) return; //PreciseTimeAwaiter.Wait(nextTryTime - connection.GetTime()); if (IsEstablished) return; diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs index 2139d46..77c336a 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs @@ -17,6 +17,7 @@ internal class ClientTcpHolepunchState2 : ConversationStateBase private readonly Guid destId; private readonly IDistributedConnection connection; private readonly EndpointData serverEndpoint; + private readonly EndpointData discoveryServerEp; private bool isInitiator; public Socket Socket; public IPEndPoint SuccesfulEndpoint; @@ -40,30 +41,38 @@ internal class ClientTcpHolepunchState2 : ConversationStateBase bool isListening = false; List localEndpoints = new List(); EndpointData publicEndpoint; + private EndpointData selfRemoteEp; private bool IsEstablished => Interlocked.CompareExchange(ref established, 0, 0) == 1; - public ClientTcpHolepunchState2(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 10000) + public ClientTcpHolepunchState2(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint,EndpointData discoveryServerEp, ChannelInfo info) : base(stateId, 10000) { this.destId = destId; this.connection = connection; this.serverEndpoint = serverEndpoint; + this.discoveryServerEp = discoveryServerEp; this.ChannelInfo = info; } //the initiator - public void Start() + public async void Start() { isInitiator = true; Log(StateId.ToString()); - localPort = BindPort(); + selfRemoteEp = await BindPort(); + if (selfRemoteEp == null) + return; + + localPort = selfEndpoint.Port; Log("Bound on port " + localPort); + Log("Remote port " + selfRemoteEp.Port); var msg = CreateEnvelope(); msg.Header = InternalConstants.RequestSimultaneousHolepunchTcp; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Port"] = localPort.ToString(); + // msg.KeyValuePairs["PortLocal"] = selfEndpoint.ToString(); + msg.KeyValuePairs["Port"] = selfRemoteEp.Port.ToString(); msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; @@ -101,7 +110,7 @@ public override void HandleMessage(MessageEnvelope message) } // the destination peer of hp - private void HandleRemoteHpRequest(MessageEnvelope message) + private async void HandleRemoteHpRequest(MessageEnvelope message) { Log(StateId.ToString()); @@ -109,6 +118,11 @@ private void HandleRemoteHpRequest(MessageEnvelope message) ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; + selfRemoteEp = await BindPort(); + if (selfRemoteEp == null) + return; + + localPort = StartTcpListener(); Log("listening on port " + localPort); @@ -116,7 +130,7 @@ private void HandleRemoteHpRequest(MessageEnvelope message) var msg = CreateEnvelope(); msg.Header = InternalConstants.AckRequestHolepunchTcp; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Port"] = localPort.ToString(); + msg.KeyValuePairs["Port"] = selfRemoteEp.Port.ToString(); if (ChannelInfo.RequiresKeyExchange()) msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); @@ -167,19 +181,19 @@ private void TryPunch() // if there are local endpoints to test if (localEndpoints.Count > 0) { - foreach (EndpointData localEp in localEndpoints) - { - if (TryConnect(localEp, 500)) - return; + //foreach (EndpointData localEp in localEndpoints) + //{ + // if (TryConnect(localEp, 500)) + // return; - if (IsCompleted()) return; + // if (IsCompleted()) return; - } + //} } - for (int i = 0; i < 2; i++) + for (int i = 0; i < 1; i++) { - if (TryConnect(publicEndpoint, (600))) + if (TryConnect(publicEndpoint, (2000))) return; if (IsCompleted()) return; @@ -288,12 +302,20 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) } } - private int BindPort() + private async Task BindPort() { var clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); clientSocket.Bind(selfEndpoint); + + var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket,discoveryServerEp.ToIpEndpoint(),5000); + if (remoteEp == null) + { + Log("Failed to get public endpoint"); + return null; + } selfEndpoint = (IPEndPoint)clientSocket.LocalEndPoint; + try { clientSocket?.Close(); @@ -302,7 +324,7 @@ private int BindPort() } catch { } - return selfEndpoint.Port; + return remoteEp; } @@ -403,6 +425,7 @@ private void HandleFailure() private void HandleRemoteSucces(MessageEnvelope message) { + Thread.Sleep(100); string use = message.KeyValuePairs["Use"]; if (ChannelInfo.RequiresKeyExchange()) SharedSecret = df.CalculateSharedSecret(otherPublicKey); @@ -415,7 +438,12 @@ private void HandleRemoteSucces(MessageEnvelope message) { Socket = acceptedSocket; } - + if(Socket == null) + { + Log("Failed to get socket"); + Completed(false); + return; + } SuccesfulEndpoint = (IPEndPoint)Socket.RemoteEndPoint; Log("Punched"); Completed(true); diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index e28369f..d8d82e1 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -660,7 +660,7 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) var ss = tcs.Task.Result; Assert.AreEqual(data.Length, received); - + } @@ -729,9 +729,9 @@ public void TestTcpChannelParallelSend() var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; - var data = new byte[1280000]; + var data = new byte[128000]; data[0] = 1; - int iter = 100; + int iter = 1000; cl2.PeerConnected += Cl2_PeerConnected; @@ -743,12 +743,12 @@ public void TestTcpChannelParallelSend() Assert.IsNotNull(channel1); channel1.Start(); - + Thread.Sleep(100); + Parallel.For(0, iter, (i) => { channel1.SendAsync(data, 0, data.Length); }); - Thread.Sleep(100); void Cl2_PeerConnected(IChannel obj) { @@ -762,7 +762,7 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) if (arg3 != data.Length) throw new Exception(); - if(Interlocked.Increment(ref numReceived) == 100) + if(Interlocked.Increment(ref numReceived) == iter) tcs.TrySetResult(true); } @@ -803,6 +803,7 @@ public void TestTcpChannelOrder() Assert.IsNotNull(channel1); channel1.Start(); + Thread.Sleep(100); for (int i = 0; i < iter; i++) { @@ -811,7 +812,6 @@ public void TestTcpChannelOrder() if(i%10 == 0) Thread.Sleep(1); }; - Thread.Sleep(100); void Cl2_PeerConnected(IChannel obj) { @@ -829,7 +829,10 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) if (Interlocked.Increment(ref numReceived) == 100) tcs.TrySetResult(true); } - + Task.Delay(2000).ContinueWith(t => + { + tcs.TrySetResult(false); + }); var ss = tcs.Task.Result; Assert.AreEqual(iter, numReceived); From f8785ebfe23cabf47f60cb96fb3e0c7d07d7140e Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Thu, 10 Apr 2025 16:59:13 +0200 Subject: [PATCH 19/27] stabilized tcp channel, refactored holepunch, started room broadcast development --- .../Components/Crypto/AesManager.cs | 23 ++ .../Crypto/Algorithms/AesGcmAlgorithm.cs | 56 +---- .../Components/Crypto/KeyDerivation/HKDF.cs | 20 ++ .../Channels/Components/ChannelFactory.cs | 4 +- .../Components/EphemeralKeyManager.cs | 24 +- .../Channels/SecureTcpChannel.cs | 3 +- .../Channels/SecureUdpChannel.cs | 3 +- .../DistributedP2P/Channels/TcpChannel.cs | 56 +++-- .../DistributedP2P/Client/ChannelInfo.cs | 2 +- .../ClientTcpHolepunchState2.cs | 171 +++++++------- .../ClientUdpHolepunchState.cs | 103 ++++++--- .../DistributedP2P/Components/IPHelper.cs | 142 ++++++++++-- .../Server/DistributedLobbyServer.cs | 6 +- .../Server/StateManagement/ServerPipeState.cs | 4 +- .../ServerTcpHolepunchState2.cs | 130 +++++------ .../ServerUdpHolepunchState.cs | 77 ++++--- .../SimpleRelay/PeerRoomState.cs | 37 +++ .../{PipeManager.cs => RelayService.cs} | 216 ++++++++++++++---- .../DistributedP2P/SimpleRelay/Room.cs | 95 ++++++++ .../HolePunch/KnownTypeSerializer.cs | 134 ++++++++++- .../P2P/Components/HolePunch/Messages.cs | 2 +- .../DistributedP2P/DistP2PServerclientTest.cs | 20 +- 22 files changed, 945 insertions(+), 383 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/SimpleRelay/PeerRoomState.cs rename NetworkLibrary/DistributedP2P/SimpleRelay/{PipeManager.cs => RelayService.cs} (62%) create mode 100644 NetworkLibrary/DistributedP2P/SimpleRelay/Room.cs diff --git a/NetworkLibrary/Components/Crypto/AesManager.cs b/NetworkLibrary/Components/Crypto/AesManager.cs index d6645f8..cca4b29 100644 --- a/NetworkLibrary/Components/Crypto/AesManager.cs +++ b/NetworkLibrary/Components/Crypto/AesManager.cs @@ -63,6 +63,29 @@ private IAesAlgorithm Create() } } + public static IAesAlgorithm Create(AesMode AesMode, byte[] Key, byte[] IV) + { + switch (AesMode) + { + case AesMode.CBCRandomIV: + return new AesCbcRandIVAlgorithm(Key, IV); + case AesMode.GCM: + //return new AesGcmManagedAlgorithm(Key, IV); +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + return new AesGcmAlgorithm(Key, IV); +#endif + return new AesGcmManagedAlgorithm(Key, IV); + case AesMode.CBCCtrIV: + return new AesCbcCtrIVAlgorithm(Key, IV); + case AesMode.CBCCtrIVHMAC: + return new AesCbcHmacCtrAlgorithm(Key, IV); + case AesMode.None: + return new NoEncyption(); + + default: throw new NotImplementedException(); + } + } + public byte[] Decrypt(byte[] bytes) { var alg = GetAlgorithm(); diff --git a/NetworkLibrary/Components/Crypto/Algorithms/AesGcmAlgorithm.cs b/NetworkLibrary/Components/Crypto/Algorithms/AesGcmAlgorithm.cs index d20cecb..ac67fca 100644 --- a/NetworkLibrary/Components/Crypto/Algorithms/AesGcmAlgorithm.cs +++ b/NetworkLibrary/Components/Crypto/Algorithms/AesGcmAlgorithm.cs @@ -84,28 +84,15 @@ public int EncryptInto(byte[] data, int offset, int count, byte[] output, int ou var non = GenerateNextNonce(out int nonceSize); int encryptedDataLength = nonceSize + tagSize + cipherSize; - Span encryptedData; - unsafe - { - fixed (byte* buffer = &output[outputOffset]) - { - encryptedData = new Span(buffer, encryptedDataLength); - } - } + Span encryptedData = new Span(output, outputOffset, encryptedDataLength); + Buffer.BlockCopy(non, 4, output, outputOffset, nonceSize); var nonce = encryptedData.Slice(0, nonceSize); var tag = encryptedData.Slice(nonceSize + cipherSize, tagSize); var cipherBytes = encryptedData.Slice(nonceSize, cipherSize); - // RandomNumberGenerator.Fill(nonce); - - unsafe - { - fixed (byte* buffer = &data[offset]) - aes.Encrypt(non, new ReadOnlySpan(buffer, count), cipherBytes, tag); - } - + aes.Encrypt(non, new ReadOnlySpan(data,offset, count), cipherBytes, tag); return encryptedDataLength; } public int EncryptInto(byte[] data, int offset, int count, @@ -117,14 +104,9 @@ public int EncryptInto(byte[] data, int offset, int count, int cipherSize = count + count1; int encryptedDataLength = tagSize + nonceSize + cipherSize; - Span encryptedData = new Span(output); - unsafe - { - fixed (byte* buffer = &output[outputOffset]) - { - encryptedData = new Span(buffer, encryptedDataLength); - } - } + Span encryptedData = new Span(output, outputOffset, encryptedDataLength); + + Buffer.BlockCopy(non, 4, output, outputOffset, nonceSize); var nonce = encryptedData.Slice(0, nonceSize); @@ -135,28 +117,16 @@ public int EncryptInto(byte[] data, int offset, int count, Buffer.BlockCopy(data, offset, b, 0, count); Buffer.BlockCopy(data1, offset1, b, count, count1); // Generate secure nonce + aes.Encrypt(non, new ReadOnlySpan(b,0, cipherSize), cipherBytes, tag); - unsafe - { - fixed (byte* buffer = &b[0]) - aes.Encrypt(non, new ReadOnlySpan(buffer, cipherSize), cipherBytes, tag); - - } BufferPool.ReturnBuffer(b); return encryptedDataLength; } } public int DecryptInto(byte[] data, int offset, int count, byte[] output, int outputOffset) { - Span encryptedData; - unsafe - { - fixed (byte* buffer = &data[offset]) - { - encryptedData = new Span(buffer, count); - } - } - + Span encryptedData = new Span(data, offset, count); + int oldOff = offset; var nonce = ParseNonce(data, ref offset, ref count).AsSpan(); int nonceSize = offset - oldOff; @@ -167,13 +137,7 @@ public int DecryptInto(byte[] data, int offset, int count, byte[] output, int ou var tag = encryptedData.Slice(nonceSize + cipherSize, tagSize); var cipherBytes = encryptedData.Slice(nonceSize, cipherSize); - unsafe - { - fixed (byte* buffer = &output[outputOffset]) - { - aes2.Decrypt(nonce, cipherBytes, tag, new Span(buffer, cipherSize)); - } - } + aes2.Decrypt(nonce, cipherBytes, tag, new Span(output,outputOffset, cipherSize)); return cipherSize; } diff --git a/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs b/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs index 382a1c2..88011a7 100644 --- a/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs +++ b/NetworkLibrary/Components/Crypto/KeyDerivation/HKDF.cs @@ -8,6 +8,26 @@ namespace NetworkLibrary.Components.Crypto.KeyDerivation public class HKDFLite { // HKDF implementation (RFC 5869) + public static byte[] DeriveKey(string source, byte[] salt = null, byte[] info = null, int outputLength = 32) + { + if (string.IsNullOrEmpty(source)) + { + source = "Saltmaker"; + } + var bytes = Encoding.UTF8.GetBytes(source); + if (salt == null) + { + salt = Encoding.UTF8.GetBytes("AES-GCM-Salt"); + } + + if (info == null) + { + info = Encoding.UTF8.GetBytes("AES-GCM-Info"); + } + + byte[] prk = HkdfExtract(salt, bytes); + return HkdfExpand(prk, info, outputLength); + } public static byte[] DeriveKey(byte[] sharedSecret, byte[] salt = null, byte[] info = null, int outputLength = 32) { if (salt == null) diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs index 054ad9f..1a783ac 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs @@ -69,7 +69,7 @@ public static IChannel CreateChannel(ChannelInfo info, Socket connectedSocket, b break; case ChannelType.SecureTcp: var symetricKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); - var algo = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey, AesMode.GCM); + var algo = AesManager.Create(AesMode.GCM, symetricKey, HKDFLite.DeriveKey(info.ChannelName,outputLength:16) ); channel = new SecureTcpChannel(algo, info, connectedSocket, isInitiator); break; case ChannelType.Udp: @@ -78,7 +78,7 @@ public static IChannel CreateChannel(ChannelInfo info, Socket connectedSocket, b break; case ChannelType.SecureUdp: var symetricKey2 = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); - var algo2 = new NetworkLibrary.Components.ConcurrentAesAlgorithm(symetricKey2, AesMode.GCM); + var algo2 = AesManager.Create(AesMode.GCM, symetricKey2, HKDFLite.DeriveKey(info.ChannelName,outputLength: 16)); channel = new SecureUdpChannel(connectedSocket, endpoint, algo2, info, isInitiator); break; diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs index f152eb6..fb77aeb 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs @@ -1,5 +1,6 @@ using NetworkLibrary.Components; using NetworkLibrary.Components.Crypto; +using NetworkLibrary.Components.Crypto.Algorithms; using NetworkLibrary.Components.Crypto.DiffieHellman; using NetworkLibrary.Components.Crypto.KeyDerivation; using NetworkLibrary.DistributedP2P.Components; @@ -16,7 +17,7 @@ internal class EphemeralKeyManager DiffieHellman df = new DiffieHellman(); - internal ConcurrentDictionary keyStore = new ConcurrentDictionary(); + internal ConcurrentDictionary keyStore = new ConcurrentDictionary(); byte[] innerBuffer = new byte[1024]; @@ -24,9 +25,12 @@ internal class EphemeralKeyManager internal byte CurrKeyNumber = 0; private int keyRotationPeriod; private bool closed = false; + private byte[] IV = new byte[16]; RandomNumberGenerator rng = RandomNumberGenerator.Create(); Guid timerGuid = Guid.NewGuid(); - public EphemeralKeyManager(ConcurrentAesAlgorithm initialKey, int keyRotationPeriod = 1000) + + int overallCounter = 0; + public EphemeralKeyManager(IAesAlgorithm initialKey, int keyRotationPeriod = 1000) { keyStore[0] = initialKey; this.keyRotationPeriod = keyRotationPeriod; @@ -82,12 +86,14 @@ private void HandleKeyExchangeReq(byte[] buffer, int offset, int count) if (closed) return; currKeyNumber++; + overallCounter++; df = new DiffieHellman(); var sharedSecret = df.CalculateSharedSecret(ByteCopy.ToArray(buffer, offset, count)); var privKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); - var algo = new ConcurrentAesAlgorithm(privKey, AesMode.GCM); - keyStore[currKeyNumber] = algo; + var iv = HKDFLite.DeriveKey("DiffieHellman" + overallCounter.ToString(), outputLength: 16); + + keyStore[currKeyNumber] = AesManager.Create(AesMode.GCM, privKey, iv); byte[] myPublic = df.GetPublicKey(); SendData?.Invoke(MessageFlags.KeyExchangeAck, myPublic, 0, myPublic.Length); @@ -99,11 +105,13 @@ private void HandleKeyExchangeAck(byte[] buffer, int offset, int count) if (closed) return; currKeyNumber++; + overallCounter++; var sharedSecret = df.CalculateSharedSecret(ByteCopy.ToArray(buffer, offset, count)); var privKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); - var algo = new ConcurrentAesAlgorithm(privKey, AesMode.GCM); - keyStore[currKeyNumber] = algo; + var iv = HKDFLite.DeriveKey("DiffieHellman" + overallCounter.ToString(), outputLength: 16); + + keyStore[currKeyNumber] = AesManager.Create(AesMode.GCM, privKey, iv); ; rng.GetBytes(innerBuffer, 0, 32); SendData?.Invoke(MessageFlags.KeyExchangeFin, innerBuffer, 0, 32); @@ -122,7 +130,7 @@ private void HandleFinalize() } - public bool GetAlgorithm(byte keyNum, out ConcurrentAesAlgorithm algo) + public bool GetAlgorithm(byte keyNum, out IAesAlgorithm algo) { return keyStore.TryGetValue(keyNum, out algo); } @@ -138,5 +146,7 @@ internal void SetKeyRotationTime(int timeMs) keyRotationPeriod = timeMs; TimedKeyExchange(); } + + } } diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs index 414b1d1..d7aeff6 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs @@ -6,6 +6,7 @@ using System.Text; using NetworkLibrary.Components; using NetworkLibrary.Components.Crypto; +using NetworkLibrary.Components.Crypto.Algorithms; using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.TCP.AES; @@ -24,7 +25,7 @@ public class SecureTcpChannel : TcpChannel /// public int KeyRotationPeriodMs { get; private set; } = 1000; - public SecureTcpChannel(ConcurrentAesAlgorithm algo,ChannelInfo info, Socket connectedSocket, bool isInitiator) : base(info, connectedSocket) + public SecureTcpChannel(IAesAlgorithm algo,ChannelInfo info, Socket connectedSocket, bool isInitiator) : base(info, connectedSocket) { this.isInitiator = isInitiator; encBuff = BufferPool.RentBuffer(128000); diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs index d56362b..fdfb0e5 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs @@ -1,4 +1,5 @@ using NetworkLibrary.Components; +using NetworkLibrary.Components.Crypto.Algorithms; using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.UDP.Reliable.Components; @@ -16,7 +17,7 @@ public class SecureUdpChannel : UdpChannel public int KeyRotationPeriodMs { get; private set; } = 1000;//every minute - public SecureUdpChannel(Socket udpSocket, IPEndPoint receiveEp, ConcurrentAesAlgorithm algo, ChannelInfo info, bool isInitiator) : base(udpSocket, receiveEp, info) + public SecureUdpChannel(Socket udpSocket, IPEndPoint receiveEp, IAesAlgorithm algo, ChannelInfo info, bool isInitiator) : base(udpSocket, receiveEp, info) { keyManager = new EphemeralKeyManager(algo, isInitiator ? KeyRotationPeriodMs : -1); keyManager.SendData += SendKeyMsg; diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs index 3f8bd02..2aa43e2 100644 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs @@ -88,6 +88,7 @@ protected void FlagAndSend(MessageFlags flag, byte[] buffer, int offset, int cou sendStream.WriteInt(amountWritten + prefixLen + 1); sendStream.Position32 = lastPos; + Interlocked.Exchange(ref msgAvailable, 1); } catch (Exception ex) @@ -116,27 +117,38 @@ protected virtual int WriteData(byte[] buffer, int offset, int count) private void SignalSend() { + bool send = false; lock (sendMtex) { if (Interlocked.CompareExchange(ref sendActive, 1, 0) == 0) { + lock (bufferMutex) { - var flush = Interlocked.Exchange(ref sendStream, flushStream); - Interlocked.Exchange(ref flushStream, flush); - sendStream.Position32 = 0; - } + if (Interlocked.CompareExchange(ref msgAvailable, 0, 1) == 1) + { + var temp = sendStream; + sendStream = flushStream; + flushStream = temp; - sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); - if (!connectedSocket.SendAsync(sendArgs)) - { - ThreadPool.UnsafeQueueUserWorkItem(_ => Sent(null, sendArgs), null); + sendStream.Position32 = 0; + send = true; + } + else + { + Interlocked.Exchange(ref sendActive, 0); + } } } - else + + } + + if (send) + { + sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); + if (!connectedSocket.SendAsync(sendArgs)) { - if (sendStream.Position32 != 0) - Interlocked.Exchange(ref msgAvailable, 1); + ThreadPool.UnsafeQueueUserWorkItem(_ => Sent(null, sendArgs), null); } } } @@ -163,26 +175,26 @@ private void Sent(object sender, SocketAsyncEventArgs e) bool send = false; lock (sendMtex) { - if (Interlocked.CompareExchange(ref msgAvailable, 0, 1) == 1) + lock (bufferMutex) { - lock (bufferMutex) + if (Interlocked.CompareExchange(ref msgAvailable, 0, 1) == 1) { - var flush = Interlocked.Exchange(ref sendStream, flushStream); - Interlocked.Exchange(ref flushStream, flush); - + var temp = sendStream; + sendStream = flushStream; + flushStream = temp; sendStream.Position32 = 0; + + send = true; + } + else + { + Interlocked.Exchange(ref sendActive, 0); } - send = true; - } - else - { - Interlocked.Exchange(ref sendActive, 0); } } if (send) { - sendArgs.SetBuffer(flushStream.GetBuffer(), 0, flushStream.Position32); if (!connectedSocket.SendAsync(sendArgs)) diff --git a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs index accb9b5..e8795d9 100644 --- a/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs +++ b/NetworkLibrary/DistributedP2P/Client/ChannelInfo.cs @@ -28,7 +28,7 @@ public ChannelInfo(ChannelType channelType, string channelName) public ChannelType ChannelType { get; internal set; } - public string ChannelName { get; internal set; } + public string ChannelName { get; internal set; } = ""; internal bool RequiresKeyExchange() { diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs index 77c336a..5cf7d3b 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs @@ -2,6 +2,7 @@ using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.Utils; using System; using System.Collections.Generic; using System.Net; @@ -11,7 +12,13 @@ namespace NetworkLibrary.DistributedP2P.Client.StateManagement { - //Todo 0.0.0.0 means server ip! + class ClientHolepunchData + { + public ChannelInfo ChannelInfo { get; set; } + public EndpointTransferMessage Endpoints { get; set; } + public byte[] DHPublic; + } + internal class ClientTcpHolepunchState2 : ConversationStateBase { private readonly Guid destId; @@ -23,11 +30,10 @@ internal class ClientTcpHolepunchState2 : ConversationStateBase public IPEndPoint SuccesfulEndpoint; private DiffieHellman df = new DiffieHellman(); - private byte[] otherPublicKey; + private byte[] othersPublicKey; public byte[] SharedSecret; public ChannelInfo ChannelInfo; - private int localPort; private Socket listeningSocket; private Socket acceptedSocket; private Socket connectedSocket; @@ -35,13 +41,16 @@ internal class ClientTcpHolepunchState2 : ConversationStateBase private int connected = 0; private int accepted = 0; - IPEndPoint selfEndpoint = new IPEndPoint(IPAddress.Any,0); int swapCnt = 0; bool isListening = false; List localEndpoints = new List(); - EndpointData publicEndpoint; - private EndpointData selfRemoteEp; + + private IPEndPoint selfRemoteEp; + private IPEndPoint selfLocalEp = new IPEndPoint(IPAddress.Any, 0); + private EndpointData publicEndpointToConnect; + + private bool IsEstablished => Interlocked.CompareExchange(ref established, 0, 0) == 1; public ClientTcpHolepunchState2(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint,EndpointData discoveryServerEp, ChannelInfo info) : base(stateId, 10000) @@ -59,31 +68,26 @@ public async void Start() isInitiator = true; Log(StateId.ToString()); - selfRemoteEp = await BindPort(); - if (selfRemoteEp == null) - return; - - localPort = selfEndpoint.Port; + await BindPort(); - Log("Bound on port " + localPort); + Log("Local port " + selfLocalEp.Port); Log("Remote port " + selfRemoteEp.Port); var msg = CreateEnvelope(); - msg.Header = InternalConstants.RequestSimultaneousHolepunchTcp; - msg.KeyValuePairs = new Dictionary(); - // msg.KeyValuePairs["PortLocal"] = selfEndpoint.ToString(); - msg.KeyValuePairs["Port"] = selfRemoteEp.Port.ToString(); - msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); - msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; - - if (ChannelInfo.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); - msg.To = destId; + msg.Header = InternalConstants.RequestSimultaneousHolepunchTcp; + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream,GetHpData()); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } + + public override void HandleMessage(MessageEnvelope message) { switch (message.Header) @@ -113,59 +117,47 @@ public override void HandleMessage(MessageEnvelope message) private async void HandleRemoteHpRequest(MessageEnvelope message) { Log(StateId.ToString()); + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); - ChannelInfo = new ChannelInfo(); - ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); - ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; - - selfRemoteEp = await BindPort(); - if (selfRemoteEp == null) - return; + ChannelInfo = hpData.ChannelInfo; + await BindPort(); - localPort = StartTcpListener(); - Log("listening on port " + localPort); + Log("Local port " + selfLocalEp.Port); + Log("Remote port " + selfRemoteEp.Port); + StartTcpListener(); + Log("listening"); var msg = CreateEnvelope(); + msg.To = destId; msg.Header = InternalConstants.AckRequestHolepunchTcp; - msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Port"] = selfRemoteEp.Port.ToString(); - - if (ChannelInfo.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + + var hpd = GetHpData(); + hpd.ChannelInfo = null; - msg.To = destId; + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, hpd); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } private void StartHolepunchRoutine(MessageEnvelope message) { if (IsCompleted()) return; + int offs = message.PayloadOffset; + + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); - var epMsg = KnownTypeSerializer.DeserializeEndpointTransferMessage(message.Payload, message.PayloadOffset); + var epMsg = hpData.Endpoints; + othersPublicKey = hpData.DHPublic; localEndpoints = epMsg.LocalEndpoints; bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); - publicEndpoint = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; - - if (ChannelInfo.RequiresKeyExchange()) - otherPublicKey = Convert.FromBase64String(message.KeyValuePairs["DH"]); - - ////213.243.208.162 - //foreach (var ep in localEndpoints) - //{ - // ep.Ip[0] = 213; - // ep.Ip[1] = 243; - // ep.Ip[2] = 208; - // ep.Ip[3] = 162; - - // publicEndpoint.Ip[0] = 213; - // publicEndpoint.Ip[1] = 243; - // publicEndpoint.Ip[2] = 208; - // publicEndpoint.Ip[3] = 162; - //} + publicEndpointToConnect = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; if (!isListening) { @@ -178,22 +170,21 @@ private void TryPunch() { try { - // if there are local endpoints to test if (localEndpoints.Count > 0) { - //foreach (EndpointData localEp in localEndpoints) - //{ - // if (TryConnect(localEp, 500)) - // return; + foreach (EndpointData localEp in localEndpoints) + { + if (TryConnect(localEp, 600)) + return; - // if (IsCompleted()) return; + if (IsCompleted()) return; - //} + } } for (int i = 0; i < 1; i++) { - if (TryConnect(publicEndpoint, (2000))) + if (TryConnect(publicEndpointToConnect, (2000))) return; if (IsCompleted()) return; @@ -270,7 +261,7 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) try { Log("Connecting to " + endpoint.ToIpEndpoint().ToString()); - connectSocket.Bind(selfEndpoint); + connectSocket.Bind(selfLocalEp); var connectTask = connectSocket.ConnectAsync(endpoint.ToIpEndpoint()); var timeoutTask = Task.Delay(timeoutMs); @@ -302,19 +293,20 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) } } - private async Task BindPort() + private async Task BindPort() { var clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - clientSocket.Bind(selfEndpoint); + clientSocket.Bind(selfLocalEp); + selfLocalEp = (IPEndPoint)clientSocket.LocalEndPoint; var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket,discoveryServerEp.ToIpEndpoint(),5000); if (remoteEp == null) { Log("Failed to get public endpoint"); - return null; + remoteEp = new EndpointData("0.0.0.0",selfLocalEp.Port); } - selfEndpoint = (IPEndPoint)clientSocket.LocalEndPoint; + selfRemoteEp = remoteEp.ToIpEndpoint(); try { @@ -323,9 +315,7 @@ private async Task BindPort() clientSocket = null; } catch { } - - return remoteEp; - + } private int StartTcpListener() @@ -334,9 +324,9 @@ private int StartTcpListener() listeningSocket.SendBufferSize = 12800000; listeningSocket.ReceiveBufferSize = 12800000; - listeningSocket.Bind(selfEndpoint); + listeningSocket.Bind(selfLocalEp); - selfEndpoint = (IPEndPoint)listeningSocket.LocalEndPoint; + selfLocalEp = (IPEndPoint)listeningSocket.LocalEndPoint; Listen(); @@ -425,12 +415,11 @@ private void HandleFailure() private void HandleRemoteSucces(MessageEnvelope message) { - Thread.Sleep(100); - string use = message.KeyValuePairs["Use"]; + if (ChannelInfo.RequiresKeyExchange()) - SharedSecret = df.CalculateSharedSecret(otherPublicKey); + SharedSecret = df.CalculateSharedSecret(othersPublicKey); - if (use == "Connected") + if (connectedSocket!=null) { Socket = connectedSocket; } @@ -438,6 +427,7 @@ private void HandleRemoteSucces(MessageEnvelope message) { Socket = acceptedSocket; } + if(Socket == null) { Log("Failed to get socket"); @@ -493,7 +483,32 @@ private void Log(string log) Console.WriteLine(prefix + log); } + private ClientHolepunchData GetHpData() + { + ClientHolepunchData hpd = new ClientHolepunchData(); + hpd.ChannelInfo = ChannelInfo; + + var epm = new EndpointTransferMessage(); + var pub = new EndpointData(selfRemoteEp); + epm.IpRemote = pub.Ip; + epm.PortRemote = pub.Port; + + var localIps = IPHelper.GetLocalIPAddresses4(); + int localPort = selfLocalEp.Port; + foreach (var ip in localIps) + { + epm.LocalEndpoints.Add(new EndpointData(ip, localPort)); + } + + hpd.Endpoints = epm; + + if (ChannelInfo.RequiresKeyExchange()) + { + hpd.DHPublic = df.GetPublicKey(); + } + return hpd; + } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 195ec45..43936c6 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -32,6 +32,11 @@ internal class ClientUdpHolepunchState : ConversationStateBase private byte[] otherPublicKey; public byte[] SharedSecret; public ChannelInfo ChannelInfo; + + private IPEndPoint selfRemoteEp; + private IPEndPoint selfLocalEp; + + public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerendPoint, ChannelInfo info) : base(stateId, 20000) { this.destId = destId; @@ -47,22 +52,19 @@ public async void Start() isInitiator = true; Log(StateId.ToString()); - EndpointData data = await StartUdpSocket(); - if (data == null) return; + await StartUdpSocket(); var msg = CreateEnvelope(); msg.Header = InternalConstants.RequestHolepunchUdp; - msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Port"] = data.Port.ToString(); - msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); - msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; - - if (ChannelInfo.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); - msg.To = destId; + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, GetHpData()); + + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } @@ -102,35 +104,40 @@ private async void HandleRemoteHpRequest(MessageEnvelope message) { Log(StateId.ToString()); - ChannelInfo = new ChannelInfo(); - ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); - ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + + ChannelInfo = hpData.ChannelInfo; - EndpointData data = await StartUdpSocket(); - if(data == null) return; + await StartUdpSocket(); var msg = CreateEnvelope(); msg.Header = InternalConstants.AckRequestHolepunchUdp; - msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Port"] = data.Port.ToString(); + msg.To = destId; - if (ChannelInfo.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + var hpd = GetHpData(); + hpd.ChannelInfo = null; - msg.To = destId; + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, hpd); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } private void StartHolepunchRoutine(MessageEnvelope message) { - var epMsg = KnownTypeSerializer.DeserializeEndpointTransferMessage(message.Payload, message.PayloadOffset); - var time = double.Parse(message.KeyValuePairs["Time"]); + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); - if (ChannelInfo.RequiresKeyExchange()) - otherPublicKey = Convert.FromBase64String(message.KeyValuePairs["DH"]); + var epMsg = hpData.Endpoints; + otherPublicKey = hpData.DHPublic; + + var time = double.Parse(message.KeyValuePairs["Time"]); - // if there are local endpoints to test + if (epMsg.LocalEndpoints.Count > 0) { var now0 = connection.GetTime(); @@ -181,6 +188,7 @@ private void TryPunch(EndpointData ep, MessageFlags flag) } private object m = new object(); PooledMemoryStream stream = new PooledMemoryStream(); + private void TryPunch(IPEndPoint ep, MessageFlags flag) { lock (m) @@ -198,7 +206,7 @@ private void TryPunch(IPEndPoint ep, MessageFlags flag) } - private async Task StartUdpSocket() + private async Task StartUdpSocket() { Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); @@ -208,15 +216,19 @@ private async Task StartUdpSocket() Socket.Bind(new IPEndPoint(IPAddress.Any, 0)); - EndpointData data = await EndpointDiscoveryClient.GetUdpPublicEndpoint(Socket, discoveryServerendPoint.ToIpEndpoint(),5000); - if(data == null) + selfLocalEp = (IPEndPoint)Socket.LocalEndPoint; + + var remoteEp = await EndpointDiscoveryClient.GetUdpPublicEndpoint(Socket, discoveryServerendPoint.ToIpEndpoint(), 5000); + if (remoteEp == null) { - OnError("Failed to retrieve public endpoint"); - return null; + Log("Failed to get public endpoint"); + remoteEp = new EndpointData("0.0.0.0", selfLocalEp.Port); } + selfRemoteEp = remoteEp.ToIpEndpoint(); + Receive(); - return data; + } private async void Receive() { @@ -351,12 +363,39 @@ protected override void Completed(bool succes) private void Log(string log) { - return; + //return; string prefix = isInitiator ? "A: " : "B: "; Console.WriteLine(prefix + log); } + private ClientHolepunchData GetHpData() + { + ClientHolepunchData hpd = new ClientHolepunchData(); + hpd.ChannelInfo = ChannelInfo; + + var epm = new EndpointTransferMessage(); + var pub = new EndpointData(selfRemoteEp); + epm.IpRemote = pub.Ip; + epm.PortRemote = pub.Port; + + var localIps = IPHelper.GetLocalIPAddresses4(); + int localPort = selfLocalEp.Port; + foreach (var ip in localIps) + { + epm.LocalEndpoints.Add(new EndpointData(ip, localPort)); + } + + hpd.Endpoints = epm; + + + if (ChannelInfo.RequiresKeyExchange()) + { + hpd.DHPublic = df.GetPublicKey(); + } + return hpd; + } + } diff --git a/NetworkLibrary/DistributedP2P/Components/IPHelper.cs b/NetworkLibrary/DistributedP2P/Components/IPHelper.cs index 16cd4a3..75d465d 100644 --- a/NetworkLibrary/DistributedP2P/Components/IPHelper.cs +++ b/NetworkLibrary/DistributedP2P/Components/IPHelper.cs @@ -1,11 +1,10 @@ using NetworkLibrary.DistributedP2P.Server; +using NetworkLibrary.P2P.Components.HolePunch; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.NetworkInformation; -using System.Text; -using NetworkLibrary.P2P.Components.HolePunch; namespace NetworkLibrary.DistributedP2P.Components { @@ -43,28 +42,8 @@ public static bool IsPrivateIPAddress(IPEndPoint endpont) IPAddress address = endpont.Address; byte[] bytes = address.GetAddressBytes(); + return IsPrivateIPAddress(bytes); - // 10.0.0.0 - 10.255.255.255 (10/8 prefix) - if (bytes[0] == 10) - return true; - - // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) - if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) - return true; - - // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) - if (bytes[0] == 192 && bytes[1] == 168) - return true; - - // 169.254.0.0 - 169.254.255.255 (169.254/16 prefix, link-local) - if (bytes[0] == 169 && bytes[1] == 254) - return true; - - // 127.0.0.0 - 127.255.255.255 (127/8 prefix, loopback) - if (bytes[0] == 127) - return true; - - return false; } // based on reserved private networks by RFC 1918 @@ -78,7 +57,11 @@ public static bool IsPrivateIPAddress(string ipAddress) byte[] bytes = address.GetAddressBytes(); + return IsPrivateIPAddress(bytes); + } + public static bool IsPrivateIPAddress(byte[] bytes) + { // 10.0.0.0 - 10.255.255.255 (10/8 prefix) if (bytes[0] == 10) return true; @@ -180,7 +163,20 @@ public static bool IsZero(byte[] ipRemote) } } - public static void ObtainIpEndpoints(int fromPort, int toPort, ServerSession sesFrom, ServerSession sesTo, out EndpointTransferMessage FromNeedsToKnow, out EndpointTransferMessage ToNeedsToKnow) + public static bool AreEqual(byte[] ip1, byte[] ip2) + { + + unsafe + { + fixed (byte* ptr1 = ip1) + fixed (byte* ptr2 = ip2) + { + return *((int*)ptr1) == *((int*)ptr2); + } + } + } + + public static void ObtainIpEndpoints(int fromPort, int toPort, ServerSession sesFrom, ServerSession sesTo, out EndpointTransferMessage FromNeedsToKnow, out EndpointTransferMessage ToNeedsToKnow) { FromNeedsToKnow = new EndpointTransferMessage(); ToNeedsToKnow = new EndpointTransferMessage(); @@ -242,5 +238,103 @@ public static void ObtainIpEndpoints(int fromPort, int toPort, ServerSession se } } + + public static void ObtainIpEndpoints(EndpointTransferMessage fromAdresses, EndpointTransferMessage toAdresses, + out EndpointTransferMessage FromNeedsToKnow, out EndpointTransferMessage ToNeedsToKnow) + { + FromNeedsToKnow = new EndpointTransferMessage(); + ToNeedsToKnow = new EndpointTransferMessage(); + + var fromLocalIps = GetAdressesAsStrings(fromAdresses.LocalEndpoints); + var toLocalIps = GetAdressesAsStrings(toAdresses.LocalEndpoints); + int toPort = toAdresses.LocalEndpoints.First().Port; + int fromPort = fromAdresses.LocalEndpoints.First().Port; + + + IPHelper.ExtractLocalIpsWithMatchingSubnet(fromLocalIps, + toLocalIps, + out List Locals_From_NeedsToKnow, + out List Locals_To_NeedsToKnow); + + + //They need to know locals when both peers have public IPs same(coming from same NAT) + // or peers and server inside LAN, means both peers have private IPs on public ip. + + if (AreEqual(fromAdresses.IpRemote, toAdresses.IpRemote) || + (IPHelper.IsPrivateIPAddress(fromAdresses.IpRemote) && IPHelper.IsPrivateIPAddress(toAdresses.IpRemote))) + { + foreach (var local in Locals_From_NeedsToKnow) + { + EndpointData data = new EndpointData(local, toPort); + FromNeedsToKnow.LocalEndpoints.Add(data); + } + + foreach (var local in Locals_To_NeedsToKnow) + { + EndpointData data = new EndpointData(local, fromPort); + ToNeedsToKnow.LocalEndpoints.Add(data); + } + } + + + // "From" is same network as the server. + if (IPHelper.IsPrivateIPAddress(fromAdresses.IpRemote)) + { + // "To" needs to get server adress to connect + // 0.0.0.0:0 means serverIp + ToNeedsToKnow.IpRemote = new byte[4]; + ToNeedsToKnow.PortRemote = fromPort; + + } + else + { + // send just the publicIp of "From" to "To" + ToNeedsToKnow.IpRemote = fromAdresses.IpRemote; + ToNeedsToKnow.PortRemote = fromPort; + } + + // "To" is same network as the server. + if (IPHelper.IsPrivateIPAddress(toAdresses.IpRemote)) + { + //"From" needs to get Server adress + FromNeedsToKnow.IpRemote = new byte[4]; + FromNeedsToKnow.PortRemote = toPort; + } + else + { + // send just the public to "From" + FromNeedsToKnow.IpRemote = toAdresses.IpRemote; + FromNeedsToKnow.PortRemote = toPort; + } + + } + + private static List GetAdressesAsStrings(List localEndpoints) + { + List result = new List(); + foreach (var local in localEndpoints) + { + result.Add(Byte2Sting(local.Ip)); + } + return result; + } + + public static string Byte2Sting(byte[] ip) + { + return $"{ip[0]}.{ip[1]}.{ip[2]}.{ip[3]}"; + } + + public static byte[] String2Byte(string ip) + { + + var parts = ip.Split('.'); + + return new byte[] { + byte.Parse(parts[0]), + byte.Parse(parts[1]), + byte.Parse(parts[2]), + byte.Parse(parts[3]) + }; + } } } diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index f8ba410..554162f 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -55,10 +55,10 @@ public class ServerParameters SessionManager sessionManager; Components.StateManager stateManager = new Components.StateManager(); - PipeManager piper; + RelayService piper; Stopwatch serverClock = new Stopwatch(); - PipeManager pipeManager; + RelayService pipeManager; private byte[] serverKey = new byte[16]; @@ -85,7 +85,7 @@ public void StartServer() var random = RandomNumberGenerator.Create(); var key = new byte[32]; random.GetNonZeroBytes(key); - pipeManager = new PipeManager(TcpPort, UdpPort, key); + pipeManager = new RelayService(TcpPort, UdpPort, key); sslServer.OnClientRequestedConnection += ValidateSslConnection; sslServer.OnClientAccepted += SslClientAccepted; diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs index 733ffe8..c66c8b0 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs @@ -20,7 +20,7 @@ class PipeData internal class ServerPipeState : ConversationStateBase { - private PipeManager piper; + private RelayService piper; private Guid from, to; private int ackCount = 0; private readonly IDistributedConnection connection; @@ -30,7 +30,7 @@ internal class ServerPipeState : ConversationStateBase private string destinationDhPublicKey; private string requesterDhPublicKey; private ChannelInfo chInfo = new ChannelInfo(); - public ServerPipeState(Guid stateId, IDistributedConnection connection, PipeManager piper) : base(stateId, 20000) + public ServerPipeState(Guid stateId, IDistributedConnection connection, RelayService piper) : base(stateId, 20000) { this.connection = connection; this.piper = piper; diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs index 881ab19..5c62a2f 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs @@ -3,6 +3,7 @@ using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.Utils; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Threading; @@ -15,10 +16,12 @@ internal class ServerTcpHolepunchState2 : ConversationStateBase private readonly SessionManager sessionManager; Guid From; Guid To; - int fromPort; - string fromPublicKey; - int toPort; - string toPublicKey; + EndpointTransferMessage fromAdresses; + byte[] fromPublicKey; + + EndpointTransferMessage toAddresses; + byte[] toPublicKey; + ChannelInfo info; private int succesCount; @@ -66,26 +69,34 @@ private void RelaySwapMsg(MessageEnvelope message) // obtain port from destination endpoint private void HandleHolepunchRequest(MessageEnvelope message) { - info = new ChannelInfo(); - info.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); - info.ChannelName = message.KeyValuePairs["Name"]; + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + + info = hpData.ChannelInfo; + fromPublicKey = hpData.DHPublic; + fromAdresses = hpData.Endpoints; From = message.From; To = message.To; - fromPort = int.Parse(message.KeyValuePairs["Port"]); - if (info.RequiresKeyExchange()) - fromPublicKey = message.KeyValuePairs["DH"]; + + hpData.Endpoints = null; + hpData.DHPublic = null; + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + + KnownTypeSerializer.SerializeHolepunchData(stream, hpData); + message.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(message); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } private void HandleHolepunchRequestAck(MessageEnvelope message) { - toPort = int.Parse(message.KeyValuePairs["Port"]); - if (info.RequiresKeyExchange()) - toPublicKey = message.KeyValuePairs["DH"]; - - - + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + toPublicKey = hpData.DHPublic; + toAddresses = hpData.Endpoints; var msg = CreateEnvelope(); msg.Header = InternalConstants.StartHP; @@ -94,33 +105,40 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) sessionManager.GetSessionData(From, out ServerSession sesFrom); sessionManager.GetSessionData(To, out ServerSession sesTo); + + if (sesFrom != null && sesTo != null) { - IPHelper.ObtainIpEndpoints(fromPort, toPort, sesFrom, sesTo, out var FromNeedsToKnow, out var ToNeedsToKnow); + if (IPHelper.IsZero(toAddresses.IpRemote)) + toAddresses.IpRemote = sesTo.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + if (IPHelper.IsZero(fromAdresses.IpRemote)) + fromAdresses.IpRemote = sesFrom.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + + IPHelper.ObtainIpEndpoints(fromAdresses, + toAddresses, + out var FromNeedsToKnow, + out var ToNeedsToKnow); - // coordination signal - double startTime = connection.GetTime(); - startTime += 1000 * (1 + Math.Max(FromNeedsToKnow.LocalEndpoints.Count, ToNeedsToKnow.LocalEndpoints.Count)); - msg.KeyValuePairs["Time"] = startTime.ToString(CultureInfo.InvariantCulture); var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.Position32 = 0; - KnownTypeSerializer.SerializeEndpointTransferMessage(stream, FromNeedsToKnow); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); msg.To = From; - if (info.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = toPublicKey; + hpData.Endpoints = FromNeedsToKnow; + hpData.DHPublic = toPublicKey; + KnownTypeSerializer.SerializeHolepunchData(stream, hpData); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); stream.Position32 = 0; - KnownTypeSerializer.SerializeEndpointTransferMessage(stream, ToNeedsToKnow); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); msg.To = To; - if (info.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = fromPublicKey; + hpData.Endpoints = ToNeedsToKnow; + hpData.DHPublic = fromPublicKey; + KnownTypeSerializer.SerializeHolepunchData(stream, hpData); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } else { @@ -146,62 +164,18 @@ private void HandleFailure(MessageEnvelope message) } int succCounter = 0; + private void HandleSucces(MessageEnvelope message) { - if (Interlocked.Increment(ref succCounter) == 1) + if (Interlocked.Increment(ref succCounter) == 2) { - var sts = message.KeyValuePairs["Status"]; - var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchSuccesAck; - msg.KeyValuePairs = new Dictionary(); - msg.SetPayload(message.Payload, message.PayloadOffset, message.PayloadCount); - - if (sts == "Accepted") - { - - if (message.From == From) - { - msg.KeyValuePairs["Use"] = "Connected"; - connection.SendAsyncMessage(To, msg); - - msg.KeyValuePairs["Use"] = "Accepted"; - connection.SendAsyncMessage(From, msg); - } - else if (message.From == To) - { - msg.KeyValuePairs["Use"] = "Connected"; - connection.SendAsyncMessage(From, msg); - - msg.KeyValuePairs["Use"] = "Accepted"; - connection.SendAsyncMessage(To, msg); - } - - } - else // connected - { - - if (message.From == From) - { - msg.KeyValuePairs["Use"] = "Accepted"; - connection.SendAsyncMessage(To, msg); - - msg.KeyValuePairs["Use"] = "Connected"; - connection.SendAsyncMessage(From, msg); - } - else if (message.From == To) - { - msg.KeyValuePairs["Use"] = "Accepted"; - connection.SendAsyncMessage(From, msg); - - msg.KeyValuePairs["Use"] = "Connected"; - connection.SendAsyncMessage(To, msg); - } - } + connection.SendAsyncMessage(To, msg); + connection.SendAsyncMessage(From, msg); Completed(true); } - } } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs index 20aed95..c5876bd 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs @@ -14,12 +14,15 @@ internal class ServerUdpHolepunchState : ConversationStateBase { private readonly IDistributedConnection connection; private readonly SessionManager sessionManager; + Guid From; Guid To; - int fromPort; - string fromPublicKey; - int toPort; - string toPublicKey; + EndpointTransferMessage fromAdresses; + byte[] fromPublicKey; + + EndpointTransferMessage toAddresses; + byte[] toPublicKey; + ChannelInfo info; private int succesCount; @@ -51,59 +54,77 @@ public override void HandleMessage(MessageEnvelope message) // obtain port from destination endpoint private void HandleHolepunchRequest(MessageEnvelope message) { - info = new ChannelInfo(); - info.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); - info.ChannelName = message.KeyValuePairs["Name"]; + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + + info = hpData.ChannelInfo; + fromPublicKey = hpData.DHPublic; + fromAdresses = hpData.Endpoints; From = message.From; To = message.To; - fromPort = int.Parse(message.KeyValuePairs["Port"]); - if(info.RequiresKeyExchange()) - fromPublicKey = message.KeyValuePairs["DH"]; + + hpData.Endpoints = null; + hpData.DHPublic = null; + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + + KnownTypeSerializer.SerializeHolepunchData(stream, hpData); + message.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(message); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } private void HandleHolepunchRequestAck(MessageEnvelope message) { - toPort = int.Parse(message.KeyValuePairs["Port"]); - if (info.RequiresKeyExchange()) - toPublicKey = message.KeyValuePairs["DH"]; - - //signal - double startTime = connection.GetTime(); - startTime += 500; + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + toPublicKey = hpData.DHPublic; + toAddresses = hpData.Endpoints; var msg = CreateEnvelope(); msg.Header = InternalConstants.StartHP; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Time"] = startTime.ToString(CultureInfo.InvariantCulture); + msg.KeyValuePairs["Time"] = (connection.GetTime() + 500).ToString(); sessionManager.GetSessionData(From, out ServerSession sesFrom); sessionManager.GetSessionData(To, out ServerSession sesTo); + + if (sesFrom != null && sesTo != null) { - IPHelper.ObtainIpEndpoints(fromPort, toPort, sesFrom, sesTo, out var FromNeedsToKnow, out var ToNeedsToKnow); + if (IPHelper.IsZero(toAddresses.IpRemote)) + toAddresses.IpRemote = sesTo.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + if (IPHelper.IsZero(fromAdresses.IpRemote)) + fromAdresses.IpRemote = sesFrom.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + + IPHelper.ObtainIpEndpoints(fromAdresses, + toAddresses, + out var FromNeedsToKnow, + out var ToNeedsToKnow); + var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.Position32 = 0; - KnownTypeSerializer.SerializeEndpointTransferMessage(stream, FromNeedsToKnow); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); msg.To = From; - if (info.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = toPublicKey; + hpData.Endpoints = FromNeedsToKnow; + hpData.DHPublic = toPublicKey; + KnownTypeSerializer.SerializeHolepunchData(stream, hpData); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); stream.Position32 = 0; - KnownTypeSerializer.SerializeEndpointTransferMessage(stream, ToNeedsToKnow); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); msg.To = To; - if (info.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = fromPublicKey; + hpData.Endpoints = ToNeedsToKnow; + hpData.DHPublic = fromPublicKey; + KnownTypeSerializer.SerializeHolepunchData(stream, hpData); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } else { diff --git a/NetworkLibrary/DistributedP2P/SimpleRelay/PeerRoomState.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/PeerRoomState.cs new file mode 100644 index 0000000..05eadb5 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/PeerRoomState.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.SimpleRelay +{ + internal class PeerRoomState + { + public Guid RoomId { get; internal set; } + public Guid ExpectedClient { get; internal set; } + public Guid EphemeralId { get; internal set; } + public IPEndPoint AssociatedEndpoint { get; internal set; } + public bool isTcp; + + internal bool Verify(PipeToken token, Guid ephemeralId) + { + if(ExpectedClient.Equals(token.Token)) + { + isTcp = true; + EphemeralId = ephemeralId; + return true; + } + return false; + } + + internal bool Verify(PipeToken token, IPEndPoint clientEp) + { + if (ExpectedClient.Equals(token.Token)) + { + AssociatedEndpoint = clientEp; + return true; + } + return false; + } + } +} diff --git a/NetworkLibrary/DistributedP2P/SimpleRelay/PipeManager.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs similarity index 62% rename from NetworkLibrary/DistributedP2P/SimpleRelay/PipeManager.cs rename to NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs index 47b1202..7d906aa 100644 --- a/NetworkLibrary/DistributedP2P/SimpleRelay/PipeManager.cs +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs @@ -1,22 +1,12 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Net; -using System.Text; -using NetworkLibrary.Components; -using NetworkLibrary.Components.Crypto.DigitalSignature; -using NetworkLibrary.MessageProtocol; +using NetworkLibrary.Components.Crypto.DigitalSignature; +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Server.StateManagement; using NetworkLibrary.TCP.Base; using NetworkLibrary.UDP; -using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.Utils; -using System.IO; -using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.DistributedP2P.Server.StateManagement; -using System.Threading.Tasks; -using System.Threading; -using System.Security.Cryptography; -using System.Net.Sockets; +using System; +using System.Collections.Concurrent; +using System.Net; namespace NetworkLibrary.DistributedP2P.SimpleRelay { @@ -58,7 +48,7 @@ internal int StoreTokenFragment(byte[] tokenFragment, int offset, int count) } } - internal class PipeManager:IDisposable + internal class RelayService : IDisposable { int tokenLifetimeMs = 200000; @@ -71,13 +61,21 @@ internal class PipeManager:IDisposable private ConcurrentDictionary> activeTcpPipeStates = new ConcurrentDictionary>(); private ConcurrentDictionary> activeUdpPipeStates = new ConcurrentDictionary>(); + // Guid will be the id of coming peer, determined by relay. + //Room state will hold room id. + private ConcurrentDictionary activeRoomStates = new ConcurrentDictionary(); + private ConcurrentDictionary activeRooms = new ConcurrentDictionary(); + + private ConcurrentDictionary roomMapTcp = new ConcurrentDictionary(); + private ConcurrentDictionary roomMapUdp = new ConcurrentDictionary(); + private readonly ConcurrentDictionary tokenStorage = new ConcurrentDictionary(); byte[] cryptoKey; PrivateKeySign signer; readonly object tokenMtex = new object(); - public PipeManager(int TcpPort, int UdpPort, byte[] pipeKey) + public RelayService(int TcpPort, int UdpPort, byte[] pipeKey) { TcpServer = new AsyncTcpServer(TcpPort); TcpServer.GatherConfig = ScatterGatherConfig.UseBuffer; @@ -149,6 +147,11 @@ private bool RouteTcp(Guid guid, byte[] bytes, int offset, int count) TcpServer.SendBytesToClient(to, bytes, offset, count); return true; } + else if (roomMapTcp.TryGetValue(guid, out var room)) + { + room.HandleMessage(guid, bytes, offset, count); + return true; + } return false; } @@ -159,17 +162,22 @@ private bool RouteUdp(IPEndPoint endpoint, byte[] bytes, int offset, int count) UdpServer.SendBytesToClient(to, bytes, offset, count); return true; } + else if (roomMapUdp.TryGetValue(endpoint, out var room)) + { + room.HandleMessage(endpoint, bytes, offset, count); + return true; + } return false; } //tcp - private void ManagePipeToken(Guid guid, byte[] bytes, int offset, int count) + private void ManagePipeToken(Guid ephemeralId, byte[] bytes, int offset, int count) { // state object etc lock (tokenMtex) { - tokenStorage.TryGetValue(guid, out var storage); + tokenStorage.TryGetValue(ephemeralId, out var storage); int result = storage.StoreTokenFragment(bytes, offset, count); @@ -178,7 +186,7 @@ private void ManagePipeToken(Guid guid, byte[] bytes, int offset, int count) if (result == -1)// nonsense { - RemoveTcpClient(guid); + RemoveTcpClient(ephemeralId); return; } @@ -190,7 +198,7 @@ private void ManagePipeToken(Guid guid, byte[] bytes, int offset, int count) { if (VerifyToken(storage.Token, token.Expiration)) { - pipeState.RegisterClient(guid); + pipeState.RegisterClient(ephemeralId); if (pipeState.IsComplete()) { @@ -198,21 +206,40 @@ private void ManagePipeToken(Guid guid, byte[] bytes, int offset, int count) TcpPipeCreated(pipeState.Clients[0], pipeState.Clients[1]); } - TcpServer.SendBytesToClient(guid, new byte[1] { 0x01 }); + TcpServer.SendBytesToClient(ephemeralId, new byte[1] { 0x01 }); } else { - RemoveTcpClient(guid); + RemoveTcpClient(ephemeralId); } } + else if (activeRoomStates.TryRemove(token.Token, out var roomState))//token is peerId + { + if (VerifyToken(storage.Token, token.Expiration)) + { + if (roomState.Verify(token,ephemeralId)) + { + if (activeRooms.TryGetValue(roomState.RoomId, out Room room)) + { + room.Add(token.Token, roomState); + TcpServer.SendBytesToClient(ephemeralId, new byte[1] { 0x01 }); + } + } + } + else + { + RemoveTcpClient(ephemeralId); + } + + } else { - RemoveTcpClient(guid); + RemoveTcpClient(ephemeralId); } } else { - RemoveTcpClient(guid); + RemoveTcpClient(ephemeralId); } } } @@ -252,6 +279,26 @@ private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int UdpServer.RemoveClient(clientEp); } } + else if (activeRoomStates.TryRemove(token.Token, out var roomState))//token is peerId + { + if (VerifyToken(tokenBytes, token.Expiration)) + { + if (roomState.Verify(token,clientEp)) + { + if (activeRooms.TryGetValue(roomState.RoomId, out Room room)) + { + room.Add(token.Token, roomState); + UdpServer.SendBytesToClient(clientEp, new byte[1] { 0x01 }, 0, 1); + + } + } + } + else + { + UdpServer.RemoveClient(clientEp); + } + } + else { UdpServer.RemoveClient(clientEp); @@ -299,7 +346,7 @@ private void RemoveTcpClient(Guid guid) TcpServer.CloseSession(guid); } - internal void TcpPipeCreated(Guid from, Guid to) + private void TcpPipeCreated(Guid from, Guid to) { pipeMapTcp.TryAdd(from, to); pipeMapTcp.TryAdd(to, from); @@ -308,13 +355,13 @@ internal void TcpPipeCreated(Guid from, Guid to) CancelTimeout(to); } - internal void UdpPipeCreated(IPEndPoint from, IPEndPoint to) + private void UdpPipeCreated(IPEndPoint from, IPEndPoint to) { pipeMapUdp.TryAdd(from, to); pipeMapUdp.TryAdd(to, from); } - internal void HandleUdpPipeDisconnect(IPEndPoint from) + private void HandleUdpPipeDisconnect(IPEndPoint from) { if (pipeMapUdp.TryRemove(from, out var to)) { @@ -322,7 +369,7 @@ internal void HandleUdpPipeDisconnect(IPEndPoint from) } } - internal void HandleTcpPipeDisconnect(Guid from) + private void HandleTcpPipeDisconnect(Guid from) { if (pipeMapTcp.TryRemove(from, out Guid to)) { @@ -333,13 +380,25 @@ internal void HandleTcpPipeDisconnect(Guid from) tokenStorage.TryRemove(from, out _); } - internal byte[] GetPipeToken(bool tcp) + + private void RegisterTcpToken(PipeToken pipeData) { - byte[] data = new byte[PipeData.TokenLength]; - int offset = 0; - PipeToken pipeData = new PipeToken(); + activeTcpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); + TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeTcpPipeStates.TryRemove(pipeData.Token, out _)); + } - pipeData.Token = Guid.NewGuid(); + private void RegisterUdpToken(PipeToken pipeData) + { + activeUdpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); + TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeUdpPipeStates.TryRemove(pipeData.Token, out _)); + + } + private void Createtoken(Guid tokenId, out byte[] data, out PipeToken pipeData) + { + data = new byte[PipeData.TokenLength]; + int offset = 0; + pipeData = new PipeToken(); + pipeData.Token = tokenId; pipeData.Expiration = DateTime.UtcNow.AddMilliseconds(tokenLifetimeMs); PrimitiveEncoder.WriteGuid(data, ref offset, pipeData.Token);//16 @@ -349,6 +408,17 @@ internal byte[] GetPipeToken(bool tcp) byte[] signature = signer.Sign(data, 0, 24); Buffer.BlockCopy(signature, 0, data, offset, 32);//32 + } + + + + + // for direct p2p + public byte[] GetPipeToken(bool tcp) + { + byte[] data; + PipeToken pipeData; + Createtoken(Guid.NewGuid(), out data, out pipeData); if (tcp) RegisterTcpToken(pipeData); @@ -358,19 +428,83 @@ internal byte[] GetPipeToken(bool tcp) return data; } - private void RegisterTcpToken(PipeToken pipeData) + //for broadcast + public bool CreateRoom(Guid roomId, RoomProtocol protocol) { - activeTcpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); - TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeTcpPipeStates.TryRemove(pipeData.Token, out _)); + Room room = new Room(roomId, protocol); + room.PeerRegistered += HandleRoomPeerRegistered; + room.PeerLeft += HandleRoomPeerLeft; + room.SendMessage += RouteRoomMessage; + room.RoomDestroyed += () => activeRooms.TryRemove(room.roomId, out _); + + bool good = activeRooms.TryAdd(roomId, room); + if (good) + { + return true; + } + else + { + room.Clear(); + return false; + } } - private void RegisterUdpToken(PipeToken pipeData) + // get token for spesific peer, who wants to join a room + public byte[] GetRoomToken(Guid roomId, Guid peerId) { - activeUdpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); - TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeUdpPipeStates.TryRemove(pipeData.Token, out _)); + if (activeRooms.TryGetValue(roomId, out var room)) + { + PeerRoomState state = new PeerRoomState(); + state.RoomId = roomId; + state.ExpectedClient = peerId; + + Createtoken(peerId, out byte[] data, out PipeToken token); + + if (activeRoomStates.TryAdd(peerId, state)) + return data; + return null; + } + return null; + } + + public void RemovePeerFromRoom(Guid roomId, Guid peerId) + { + if (activeRooms.TryGetValue(roomId, out var room)) + { + room.RemovePeer(peerId); + + } } + private void RouteRoomMessage( PeerRoomState to, byte[] b, int o, int c) + { + if (to.isTcp) + { + TcpServer.SendBytesToClient(to.EphemeralId, b, o, c); + } + else + { + UdpServer.SendBytesToClient(to.AssociatedEndpoint, b, o, c); + } + } + + private void HandleRoomPeerLeft(PeerRoomState state) + { + if (state.isTcp) + { + roomMapTcp.TryRemove(state.EphemeralId, out _); + } + else + { + roomMapUdp.TryRemove(state.AssociatedEndpoint, out _); + } + } + + private void HandleRoomPeerRegistered(PeerRoomState state) + { + throw new NotImplementedException(); + } public void Dispose() { TcpServer.ShutdownServer(); diff --git a/NetworkLibrary/DistributedP2P/SimpleRelay/Room.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/Room.cs new file mode 100644 index 0000000..d47fffd --- /dev/null +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/Room.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.SimpleRelay +{ + public enum RoomProtocol + { + Tcp, + Udp, + } + internal class Room + { + public Guid roomId; + public readonly RoomProtocol protocol; + public Action PeerRegistered; + public Action PeerLeft; + public Action SendMessage; + public Action RoomDestroyed; + + //peerId -> info + ConcurrentDictionary roster = new ConcurrentDictionary(); + + public Room(Guid roomId, RoomProtocol protocol) + { + this.roomId = roomId; + this.protocol = protocol; + } + + // peer id determined by true server + internal void Add(Guid peerId, PeerRoomState roomState) + { + if( roster.TryAdd(peerId, roomState)) + { + PeerRegistered?.Invoke(roomState); + } + } + + internal void HandleMessage(Guid from, byte[] bytes, int offset, int count) + { + foreach (var peerInfo in roster.Values) + { + if (peerInfo.EphemeralId.Equals(from)) + continue; + + SendMessage?.Invoke(peerInfo, bytes, offset, count); + } + } + + internal void HandleMessage(IPEndPoint from, byte[] bytes, int offset, int count) + { + foreach (var peerInfo in roster.Values) + { + if (peerInfo.AssociatedEndpoint.Equals(from)) + continue; + + SendMessage?.Invoke(peerInfo, bytes, offset, count); + } + } + + internal bool RemovePeer(Guid peerId) + { + bool removed = roster.TryRemove(peerId, out var st); + if (removed) + { + PeerLeft?.Invoke(st); + + if (roster.Count == 0) + { + RoomDestroyed?.Invoke(); + } + } + + return removed; + } + + public void Clear() + { + PeerRegistered = null; + PeerLeft = null; + SendMessage = null; + RoomDestroyed = null; + } + + //internal IPEndPoint GetEndpoint(Guid to) + //{ + // roster.TryGetValue(to, out PeerRoomState state); + // return state.AssociatedEndpoint; + //} + + + } +} diff --git a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs index 99df714..78cfaf9 100644 --- a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs +++ b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs @@ -1,20 +1,102 @@ using NetworkLibrary.Components; +using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Client.StateManagement; using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.DistributedP2P.Server.StateManagement; -using NetworkLibrary.P2P.Generic; using NetworkLibrary.Utils; using System; using System.Collections.Generic; -using System.IO; using System.Reflection; -using System.Threading; namespace NetworkLibrary.P2P.Components.HolePunch { public class KnownTypeSerializer { - #region AuthenticatedPeerList + #region ClientHolepunchData + + internal static void SerializeHolepunchData(PooledMemoryStream stream, ClientHolepunchData hpData) + { + byte index = 0; + int oldPos = stream.Position32; + stream.WriteByte(index); + + if (hpData.ChannelInfo != null) + { + SerializeChannelInfo(stream, hpData.ChannelInfo); + index = 1; + + } + if (hpData.Endpoints != null) + { + SerializeEndpointTransferMessage(stream, hpData.Endpoints); + index += 2; + } + + if (hpData.DHPublic != null) + { + PrimitiveEncoder.WriteInt32(stream, hpData.DHPublic.Length); + stream.Write(hpData.DHPublic,0,hpData.DHPublic.Length); + index += 4; + } + + var buf = stream.GetBuffer(); + buf[oldPos] = index; + + } + + + internal static ClientHolepunchData DeserializeHolepunchData(byte[] buffer, ref int offset) + { + var hpData = new ClientHolepunchData(); + var index = buffer[offset++]; + + + if ((index & 1) != 0) + { + hpData.ChannelInfo = DeserializeChannelInfo(buffer, ref offset); + } + if ((index & 1 << 1) != 0) + { + hpData.Endpoints = DeserializeEndpointTransferMessage(buffer, ref offset); + } + if ((index & 1 << 2) != 0) + { + int Dhlen = PrimitiveEncoder.ReadInt32(buffer, ref offset); + if (Dhlen > 0) + { + hpData.DHPublic = ByteCopy.ToArray(buffer, offset, Dhlen); + offset += Dhlen; + } + else + hpData.DHPublic = new byte[0]; + } + + return hpData; + } + + #endregion + + #region ChannelInfo + + public static void SerializeChannelInfo(PooledMemoryStream stream, ChannelInfo channelInfo) + { + PrimitiveEncoder.WriteInt32(stream, (int)channelInfo.ChannelType); + PrimitiveEncoder.WriteStringUtf8(stream, channelInfo.ChannelName ?? ""); + } + + + public static ChannelInfo DeserializeChannelInfo(byte[] buffer, ref int offset) + { + return new ChannelInfo() + { + ChannelType = (ChannelType)PrimitiveEncoder.ReadInt32(buffer, ref offset), + ChannelName = PrimitiveEncoder.ReadStringUtf8(buffer, ref offset), + }; + } + #endregion + + #region PeerStatusList internal static void SerializePeerStatusList(PooledMemoryStream stream, PeerStatusList statusList) { byte index = 0; @@ -22,7 +104,7 @@ internal static void SerializePeerStatusList(PooledMemoryStream stream, PeerStat int oldPos = stream.Position32; stream.WriteByte(index); - if (statusList.NewOnline.Count>0) + if (statusList.NewOnline.Count > 0) { PrimitiveEncoder.WriteInt32(stream, statusList.NewOnline.Count); foreach (var item in statusList.NewOnline) @@ -31,7 +113,7 @@ internal static void SerializePeerStatusList(PooledMemoryStream stream, PeerStat } index = 1; } - if (statusList.WentOffline.Count>0) + if (statusList.WentOffline.Count > 0) { PrimitiveEncoder.WriteInt32(stream, statusList.WentOffline.Count); foreach (var item in statusList.WentOffline) @@ -56,10 +138,10 @@ internal static PeerStatusList DeserializePeerStatusList(byte[] buffer, ref int for (int i = 0; i < len; i++) { //online - var sts = DeserializePeerStatus(buffer, ref offset); + var sts = DeserializePeerStatus(buffer, ref offset); data.NewOnline.TryAdd(sts.EphemeralId, sts); } - + } @@ -77,7 +159,7 @@ internal static PeerStatusList DeserializePeerStatusList(byte[] buffer, ref int return data; } - internal static void SerializePeerStatus(PooledMemoryStream stream, PeerStatus status) + internal static void SerializePeerStatus(PooledMemoryStream stream, PeerStatus status) { PrimitiveEncoder.WriteGuid(stream, status.EphemeralId); PrimitiveEncoder.WriteGuid(stream, status.PeerId); @@ -87,7 +169,7 @@ internal static void SerializePeerStatus(PooledMemoryStream stream, PeerStatus s internal static PeerStatus DeserializePeerStatus(byte[] buffer, ref int offset) { PeerStatus peerStatus = new PeerStatus(); - peerStatus.EphemeralId = PrimitiveEncoder.ReadGuid(buffer,ref offset); + peerStatus.EphemeralId = PrimitiveEncoder.ReadGuid(buffer, ref offset); peerStatus.PeerId = PrimitiveEncoder.ReadGuid(buffer, ref offset); peerStatus.OnlineSince = PrimitiveEncoder.ReadDatetime(buffer, ref offset); @@ -96,6 +178,7 @@ internal static PeerStatus DeserializePeerStatus(byte[] buffer, ref int offset) #endregion + #region PipeData internal static void SerializePipeData(PooledMemoryStream stream, PipeData pipeData) @@ -231,6 +314,37 @@ public static EndpointTransferMessage DeserializeEndpointTransferMessage(byte[] } + if ((index & 1 << 1) != 0) + { + data.PortRemote = PrimitiveEncoder.ReadInt32(buffer, ref offset); + } + + if ((index & 1 << 2) != 0) + { + int listCount = PrimitiveEncoder.ReadInt32(buffer, ref offset); + data.LocalEndpoints = new List(listCount + 1); + for (int i = 0; i < listCount; i++) + { + data.LocalEndpoints.Add(DeserializeEndpointData(buffer, ref offset)); + } + } + + return data; + } + + public static EndpointTransferMessage DeserializeEndpointTransferMessage(byte[] buffer, ref int offset) + { + var index = buffer[offset++]; + + var data = new EndpointTransferMessage(); + if ((index & 1) != 0) + { + int len = PrimitiveEncoder.ReadInt32(buffer, ref offset); + data.IpRemote = ByteCopy.ToArray(buffer, offset, len); + offset += len; + } + + if ((index & 1 << 1) != 0) { data.PortRemote = PrimitiveEncoder.ReadInt32(buffer, ref offset); diff --git a/NetworkLibrary/P2P/Components/HolePunch/Messages.cs b/NetworkLibrary/P2P/Components/HolePunch/Messages.cs index 636f8b3..d136682 100644 --- a/NetworkLibrary/P2P/Components/HolePunch/Messages.cs +++ b/NetworkLibrary/P2P/Components/HolePunch/Messages.cs @@ -29,7 +29,7 @@ public EndpointData() public EndpointData(IPEndPoint ep) { - Ip = ep.Address.GetAddressBytes(); + Ip = ep.Address.MapToIPv4().GetAddressBytes(); Port = ep.Port; } diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index d8d82e1..86e3a72 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -180,6 +180,12 @@ public void SecurePipeTest() DistributedLobbyClient distributedLobbyClient2 = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); TaskCompletionSource tcs = new TaskCompletionSource(); + + Task.Delay(12000).ContinueWith(t => + { + tcs.TrySetResult(false); + }); + ManualResetEvent mre = new ManualResetEvent(false); List received = new List(); int iter = 20; @@ -233,12 +239,15 @@ void Channel_BytesReceived(byte[] buff, int offset, int count) { received.Add(buff[offset]); - if(received.Count == iter) - tcs.TrySetResult(true); + //if(received.Count == iter) + // tcs.TrySetResult(true); } + + + var ss = tcs.Task.Result; - Assert.IsTrue(ss); + Assert.IsTrue(received.Count == iter); for (int i = 0; i < received.Count; i++) { @@ -613,9 +622,8 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) var ss = tcs.Task.Result; Assert.AreEqual(data.Length, received); - - } + [TestMethod] public void TcpHolepunch() { @@ -829,7 +837,7 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) if (Interlocked.Increment(ref numReceived) == 100) tcs.TrySetResult(true); } - Task.Delay(2000).ContinueWith(t => + Task.Delay(5000).ContinueWith(t => { tcs.TrySetResult(false); }); From cd76ec64676d8aea07f83f9098bfa74b39822253 Mon Sep 17 00:00:00 2001 From: dogancan ozturk Date: Thu, 10 Apr 2025 23:39:07 +0200 Subject: [PATCH 20/27] minor changes --- .../Components/EphemeralKeyManager.cs | 2 +- .../Channels/Components/KeepAlive.cs | 4 +- .../ClientUdpHolepunchState.cs | 58 ++++++++++--------- .../ServerUdpHolepunchState.cs | 2 +- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs index fb77aeb..57c4bcb 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs @@ -123,7 +123,7 @@ private void HandleKeyExchangeAck(byte[] buffer, int offset, int count) //[Bob] private void HandleFinalize() { - Console.WriteLine("KeyExchanged"); + // Console.WriteLine("KeyExchanged"); if (closed) return; CurrKeyNumber = currKeyNumber; diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs index a2eeddb..759c1b4 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs @@ -48,13 +48,13 @@ private void SendKeepAlive() { r.GetBytes(innerBuff, 0, 32); SendData?.Invoke(MessageFlags.KeepAliveMessage, innerBuff, 0, 32); - Console.WriteLine("Keep alive sent"); + //Console.WriteLine("Keep alive sent"); } private void HandleKeepAlive(byte[] buffer, int offset, int count) { lastReceived = DateTime.Now; - Console.WriteLine("Keep alive received"); + //Console.WriteLine("Keep alive received"); } internal void Close() diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 43936c6..2bd038a 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -6,8 +6,6 @@ using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.Utils; using System; -using System.Collections.Generic; -using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; @@ -36,7 +34,6 @@ internal class ClientUdpHolepunchState : ConversationStateBase private IPEndPoint selfRemoteEp; private IPEndPoint selfLocalEp; - public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerendPoint, ChannelInfo info) : base(stateId, 20000) { this.destId = destId; @@ -92,11 +89,11 @@ public override void HandleMessage(MessageEnvelope message) break; } } - catch(Exception ex) + catch (Exception ex) { - OnError(ex.StackTrace); + OnError(ex.Message + "\n" + ex.StackTrace); } - + } // the destination peer of hp @@ -129,6 +126,7 @@ private async void HandleRemoteHpRequest(MessageEnvelope message) private void StartHolepunchRoutine(MessageEnvelope message) { + int offs = message.PayloadOffset; var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); @@ -137,31 +135,31 @@ private void StartHolepunchRoutine(MessageEnvelope message) var time = double.Parse(message.KeyValuePairs["Time"]); - + if (epMsg.LocalEndpoints.Count > 0) { - var now0 = connection.GetTime(); - var delay0 = (time - now0) / 4; - PreciseTimeAwaiter.Wait(delay0); + //var now0 = connection.GetTime(); + //var delay0 = (time - now0) / 4; + Thread.Sleep(50); foreach (EndpointData localEp in epMsg.LocalEndpoints) { for (int i = 0; i < 2; i++) { TryPunch(localEp, MessageFlags.HP); - PreciseTimeAwaiter.Wait(20); + Thread.Sleep(100); if (IsCompleted()) return; } } } - + if (IsCompleted()) return; // use server ip, peer is on same network as server bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); - EndpointData publicEp = new EndpointData() { Ip = useServerIp?serverEndpoint.Ip: epMsg.IpRemote, Port = epMsg.PortRemote }; - + EndpointData publicEp = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; + var now = connection.GetTime(); var delay = time - now; @@ -172,10 +170,10 @@ private void StartHolepunchRoutine(MessageEnvelope message) PreciseTimeAwaiter.Wait(delay); if (IsCompleted()) return; - for (int i = 0; i < 5; i++) + for (int i = 0; i < 8; i++) { TryPunch(publicEp, MessageFlags.HP); - PreciseTimeAwaiter.Wait(20 * i * i); + PreciseTimeAwaiter.Wait(20 + (20 * i * i)); if (IsCompleted()) return; } @@ -188,27 +186,27 @@ private void TryPunch(EndpointData ep, MessageFlags flag) } private object m = new object(); PooledMemoryStream stream = new PooledMemoryStream(); - + private void TryPunch(IPEndPoint ep, MessageFlags flag) { lock (m) { - Log($"Sending {flag.ToString() }To " + ep.ToString()); - + Log($"Sending {flag.ToString()}To " + ep.ToString()); + stream.Position = 0; stream.WriteByte((byte)flag); var epd = new EndpointData(ep); KnownTypeSerializer.SerializeEndpointData(stream, epd); - Socket.SendTo(stream.GetBuffer(),stream.Position32, SocketFlags.None, ep); + Socket.SendTo(stream.GetBuffer(), stream.Position32, SocketFlags.None, ep); } } private async Task StartUdpSocket() { - + Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); Socket.SendBufferSize = 12800000; Socket.ReceiveBufferSize = 12800000; @@ -228,7 +226,7 @@ private async Task StartUdpSocket() Receive(); - + } private async void Receive() { @@ -245,8 +243,8 @@ private async void Receive() var completedTask = await Task.WhenAny(receiveTask, Task.Delay(5000)); if (completedTask == receiveTask) { - SocketReceiveFromResult received = receiveTask.Result; - + SocketReceiveFromResult received = await receiveTask; + if (buffer[0] == (byte)MessageFlags.HP) { // this must be only once @@ -254,7 +252,7 @@ private async void Receive() { var ipep = (IPEndPoint)received.RemoteEndPoint; Log("[-]Received 0xFF from " + ipep.ToString()); - TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); + TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); } } @@ -282,7 +280,7 @@ private async void Receive() return; } } - catch(Exception e) + catch (Exception e) { Log("ERROR" + e.Message); Cancel(); @@ -305,6 +303,7 @@ private void ReceivedBidirectional(EndPoint remoteEndPoint) if (Interlocked.CompareExchange(ref SuccesfulEndpoint, ipep, null) != null) return; + var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchSucces; @@ -336,15 +335,18 @@ private void HandleFailure() Completed(false); } - + // Need consesus! private void HandleRemoteSucces(MessageEnvelope message) { - if (ChannelInfo.RequiresKeyExchange()) SharedSecret = df.CalculateSharedSecret(otherPublicKey); + if (SuccesfulEndpoint == null) + throw new Exception("Endpont is null"); + Log("Punched"); Completed(true); + } protected override void Completed(bool succes) diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs index c5876bd..320a1ce 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs @@ -86,7 +86,7 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) var msg = CreateEnvelope(); msg.Header = InternalConstants.StartHP; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Time"] = (connection.GetTime() + 500).ToString(); + msg.KeyValuePairs["Time"] = ((connection.GetTime() + 500) + (200*Math.Max(fromAdresses.LocalEndpoints.Count, toAddresses.LocalEndpoints.Count))).ToString(); sessionManager.GetSessionData(From, out ServerSession sesFrom); sessionManager.GetSessionData(To, out ServerSession sesTo); From 9b0d533d0e1386a42e0265219c3498199da20a1a Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Fri, 11 Apr 2025 13:47:44 +0200 Subject: [PATCH 21/27] refactor, censensus on hp, timesync improvements. --- .../Channels/Components/ChannelFactory.cs | 4 +- .../Channels/Components/IChannel.cs | 31 ++++ .../Channels/Components/KeepAlive.cs | 4 +- .../Channels/Components/UdpChannelBase.cs | 60 ++++--- .../DistributedP2P/Channels/TcpChannel.cs | 23 ++- .../DistributedP2P/Channels/UdpChannel.cs | 15 +- .../Client/DistributedLobbyClient.cs | 17 +- .../StateManagement/ClientConnectionState.cs | 1 - .../Client/StateManagement/ClientPipeState.cs | 1 - ...s => ClientSequentialTcpHolepunchState.cs} | 111 +++++++------ ...=> ClientSimultaneousTcpHolepunchState.cs} | 152 +++++++++++++----- .../ClientUdpHolepunchState.cs | 29 ++-- .../IDistributedConnection.cs | 5 +- .../DistributedP2P/Components/TimeSync.cs | 46 +++--- .../Server/DistributedLobbyServer.cs | 108 ++++++++----- .../DistributedP2P/Server/RoomManager.cs | 16 ++ .../DistributedP2P/Server/SessionManager.cs | 2 +- ...s => ServerSequentialTcpHolepunchState.cs} | 6 +- ...=> ServerSimultaneousTcpHolepunchState.cs} | 88 ++++++---- .../SimpleRelay/RelayService.cs | 31 ++-- .../DistributedP2P/SimpleRelay/Room.cs | 26 +++ NetworkLibrary/UDP/AsyncUdpServer.cs | 3 + .../DistributedP2P/DistP2PServerclientTest.cs | 44 ++--- 23 files changed, 539 insertions(+), 284 deletions(-) rename NetworkLibrary/DistributedP2P/Client/StateManagement/{ClientTcpHolepunchState2.cs => ClientSequentialTcpHolepunchState.cs} (85%) rename NetworkLibrary/DistributedP2P/Client/StateManagement/{ClientTcpHolepunchState.cs => ClientSimultaneousTcpHolepunchState.cs} (67%) rename NetworkLibrary/DistributedP2P/{Server => Components}/IDistributedConnection.cs (69%) create mode 100644 NetworkLibrary/DistributedP2P/Server/RoomManager.cs rename NetworkLibrary/DistributedP2P/Server/StateManagement/{ServerTcpHolepunchState2.cs => ServerSequentialTcpHolepunchState.cs} (95%) rename NetworkLibrary/DistributedP2P/Server/StateManagement/{ServerTcpHolepunchState.cs => ServerSimultaneousTcpHolepunchState.cs} (66%) diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs index 1a783ac..401e393 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs @@ -13,7 +13,7 @@ namespace NetworkLibrary.DistributedP2P.Channels.Components { internal class ChannelFactory { - public static IChannel CreateChannel(ClientTcpHolepunchState TcpHpstate, bool isInitiator) + public static IChannel CreateChannel(ClientSimultaneousTcpHolepunchState TcpHpstate, bool isInitiator) { ChannelInfo info = TcpHpstate.ChannelInfo; Socket connectedSocket = TcpHpstate.Socket; @@ -24,7 +24,7 @@ public static IChannel CreateChannel(ClientTcpHolepunchState TcpHpstate, bool is return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator); } - public static IChannel CreateChannel(ClientTcpHolepunchState2 TcpHpstate, bool isInitiator) + public static IChannel CreateChannel(ClientSequentialTcpHolepunchState TcpHpstate, bool isInitiator) { ChannelInfo info = TcpHpstate.ChannelInfo; Socket connectedSocket = TcpHpstate.Socket; diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs b/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs index e180cc4..5fefbfd 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/IChannel.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using NetworkLibrary.DistributedP2P.Client; namespace NetworkLibrary.DistributedP2P.Channels.Components @@ -6,11 +7,41 @@ namespace NetworkLibrary.DistributedP2P.Channels.Components public interface IChannel { + event Action OnBytesReceived; + + event Action OnDisconnected; + + /// + /// Information about channel type + /// ChannelInfo Info { get; } + /// + /// Starts data receiver. + /// Start must be called after all event subscription is made + /// void Start(); + /// + /// Sends buytes to destionation asyncronusly. + /// + /// + /// + /// + void Send(byte[] buffer, int offset, int count); + + /// + /// Pings the channel, -1 means ping failed. + /// + /// + Task Ping(); + + /// + /// Closes the channel and releases resources. + /// void CloseChannel(); + + } } \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs index 759c1b4..f88061b 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs @@ -46,8 +46,8 @@ public void HandleMessage(MessageFlags flag, byte[] buffer, int offset, int coun RandomNumberGenerator r = RandomNumberGenerator.Create(); private void SendKeepAlive() { - r.GetBytes(innerBuff, 0, 32); - SendData?.Invoke(MessageFlags.KeepAliveMessage, innerBuff, 0, 32); + r.GetBytes(innerBuff, 0, 16); + SendData?.Invoke(MessageFlags.KeepAliveMessage, innerBuff, 0, 16); //Console.WriteLine("Keep alive sent"); } diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs index ad863f2..d6f2da4 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs @@ -1,14 +1,12 @@ using NetworkLibrary.DistributedP2P.Client; using System; -using System.Collections.Generic; using System.Net; using System.Net.Sockets; -using System.Text; using System.Threading; namespace NetworkLibrary.DistributedP2P.Channels.Components { - internal class UdpChannelBase : IChannel, IDisposable + internal class UdpChannelBase : IDisposable { private Socket udpSocket; private SocketAsyncEventArgs receiveArgs; @@ -16,7 +14,8 @@ internal class UdpChannelBase : IChannel, IDisposable public ChannelInfo Info { get; private set; } - public event Action OnMessageReceived; + public event Action OnBytesReceived; + public event Action OnDisconnected; public UdpChannelBase(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) { this.udpSocket = udpSocket; @@ -31,7 +30,14 @@ public void Start() public void Send(byte[] data, int offset, int count) { - udpSocket.SendTo(data, offset, count, SocketFlags.None, associatedEndpoint); + try + { + udpSocket.SendTo(data, offset, count, SocketFlags.None, associatedEndpoint); + } + catch(Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + } } private void StartReceiver() @@ -43,7 +49,6 @@ private void StartReceiver() receiveArgs.Completed += OnReceiveCompleted; receiveArgs.RemoteEndPoint = associatedEndpoint; Receive(); - } private void Receive() @@ -66,7 +71,6 @@ private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) if (e.BytesTransferred > 0) { - try { ProcessReceivedData(e.Buffer, e.Offset, e.BytesTransferred, e.RemoteEndPoint); @@ -87,40 +91,52 @@ private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) private void ProcessReceivedData(byte[] buffer, int offset, int bytesTransferred, EndPoint remoteEndPoint) { - OnMessageReceived?.Invoke(buffer, offset, bytesTransferred); + OnBytesReceived?.Invoke(buffer, offset, bytesTransferred); } private void HandleSocketError(SocketError error) { - Console.WriteLine($"Socket error occurred: {error}"); + if (error != SocketError.Shutdown) + Log($"Socket error occurred: {error}"); + + OnDisconnected?.Invoke(); + CloseChannel(); } + private void Log(string err) + { + Console.WriteLine(err); + } public virtual void CloseChannel() { Dispose(); } + + int disposed = 0; public virtual void Dispose() { - try + if (Interlocked.CompareExchange(ref disposed, 1, 0) == 0) { - if (receiveArgs != null) + try { + if (receiveArgs != null) + { - BufferPool.ReturnBuffer(receiveArgs.Buffer); - receiveArgs.Dispose(); - receiveArgs = null; - } + BufferPool.ReturnBuffer(receiveArgs.Buffer); + receiveArgs.Dispose(); + receiveArgs = null; + } - if (udpSocket != null) - { - udpSocket.Close(); - udpSocket.Dispose(); - udpSocket = null; + if (udpSocket != null) + { + udpSocket.Close(); + udpSocket.Dispose(); + udpSocket = null; + } } + catch { } } - catch { } - } } diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs index 2aa43e2..4647775 100644 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs @@ -13,8 +13,8 @@ public class TcpChannel : IChannel { public ChannelInfo Info { get; private set; } - public event Action BytesReceived; - public event Action Disconnected; + public event Action OnBytesReceived; + public event Action OnDisconnected; private readonly Socket connectedSocket; private int totalBytesReceived; @@ -60,7 +60,7 @@ public Task Ping() return pinger.Ping(); } - public void SendAsync(byte[] buffer, int offset, int count) + public void Send(byte[] buffer, int offset, int count) { FlagAndSend(MessageFlags.StandardMessage, buffer, offset, count); } @@ -162,16 +162,23 @@ private void Sent(object sender, SocketAsyncEventArgs e) if (e.SocketError != SocketError.Success) { ErrorAndEnd($"While sending a socket error occured {e.SocketError}"); - CloseChannel(); return; } - else if (e.BytesTransferred == 0) { Log("0 bytes Sent"); CloseChannel(); return; } + else if (e.BytesTransferred < e.Count) + { + sendArgs.SetBuffer(e.Offset + e.BytesTransferred, e.Count - e.BytesTransferred); + if (!connectedSocket.SendAsync(sendArgs)) + { + ThreadPool.UnsafeQueueUserWorkItem(_ => Sent(null, sendArgs), null); + } + return; + } bool send = false; lock (sendMtex) { @@ -216,6 +223,7 @@ public void Start() { Receive(); } + private void InitializeReceiver() { receiveArgs = new SocketAsyncEventArgs(); @@ -247,6 +255,7 @@ private void Received(object sender, SocketAsyncEventArgs e) CloseChannel(); return; } + totalBytesReceived += e.BytesTransferred; try { @@ -290,7 +299,7 @@ protected virtual void HandleReceivedMessage(byte[] buffer, int offset, int coun protected void PublishBytes(byte[] buffer, int offset, int count) { - BytesReceived?.Invoke(buffer, offset, count); + OnBytesReceived?.Invoke(buffer, offset, count); } public void CloseChannel() @@ -310,7 +319,7 @@ protected virtual void ReleaseResources() } catch { } keepAlive.Close(); - Disconnected?.Invoke(); + OnDisconnected?.Invoke(); } diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs index 2e3de0a..64cf184 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs @@ -17,8 +17,8 @@ public class UdpChannel : IChannel private UdpChannelBase innerchannel; - public event Action OnMessageReceived; - public event Action Disconnected; + public event Action OnBytesReceived; + public event Action OnDisconnected; protected JumboModule JumboUdp = new JumboModule(0); internal ReliableModule ReliableUdp; @@ -70,7 +70,8 @@ public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) public void Start() { - innerchannel.OnMessageReceived += BytesReceived; + innerchannel.OnBytesReceived += BytesReceived; + innerchannel.OnDisconnected += HandleDisconnect; innerchannel.Start(); } @@ -131,7 +132,7 @@ protected virtual void HandleInternalReliableMessage(byte[] buffer, int offset, protected virtual void HandleMessage(byte[] buffer, int offset, int count) { - OnMessageReceived?.Invoke(buffer, offset, count); + OnBytesReceived?.Invoke(buffer, offset, count); } protected void HandleJumboSegment(byte[] buffer, int offset, int count) @@ -236,7 +237,7 @@ protected void HandleDisconnect() { if (Interlocked.CompareExchange(ref isClosed, 1, 0) == 0) { - Disconnected?.Invoke(); + OnDisconnected?.Invoke(); Dispose(); } @@ -257,8 +258,8 @@ protected virtual void ReleseResources() innerchannel.CloseChannel(); - Disconnected = null; - OnMessageReceived = null; + OnDisconnected = null; + OnBytesReceived = null; } diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 221bce0..111f05b 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -152,11 +152,12 @@ private void HandleServerMsg(MessageEnvelope envelope) break; case InternalConstants.RequestSequentialHolepunchTcp: - ManageTcpHolepunchRequest(envelope); + ManageSequentialTcpHolepunchReq(envelope); break; case InternalConstants.RequestSimultaneousHolepunchTcp: - ManageTcpHolepunchRequest2(envelope); + ManageSimyltaneousTcpHolepunchReq(envelope); + break; } @@ -256,7 +257,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp { if (strategy == TcpHolePunchStrategy.Sequential) { - var state = new ClientTcpHolepunchState2(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info); + var state = new ClientSequentialTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info); stateManager.RegisterState(state); state.Start(); @@ -270,7 +271,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp } else { - var state = new ClientTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, info); + var state = new ClientSimultaneousTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info); stateManager.RegisterState(state); state.Start(); @@ -306,9 +307,9 @@ void State_OnComplete(IConversationState obj) } - private void ManageTcpHolepunchRequest(MessageEnvelope envelope) + private void ManageSimyltaneousTcpHolepunchReq(MessageEnvelope envelope) { - var state = new ClientTcpHolepunchState(envelope.MessageId, envelope.From, this, serverEndpoint, null); + var state = new ClientSimultaneousTcpHolepunchState(envelope.MessageId, envelope.From, this, serverEndpoint, DiscoveryServerEndpoint, null); stateManager.RegisterState(state); state.OnComplete += State_OnComplete; state.HandleMessage(envelope); @@ -323,9 +324,9 @@ void State_OnComplete(IConversationState obj) } } - private void ManageTcpHolepunchRequest2(MessageEnvelope envelope) + private void ManageSequentialTcpHolepunchReq(MessageEnvelope envelope) { - var state = new ClientTcpHolepunchState2(envelope.MessageId, envelope.From, this, serverEndpoint, DiscoveryServerEndpoint, null); + var state = new ClientSequentialTcpHolepunchState(envelope.MessageId, envelope.From, this, serverEndpoint, DiscoveryServerEndpoint, null); stateManager.RegisterState(state); state.OnComplete += State_OnComplete; state.HandleMessage(envelope); diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs index 5af5a52..9f6be3a 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs @@ -1,5 +1,4 @@ using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.P2P.Generic; using System; diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index 0726f00..e746b6e 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -1,5 +1,4 @@ using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; using System; using System.Collections.Generic; diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs similarity index 85% rename from NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs rename to NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs index 5cf7d3b..54fc6a5 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState2.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs @@ -1,6 +1,5 @@ using NetworkLibrary.Components.Crypto.DiffieHellman; using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.Utils; using System; @@ -19,7 +18,7 @@ class ClientHolepunchData public byte[] DHPublic; } - internal class ClientTcpHolepunchState2 : ConversationStateBase + internal class ClientSequentialTcpHolepunchState : ConversationStateBase { private readonly Guid destId; private readonly IDistributedConnection connection; @@ -44,16 +43,17 @@ internal class ClientTcpHolepunchState2 : ConversationStateBase int swapCnt = 0; bool isListening = false; - List localEndpoints = new List(); - + List localEndpoints = new List(); + private IPEndPoint selfRemoteEp; private IPEndPoint selfLocalEp = new IPEndPoint(IPAddress.Any, 0); private EndpointData publicEndpointToConnect; - + int conditionCount = 0; + private bool IsEstablished => Interlocked.CompareExchange(ref established, 0, 0) == 1; - public ClientTcpHolepunchState2(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint,EndpointData discoveryServerEp, ChannelInfo info) : base(stateId, 10000) + public ClientSequentialTcpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerEp, ChannelInfo info) : base(stateId, 10000) { this.destId = destId; this.connection = connection; @@ -69,16 +69,14 @@ public async void Start() Log(StateId.ToString()); await BindPort(); - - Log("Local port " + selfLocalEp.Port); - Log("Remote port " + selfRemoteEp.Port); + Log($"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); var msg = CreateEnvelope(); msg.To = destId; - msg.Header = InternalConstants.RequestSimultaneousHolepunchTcp; - + msg.Header = InternalConstants.RequestSequentialHolepunchTcp; + var stream = SharerdMemoryStreamPool.RentStreamStatic(); - KnownTypeSerializer.SerializeHolepunchData(stream,GetHpData()); + KnownTypeSerializer.SerializeHolepunchData(stream, GetHpData()); msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); @@ -86,13 +84,13 @@ public async void Start() SharerdMemoryStreamPool.ReturnStreamStatic(stream); } - + public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - case InternalConstants.RequestSimultaneousHolepunchTcp: + case InternalConstants.RequestSequentialHolepunchTcp: HandleRemoteHpRequest(message); break; @@ -118,14 +116,11 @@ private async void HandleRemoteHpRequest(MessageEnvelope message) { Log(StateId.ToString()); int offs = message.PayloadOffset; - var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); ChannelInfo = hpData.ChannelInfo; - await BindPort(); - - Log("Local port " + selfLocalEp.Port); - Log("Remote port " + selfRemoteEp.Port); + Log($"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); StartTcpListener(); Log("listening"); @@ -133,7 +128,7 @@ private async void HandleRemoteHpRequest(MessageEnvelope message) var msg = CreateEnvelope(); msg.To = destId; msg.Header = InternalConstants.AckRequestHolepunchTcp; - + var hpd = GetHpData(); hpd.ChannelInfo = null; @@ -159,6 +154,9 @@ private void StartHolepunchRoutine(MessageEnvelope message) bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); publicEndpointToConnect = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; + SignalCompletionCondition(); + if (IsCompleted()) return; + if (!isListening) { TryPunch(); @@ -166,6 +164,7 @@ private void StartHolepunchRoutine(MessageEnvelope message) } + private void TryPunch() { try @@ -194,7 +193,7 @@ private void TryPunch() catch { } finally { - if(Interlocked.CompareExchange(ref established,0,0) == 0) + if (Interlocked.CompareExchange(ref established, 0, 0) == 0) SwapAndNotify(); } } @@ -205,7 +204,7 @@ private void SwapAndNotify() { if (IsCompleted()) return; - + int cnt = 0; while (!isListening) { @@ -215,8 +214,9 @@ private void SwapAndNotify() Swap(); if (IsCompleted()) return; } - catch { - Thread.Sleep(200); + catch + { + Thread.Sleep(200); } cnt++; @@ -245,7 +245,7 @@ private void Swap() { Log("Swapping to Sender"); StopListener(); - ThreadPool.UnsafeQueueUserWorkItem( _ => TryPunch(), null); + ThreadPool.UnsafeQueueUserWorkItem(_ => TryPunch(), null); } else { @@ -299,11 +299,11 @@ private async Task BindPort() clientSocket.Bind(selfLocalEp); selfLocalEp = (IPEndPoint)clientSocket.LocalEndPoint; - var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket,discoveryServerEp.ToIpEndpoint(),5000); + var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket, discoveryServerEp.ToIpEndpoint(), 5000); if (remoteEp == null) { Log("Failed to get public endpoint"); - remoteEp = new EndpointData("0.0.0.0",selfLocalEp.Port); + remoteEp = new EndpointData("0.0.0.0", selfLocalEp.Port); } selfRemoteEp = remoteEp.ToIpEndpoint(); @@ -315,7 +315,7 @@ private async Task BindPort() clientSocket = null; } catch { } - + } private int StartTcpListener() @@ -365,9 +365,10 @@ private void StopListener() listeningSocket?.Dispose(); listeningSocket = null; } - catch { } + catch { } } - + + // In this case one or the other private void HandleConnectedSocket(Socket socket) { @@ -415,33 +416,40 @@ private void HandleFailure() private void HandleRemoteSucces(MessageEnvelope message) { - - if (ChannelInfo.RequiresKeyExchange()) - SharedSecret = df.CalculateSharedSecret(othersPublicKey); - - if (connectedSocket!=null) - { - Socket = connectedSocket; - } - else + SignalCompletionCondition(); + } + private void SignalCompletionCondition() + { + if (Interlocked.Increment(ref conditionCount) == 2) { - Socket = acceptedSocket; - } + if (ChannelInfo.RequiresKeyExchange()) + SharedSecret = df.CalculateSharedSecret(othersPublicKey); - if(Socket == null) - { - Log("Failed to get socket"); - Completed(false); - return; + if (connectedSocket != null) + { + Socket = connectedSocket; + } + else + { + Socket = acceptedSocket; + } + + if (Socket == null) + { + Log("Failed to get socket"); + Completed(false); + return; + } + SuccesfulEndpoint = (IPEndPoint)Socket.RemoteEndPoint; + Log("Punched"); + Completed(true); } - SuccesfulEndpoint = (IPEndPoint)Socket.RemoteEndPoint; - Log("Punched"); - Completed(true); } + public override void Cancel() { - lock(cancellationMutex) + lock (cancellationMutex) { if (!IsCompleted()) { @@ -452,7 +460,7 @@ public override void Cancel() Completed(false); } } - + } protected override void Completed(bool succes) @@ -502,7 +510,6 @@ private ClientHolepunchData GetHpData() hpd.Endpoints = epm; - if (ChannelInfo.RequiresKeyExchange()) { hpd.DHPublic = df.GetPublicKey(); diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs similarity index 67% rename from NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs rename to NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs index fc37c8f..47f853c 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs @@ -1,6 +1,5 @@ using NetworkLibrary.Components.Crypto.DiffieHellman; using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; using System; using System.Collections.Generic; @@ -8,20 +7,24 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using NetworkLibrary.Utils; namespace NetworkLibrary.DistributedP2P.Client.StateManagement { - internal class ClientTcpHolepunchState : ConversationStateBase + internal class ClientSimultaneousTcpHolepunchState : ConversationStateBase { private readonly Guid destId; private readonly IDistributedConnection connection; private readonly EndpointData serverEndpoint; + private readonly EndpointData discoveryServerEp; private bool isInitiator; public Socket Socket; public IPEndPoint SuccesfulEndpoint; private DiffieHellman df = new DiffieHellman(); private byte[] otherPublicKey; + List localEndpoints = new List(); + public byte[] SharedSecret; public ChannelInfo ChannelInfo; @@ -32,36 +35,47 @@ internal class ClientTcpHolepunchState : ConversationStateBase private int established = 0; private int connected = 0; private int accepted = 0; + + private IPEndPoint selfRemoteEp; + private IPEndPoint selfLocalEp = new IPEndPoint(IPAddress.Any, 0); + private EndpointData publicEndpointToConnect; + + private bool IsEstablished => Interlocked.CompareExchange(ref established, 0, 0) == 1; - public ClientTcpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 5000) + public ClientSimultaneousTcpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerEp, ChannelInfo info) : base(stateId, 5000) { this.destId = destId; this.connection = connection; this.serverEndpoint = serverEndpoint; + this.discoveryServerEp = discoveryServerEp; this.ChannelInfo = info; } //the initiator - public void Start() + public async void Start() { + isInitiator = true; Log(StateId.ToString()); - localPort = StartTcpSocket(); - - var msg = CreateEnvelope(); - msg.Header = InternalConstants.RequestSequentialHolepunchTcp; - msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Port"] = localPort.ToString(); - msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); - msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; + await BindPort(); + Log($"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); - if (ChannelInfo.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + StartListening(); + Log("listening"); + var msg = CreateEnvelope(); msg.To = destId; + msg.Header = InternalConstants.RequestSimultaneousHolepunchTcp; + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, GetHpData()); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } @@ -70,7 +84,7 @@ public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - case InternalConstants.RequestSequentialHolepunchTcp: + case InternalConstants.RequestSimultaneousHolepunchTcp: HandleRemoteHpRequest(message); break; @@ -89,36 +103,78 @@ public override void HandleMessage(MessageEnvelope message) } // the destination peer of hp - private void HandleRemoteHpRequest(MessageEnvelope message) + private async void HandleRemoteHpRequest(MessageEnvelope message) { Log(StateId.ToString()); + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + + ChannelInfo = hpData.ChannelInfo; + await BindPort(); + Log($"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); - ChannelInfo = new ChannelInfo(); - ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); - ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; + StartListening(); + Log("listening"); - int port = StartTcpSocket(); var msg = CreateEnvelope(); + msg.To = destId; msg.Header = InternalConstants.AckRequestHolepunchTcp; - msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Port"] = port.ToString(); - if (ChannelInfo.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = Convert.ToBase64String(df.GetPublicKey()); + var hpd = GetHpData(); + hpd.ChannelInfo = null; - msg.To = destId; + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, hpd); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + + + } + + private async Task BindPort() + { + var clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + clientSocket.Bind(selfLocalEp); + selfLocalEp = (IPEndPoint)clientSocket.LocalEndPoint; + + var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket, discoveryServerEp.ToIpEndpoint(), 5000); + if (remoteEp == null) + { + Log("Failed to get public endpoint"); + remoteEp = new EndpointData("0.0.0.0", selfLocalEp.Port); + } + + selfRemoteEp = remoteEp.ToIpEndpoint(); + + try + { + clientSocket?.Close(); + clientSocket?.Dispose(); + clientSocket = null; + } + catch { } + } private void StartHolepunchRoutine(MessageEnvelope message) { - var epMsg = KnownTypeSerializer.DeserializeEndpointTransferMessage(message.Payload, message.PayloadOffset); - var time = double.Parse(message.KeyValuePairs["Time"]); - if (ChannelInfo.RequiresKeyExchange()) - otherPublicKey = Convert.FromBase64String(message.KeyValuePairs["DH"]); + if (IsCompleted()) return; + int offs = message.PayloadOffset; + + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + var epMsg = hpData.Endpoints; + otherPublicKey = hpData.DHPublic; + localEndpoints = epMsg.LocalEndpoints; + + bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); + publicEndpointToConnect = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; + var time = double.Parse(message.KeyValuePairs["Time"]); + + // if there are local endpoints to test if (epMsg.LocalEndpoints.Count > 0) { @@ -132,10 +188,6 @@ private void StartHolepunchRoutine(MessageEnvelope message) if (IsEstablished) return; - // use server ip, peer is on same network as server - bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); - EndpointData publicEp = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; - var now = connection.GetTime(); var delay = time - now; @@ -144,13 +196,10 @@ private void StartHolepunchRoutine(MessageEnvelope message) PreciseTimeAwaiter.Wait(delay); if (IsEstablished) return; - var nextTryTime = connection.GetTime()+1500; - for (int i = 0; i < 4; i++) { - if (TryConnect(publicEp, (2000))) + if (TryConnect(publicEndpointToConnect, (2000))) return; - //PreciseTimeAwaiter.Wait(nextTryTime - connection.GetTime()); if (IsEstablished) return; } @@ -195,14 +244,14 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) } - private int StartTcpSocket() + private int StartListening() { listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listeningSocket.SendBufferSize = 12800000; listeningSocket.ReceiveBufferSize = 12800000; listeningSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - listeningSocket.Bind(new IPEndPoint(IPAddress.Any, 0)); + listeningSocket.Bind(selfLocalEp); Listen(); @@ -326,6 +375,31 @@ private void Log(string log) Console.WriteLine(prefix + log); } + private ClientHolepunchData GetHpData() + { + ClientHolepunchData hpd = new ClientHolepunchData(); + hpd.ChannelInfo = ChannelInfo; + + var epm = new EndpointTransferMessage(); + var pub = new EndpointData(selfRemoteEp); + epm.IpRemote = pub.Ip; + epm.PortRemote = pub.Port; + + var localIps = IPHelper.GetLocalIPAddresses4(); + int localPort = selfLocalEp.Port; + foreach (var ip in localIps) + { + epm.LocalEndpoints.Add(new EndpointData(ip, localPort)); + } + + hpd.Endpoints = epm; + + if (ChannelInfo.RequiresKeyExchange()) + { + hpd.DHPublic = df.GetPublicKey(); + } + return hpd; + } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 2bd038a..7d44e93 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -2,7 +2,6 @@ using NetworkLibrary.Components.Crypto.DiffieHellman; using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.DistributedP2P.Server; using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.Utils; using System; @@ -34,6 +33,8 @@ internal class ClientUdpHolepunchState : ConversationStateBase private IPEndPoint selfRemoteEp; private IPEndPoint selfLocalEp; + private int conditionCount = 0; + public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerendPoint, ChannelInfo info) : base(stateId, 20000) { this.destId = destId; @@ -134,7 +135,7 @@ private void StartHolepunchRoutine(MessageEnvelope message) otherPublicKey = hpData.DHPublic; var time = double.Parse(message.KeyValuePairs["Time"]); - + SignalCompletionCondition(); if (epMsg.LocalEndpoints.Count > 0) { @@ -293,7 +294,7 @@ private async void Receive() } } - + // only called once when succesfuly received. private void ReceivedBidirectional(EndPoint remoteEndPoint) { @@ -308,6 +309,8 @@ private void ReceivedBidirectional(EndPoint remoteEndPoint) var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchSucces; connection.SendAsyncMessage(msg); + + SignalCompletionCondition(); } private void TimedOut() @@ -338,15 +341,23 @@ private void HandleFailure() // Need consesus! private void HandleRemoteSucces(MessageEnvelope message) { - if (ChannelInfo.RequiresKeyExchange()) - SharedSecret = df.CalculateSharedSecret(otherPublicKey); + SignalCompletionCondition(); + } - if (SuccesfulEndpoint == null) - throw new Exception("Endpont is null"); + private void SignalCompletionCondition() + { + if(Interlocked.Increment(ref conditionCount) == 3) + { + if (ChannelInfo.RequiresKeyExchange()) + SharedSecret = df.CalculateSharedSecret(otherPublicKey); - Log("Punched"); - Completed(true); + if (SuccesfulEndpoint == null) + throw new Exception("Endpont is null"); + Log("Punched"); + Completed(true); + } + } protected override void Completed(bool succes) diff --git a/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs b/NetworkLibrary/DistributedP2P/Components/IDistributedConnection.cs similarity index 69% rename from NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs rename to NetworkLibrary/DistributedP2P/Components/IDistributedConnection.cs index 983ee1b..c9d4d02 100644 --- a/NetworkLibrary/DistributedP2P/Server/IDistributedConnection.cs +++ b/NetworkLibrary/DistributedP2P/Components/IDistributedConnection.cs @@ -3,15 +3,14 @@ using System.Net; using System.Text; using System.Threading.Tasks; -using NetworkLibrary.DistributedP2P.Components; -namespace NetworkLibrary.DistributedP2P.Server +namespace NetworkLibrary.DistributedP2P.Components { internal interface IDistributedConnection : ITimeProvider { void SendAsyncMessage(MessageEnvelope msgs); void SendAsyncMessage(Guid a, MessageEnvelope msgs); Task SendMessageAndWaitResponse(Guid a, MessageEnvelope msg); - Task SendMessageAndWaitResponse( MessageEnvelope msg); + Task SendMessageAndWaitResponse(MessageEnvelope msg); } } diff --git a/NetworkLibrary/DistributedP2P/Components/TimeSync.cs b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs index 34fc5da..ee3b207 100644 --- a/NetworkLibrary/DistributedP2P/Components/TimeSync.cs +++ b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using NetworkLibrary.Utils; using NetworkLibrary.P2P; -using NetworkLibrary.DistributedP2P.Server; namespace NetworkLibrary.DistributedP2P.Components { @@ -95,39 +94,44 @@ public async Task SyncTime(bool usePtp = false) //12 first, 3,2,1 - int sampleSize = 3; + int sampleSize = 12; var sCnt = Interlocked.CompareExchange(ref syncCount, 0, 0); - if (sCnt == 0) - sampleSize = 12; - if (sCnt > 50) - sampleSize = 2; - - if (sCnt > 100) - sampleSize = 1; - + sampleSize = 4; + var localHistory = new List(); + var localHistoryd = new List(); for (int i = 0; i < sampleSize; i++) { var result = usePtp ? await GetOffsetPTP().ConfigureAwait(false) : await GetOffsetNTP().ConfigureAwait(false); if (result.Succes) { - timesHistory.Add(result.PreciseTime); - timesHistoryd.Add(result.DateTimeOffset); + localHistory.Add(result); + localHistoryd.Add(result); } else return false; } + var bestSamples = localHistory.OrderBy(x => x.RTT).Take((sampleSize / 2)).Select(x => x.PreciseTime).ToList(); + var bestSamplesd = localHistoryd.OrderBy(x => x.RTT).Take((sampleSize / 2)).Select(x=>x.DateTimeOffset).ToList(); + //var bestSamples = localHistory; + //var bestSamplesd = localHistoryd; + + timesHistory.AddRange(bestSamples); + timesHistoryd.AddRange(bestSamplesd); + + if (timesHistory.Count < 4) return false; var times = Statistics.FilterOutliers(timesHistory); var timesd = Statistics.FilterOutliers(timesHistoryd); - if (timesHistory.Count > 600) + + if (timesHistory.Count > 100) { - timesHistory = timesHistory.Skip(60).ToList(); - timesHistoryd = timesHistoryd.Skip(60).ToList(); + timesHistory = timesHistory.Skip(10).ToList(); + timesHistoryd = timesHistoryd.Skip(10).ToList(); } double average = times.Sum() / times.Count(); @@ -158,7 +162,7 @@ public async Task SyncTime(bool usePtp = false) } - class TimeResult { public double PreciseTime; public bool Succes; public DateTime ServerUTC; public TimeSpan DateTimeOffset; } + class TimeResult { public double PreciseTime; public bool Succes; public DateTime ServerUTC; public TimeSpan DateTimeOffset; public double RTT; } private async Task GetOffsetNTP() { @@ -173,18 +177,17 @@ private async Task GetOffsetNTP() var response = await connection.SendMessageAndWaitResponse(msg).ConfigureAwait(false); if (response.Header != MessageEnvelope.RequestTimeout) { + var now1 = clientClock.Elapsed.TotalMilliseconds; + var now1d = DateTime.UtcNow; var serverTime = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); var serverTimed = response.TimeStamp; - var now1 = clientClock.Elapsed.TotalMilliseconds; - var now1d = DateTime.UtcNow; - var timeOffset = ((serverTime - now) + (serverTime - now1)) / 2; var timeOffsetd = ((serverTimed - nowd) + (serverTimed - now1d)).TotalMilliseconds / 2; TimeSpan offd = TimeSpan.FromMilliseconds(timeOffsetd); - return new TimeResult() { PreciseTime = timeOffset, DateTimeOffset = offd, Succes = true }; + return new TimeResult() { PreciseTime = timeOffset, DateTimeOffset = offd, Succes = true, RTT = now1-now }; } return new TimeResult(); @@ -203,13 +206,14 @@ private async Task GetOffsetPTP() var t3d = t2d; var t4 = await GetServerTime(); + var trtt = clientClock.Elapsed.TotalMilliseconds; if (t4.Succes) { double offset = (((t4.PreciseTime - t3) - (t2 - t1.PreciseTime)) / 2); double offsetd = (((t4.ServerUTC - t3d) - (t2d - t1.ServerUTC)).TotalMilliseconds / 2); TimeSpan offd = TimeSpan.FromMilliseconds(offsetd); - return new TimeResult() { PreciseTime = offset, DateTimeOffset = offd, Succes = true }; + return new TimeResult() { PreciseTime = offset, DateTimeOffset = offd, Succes = true,RTT = t2- trtt }; } else return new TimeResult(); diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 554162f..4bc2056 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -1,26 +1,18 @@ using NetworkLibrary.Components.Crypto.Certificate; using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.P2P.Components.StateManagement; -using NetworkLibrary.TCP.Base; -using NetworkLibrary.TCP.SSL.Base; -using NetworkLibrary.UDP; +using NetworkLibrary.DistributedP2P.Server.StateManagement; +using NetworkLibrary.DistributedP2P.SimpleRelay; using NetworkLibrary.MessageProtocol; +using NetworkLibrary.P2P; +using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.Utils; using System; using System.Collections.Generic; -using System.Net; +using System.Diagnostics; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Data.Common; -using NetworkLibrary.P2P; -using System.Diagnostics; - +using System.Threading; using System.Threading.Tasks; -using NetworkLibrary.DistributedP2P.Server.StateManagement; -using System.Security.Cryptography; -using NetworkLibrary.DistributedP2P.SimpleRelay; -using NetworkLibrary.Utils; -using NetworkLibrary.P2P.Components.HolePunch; namespace NetworkLibrary.DistributedP2P.Server { @@ -38,7 +30,7 @@ public class ServerParameters public int UdpPort; public int DiscoveryServerPort; } - public class DistributedLobbyServerBase : IDistributedConnection,IDisposable where S : ISerializer, new() + public class DistributedLobbyServerBase : IDistributedConnection, IDisposable where S : ISerializer, new() { public readonly int SSlPort; public readonly int TcpPort; @@ -54,11 +46,12 @@ public class ServerParameters IServerDbConnector dbConnector; SessionManager sessionManager; - Components.StateManager stateManager = new Components.StateManager(); + Components.StateManager stateManager = new Components.StateManager(); RelayService piper; Stopwatch serverClock = new Stopwatch(); - RelayService pipeManager; + RelayService relayService; + RoomManager roomManager = new RoomManager(); private byte[] serverKey = new byte[16]; @@ -82,10 +75,8 @@ public void StartServer() sslServer = new SecureMessageServer(SSlPort, serverCertificate); - var random = RandomNumberGenerator.Create(); - var key = new byte[32]; - random.GetNonZeroBytes(key); - pipeManager = new RelayService(TcpPort, UdpPort, key); + + relayService = new RelayService(TcpPort, UdpPort); sslServer.OnClientRequestedConnection += ValidateSslConnection; sslServer.OnClientAccepted += SslClientAccepted; @@ -101,7 +92,7 @@ public void StartServer() discoveryServer.Start(); } - + private bool ValidateSslConnection(Socket acceptedSocket) { @@ -112,9 +103,9 @@ private bool ValidateSslConnection(Socket acceptedSocket) private void SslClientAccepted(Guid ephemeralClientId) { - TimerService.RegisterTimer(ephemeralClientId, 20000, () => - { - if (!sessionManager.IsSessionActive(ephemeralClientId)) + TimerService.RegisterTimer(ephemeralClientId, 20000, () => + { + if (!sessionManager.IsSessionActive(ephemeralClientId)) { sslServer.CloseSession(ephemeralClientId); } @@ -125,7 +116,7 @@ private void HandleConnRequest(MessageEnvelope msg) { Guid stateId = msg.MessageId; - var state = new ServerConnectionState(stateId, msg.From, this, authenticator, dbConnector,DiscoveryServerPort); + var state = new ServerConnectionState(stateId, msg.From, this, authenticator, dbConnector, DiscoveryServerPort); stateManager.RegisterState(state); state.HandleMessage(msg); @@ -139,7 +130,7 @@ private void ConnectionStateComplete(IConversationState state_) { var sessionEp = sslServer.GetSessionEndpoint(state.EphemeralClientId); var statusList = sessionManager.CreateSession(state.clientDbInfo, state.EphemeralClientId, sessionEp, state.clientLocalIps); - if(statusList!=null) + if (statusList != null) PublishPeerList(new List() { statusList }); } } @@ -184,11 +175,11 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) // ping message.From = clientId; - + if (stateManager.HandleMessage(message)) return; - + switch (message.Header) { @@ -198,16 +189,16 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) case InternalConstants.PipeRequestTcp: - var pipeState = new ServerPipeState(message.MessageId, this, pipeManager); + var pipeState = new ServerPipeState(message.MessageId, this, relayService); stateManager.RegisterState(pipeState); pipeState.HandleMessage(message); - break; + break; case InternalConstants.PipeRequestUdp: - var pipeState1 = new ServerPipeState(message.MessageId, this, pipeManager); + var pipeState1 = new ServerPipeState(message.MessageId, this, relayService); stateManager.RegisterState(pipeState1); pipeState1.HandleMessage(message); @@ -219,34 +210,65 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) state.HandleMessage(message); break; - case InternalConstants.RequestSequentialHolepunchTcp: - var state2 = new ServerTcpHolepunchState(message.MessageId, this, sessionManager); + case InternalConstants.RequestSimultaneousHolepunchTcp: + var state2 = new ServerSimultaneousTcpHolepunchState(message.MessageId, this, sessionManager); stateManager.RegisterState(state2); state2.HandleMessage(message); break; - case InternalConstants.RequestSimultaneousHolepunchTcp: - var state3 = new ServerTcpHolepunchState2(message.MessageId, this, sessionManager); + case InternalConstants.RequestSequentialHolepunchTcp: + var state3 = new ServerSequentialTcpHolepunchState(message.MessageId, this, sessionManager); stateManager.RegisterState(state3); state3.HandleMessage(message); break; case Constants.TimeSync: - + //if (ctr++ % 2 == 0) + { + //Thread.Sleep(80); + } byte[] time = new byte[8]; message.Payload = time; PrimitiveEncoder.WriteFixedDouble(time, 0, serverClock.Elapsed.TotalMilliseconds); message.TimeStamp = DateTime.UtcNow; + //if (ctr % 3 == 0) + { + //Thread.Sleep(100); + } SendAsyncMessage(clientId, message); - break; + break; + } + } + int ctr = 0; + private bool CreateRoom(string roomName, string roomPassword, RoomProtocol protocol) + { + if (roomManager.TryCreateRoom(roomName, roomPassword, out Guid RoomId)) + { + if (relayService.CreateRoom(RoomId, protocol)) + { + return true; + } } + return false; + } + + private bool GetRoomToken(Guid peerId, Guid roomId, out byte[] token) + { + token = relayService.GetRoomToken(peerId, roomId); + return token != null; + } + + private bool RemoveFromRoom(Guid peerId, Guid roomId) + { + return true; } public void SendAsyncMessage(Guid clientId, MessageEnvelope message) { sslServer.SendAsyncMessage(clientId, message); } + public void SendAsyncMessage(MessageEnvelope message) { sslServer.SendAsyncMessage(message.To, message); @@ -256,7 +278,7 @@ public Task SendMessageAndWaitResponse(Guid destination, Messag return sslServer.SendMessageAndWaitResponse(destination, envelope); } - public Task SendMessageAndWaitResponse( MessageEnvelope envelope) + public Task SendMessageAndWaitResponse(MessageEnvelope envelope) { return sslServer.SendMessageAndWaitResponse(envelope.To, envelope); } @@ -278,7 +300,7 @@ public DateTime GetDateTime() return DateTime.UtcNow; } - public double GetTime() + public double GetTime() { return serverClock.Elapsed.TotalMilliseconds; } @@ -289,10 +311,10 @@ public Task SyncTime() public void Dispose() { sslServer.ShutdownServer(); - pipeManager.Dispose(); + relayService.Dispose(); discoveryServer.Dispose(); } - + } } diff --git a/NetworkLibrary/DistributedP2P/Server/RoomManager.cs b/NetworkLibrary/DistributedP2P/Server/RoomManager.cs new file mode 100644 index 0000000..24aa39b --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/RoomManager.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Server +{ + internal class RoomManager + { + + + internal bool TryCreateRoom(string roomName, string roomPassword, out Guid roomId) + { + throw new NotImplementedException(); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs index 707ef53..9491923 100644 --- a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs +++ b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs @@ -20,7 +20,7 @@ namespace NetworkLibrary.DistributedP2P.Server { - + internal class SessionManager { diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSequentialTcpHolepunchState.cs similarity index 95% rename from NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs rename to NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSequentialTcpHolepunchState.cs index 5c62a2f..dd7c88e 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState2.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSequentialTcpHolepunchState.cs @@ -10,7 +10,7 @@ namespace NetworkLibrary.DistributedP2P.Server.StateManagement { - internal class ServerTcpHolepunchState2 : ConversationStateBase + internal class ServerSequentialTcpHolepunchState : ConversationStateBase { private readonly IDistributedConnection connection; private readonly SessionManager sessionManager; @@ -25,7 +25,7 @@ internal class ServerTcpHolepunchState2 : ConversationStateBase ChannelInfo info; private int succesCount; - public ServerTcpHolepunchState2(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) + public ServerSequentialTcpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) { this.connection = connection; this.sessionManager = sessionManager; @@ -35,7 +35,7 @@ public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - case InternalConstants.RequestSimultaneousHolepunchTcp: + case InternalConstants.RequestSequentialHolepunchTcp: HandleHolepunchRequest(message); break; case InternalConstants.AckRequestHolepunchTcp: diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSimultaneousTcpHolepunchState.cs similarity index 66% rename from NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs rename to NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSimultaneousTcpHolepunchState.cs index b2ab238..8a4276f 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSimultaneousTcpHolepunchState.cs @@ -11,20 +11,22 @@ namespace NetworkLibrary.DistributedP2P.Server.StateManagement { - internal class ServerTcpHolepunchState : ConversationStateBase + internal class ServerSimultaneousTcpHolepunchState : ConversationStateBase { private readonly IDistributedConnection connection; private readonly SessionManager sessionManager; Guid From; Guid To; - int fromPort; - string fromPublicKey; - int toPort; - string toPublicKey; + EndpointTransferMessage fromAdresses; + byte[] fromPublicKey; + + EndpointTransferMessage toAddresses; + byte[] toPublicKey; + ChannelInfo info; private int succesCount; - public ServerTcpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) + public ServerSimultaneousTcpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) { this.connection = connection; this.sessionManager = sessionManager; @@ -34,7 +36,7 @@ public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - case InternalConstants.RequestSequentialHolepunchTcp: + case InternalConstants.RequestSimultaneousHolepunchTcp: HandleHolepunchRequest(message); break; case InternalConstants.AckRequestHolepunchTcp: @@ -52,61 +54,77 @@ public override void HandleMessage(MessageEnvelope message) // obtain port from destination endpoint private void HandleHolepunchRequest(MessageEnvelope message) { - info = new ChannelInfo(); - info.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); - info.ChannelName = message.KeyValuePairs["Name"]; + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + + info = hpData.ChannelInfo; + fromPublicKey = hpData.DHPublic; + fromAdresses = hpData.Endpoints; From = message.From; To = message.To; - fromPort = int.Parse(message.KeyValuePairs["Port"]); - if (info.RequiresKeyExchange()) - fromPublicKey = message.KeyValuePairs["DH"]; + + hpData.Endpoints = null; + hpData.DHPublic = null; + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + + KnownTypeSerializer.SerializeHolepunchData(stream, hpData); + message.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(message); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } private void HandleHolepunchRequestAck(MessageEnvelope message) { - toPort = int.Parse(message.KeyValuePairs["Port"]); - if (info.RequiresKeyExchange()) - toPublicKey = message.KeyValuePairs["DH"]; - - - + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + toPublicKey = hpData.DHPublic; + toAddresses = hpData.Endpoints; var msg = CreateEnvelope(); msg.Header = InternalConstants.StartHP; msg.KeyValuePairs = new Dictionary(); + msg.KeyValuePairs["Time"] = ((connection.GetTime() + 500) + (500 * Math.Max(fromAdresses.LocalEndpoints.Count, toAddresses.LocalEndpoints.Count))).ToString(); + sessionManager.GetSessionData(From, out ServerSession sesFrom); sessionManager.GetSessionData(To, out ServerSession sesTo); + + if (sesFrom != null && sesTo != null) { - IPHelper.ObtainIpEndpoints(fromPort,toPort,sesFrom, sesTo, out var FromNeedsToKnow, out var ToNeedsToKnow); + if (IPHelper.IsZero(toAddresses.IpRemote)) + toAddresses.IpRemote = sesTo.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + if (IPHelper.IsZero(fromAdresses.IpRemote)) + fromAdresses.IpRemote = sesFrom.ClientPublicIp.Address.MapToIPv4().GetAddressBytes(); + + IPHelper.ObtainIpEndpoints(fromAdresses, + toAddresses, + out var FromNeedsToKnow, + out var ToNeedsToKnow); - // coordination signal - double startTime = connection.GetTime(); - startTime += 1000*(1+Math.Max(FromNeedsToKnow.LocalEndpoints.Count,ToNeedsToKnow.LocalEndpoints.Count)); - msg.KeyValuePairs["Time"] = startTime.ToString(CultureInfo.InvariantCulture); var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.Position32 = 0; - KnownTypeSerializer.SerializeEndpointTransferMessage(stream, FromNeedsToKnow); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); msg.To = From; - if (info.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = toPublicKey; + hpData.Endpoints = FromNeedsToKnow; + hpData.DHPublic = toPublicKey; + KnownTypeSerializer.SerializeHolepunchData(stream, hpData); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); stream.Position32 = 0; - KnownTypeSerializer.SerializeEndpointTransferMessage(stream, ToNeedsToKnow); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); msg.To = To; - if (info.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = fromPublicKey; + hpData.Endpoints = ToNeedsToKnow; + hpData.DHPublic = fromPublicKey; + KnownTypeSerializer.SerializeHolepunchData(stream, hpData); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); } else { @@ -127,10 +145,10 @@ private void HandleFailure(MessageEnvelope message) Completed(false); } - int succCounter = 0; + private void HandleSucces(MessageEnvelope message) { - if(Interlocked.Increment(ref succCounter) == 1) + if(Interlocked.Increment(ref succesCount) == 1) { var sts = message.KeyValuePairs["Status"]; diff --git a/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs index 7d906aa..b45f0ff 100644 --- a/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Concurrent; using System.Net; +using System.Security.Cryptography; namespace NetworkLibrary.DistributedP2P.SimpleRelay { @@ -71,24 +72,26 @@ internal class RelayService : IDisposable private readonly ConcurrentDictionary tokenStorage = new ConcurrentDictionary(); - byte[] cryptoKey; + byte[] cryptoKey = new byte[32]; PrivateKeySign signer; readonly object tokenMtex = new object(); - public RelayService(int TcpPort, int UdpPort, byte[] pipeKey) + public RelayService(int TcpPort, int UdpPort) { + var rng = RandomNumberGenerator.Create(); + rng.GetBytes(cryptoKey, 0, cryptoKey.Length); + TcpServer = new AsyncTcpServer(TcpPort); TcpServer.GatherConfig = ScatterGatherConfig.UseBuffer; UdpServer = new AsyncUdpServer(UdpPort); - - cryptoKey = pipeKey; + UdpServer.ClientDisconnected += HandleUdpPipeDisconnect; TcpServer.OnClientAccepted += TcpClientAccepted; TcpServer.OnClientDisconnected += HandleTcpPipeDisconnect; TcpServer.OnBytesReceived += HandleTcpBytes; UdpServer.OnBytesRecieved += HandleUdpBytes; - signer = new PrivateKeySign(pipeKey); + signer = new PrivateKeySign(cryptoKey); UdpServer.StartServer(); @@ -217,7 +220,7 @@ private void ManagePipeToken(Guid ephemeralId, byte[] bytes, int offset, int cou { if (VerifyToken(storage.Token, token.Expiration)) { - if (roomState.Verify(token,ephemeralId)) + if (roomState.Verify(token, ephemeralId)) { if (activeRooms.TryGetValue(roomState.RoomId, out Room room)) { @@ -283,7 +286,7 @@ private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int { if (VerifyToken(tokenBytes, token.Expiration)) { - if (roomState.Verify(token,clientEp)) + if (roomState.Verify(token, clientEp)) { if (activeRooms.TryGetValue(roomState.RoomId, out Room room)) { @@ -367,6 +370,10 @@ private void HandleUdpPipeDisconnect(IPEndPoint from) { pipeMapUdp.TryRemove(to, out _); } + else if(roomMapUdp.TryRemove(from, out Room room)) + { + room.HandleDisconnect(from); + } } private void HandleTcpPipeDisconnect(Guid from) @@ -376,11 +383,15 @@ private void HandleTcpPipeDisconnect(Guid from) TcpServer.CloseSession(to); pipeMapTcp.TryRemove(to, out _); } + else if (roomMapTcp.TryRemove(from, out Room room)) + { + room.HandleDisconnect(from); + } tokenStorage.TryRemove(from, out _); } - + private void RegisterTcpToken(PipeToken pipeData) { activeTcpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); @@ -410,7 +421,7 @@ private void Createtoken(Guid tokenId, out byte[] data, out PipeToken pipeData) Buffer.BlockCopy(signature, 0, data, offset, 32);//32 } - + // for direct p2p @@ -477,7 +488,7 @@ public void RemovePeerFromRoom(Guid roomId, Guid peerId) } } - private void RouteRoomMessage( PeerRoomState to, byte[] b, int o, int c) + private void RouteRoomMessage(PeerRoomState to, byte[] b, int o, int c) { if (to.isTcp) { diff --git a/NetworkLibrary/DistributedP2P/SimpleRelay/Room.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/Room.cs index d47fffd..c81b419 100644 --- a/NetworkLibrary/DistributedP2P/SimpleRelay/Room.cs +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/Room.cs @@ -76,6 +76,32 @@ internal bool RemovePeer(Guid peerId) return removed; } + internal bool HandleDisconnect(IPEndPoint peerEndpoint) + { + foreach (var item in roster) + { + if (item.Value.AssociatedEndpoint.Equals(peerEndpoint)) + { + RemovePeer(item.Key); + return true; + } + } + return false; + } + + internal bool HandleDisconnect(Guid ephemeralId) + { + foreach (var item in roster) + { + if (item.Value.EphemeralId.Equals(ephemeralId)) + { + RemovePeer(item.Key); + return true; + } + } + return false; + } + public void Clear() { PeerRegistered = null; diff --git a/NetworkLibrary/UDP/AsyncUdpServer.cs b/NetworkLibrary/UDP/AsyncUdpServer.cs index d1ba7fe..7a1c082 100644 --- a/NetworkLibrary/UDP/AsyncUdpServer.cs +++ b/NetworkLibrary/UDP/AsyncUdpServer.cs @@ -18,6 +18,8 @@ public class AsyncUdpServer:IDisposable public BytesRecieved OnBytesRecieved; public int ClientReceiveBufferSize = 65000; + public Action ClientDisconnected; + public int SocketReceiveBufferSize { get => receiveBufferSize; @@ -116,6 +118,7 @@ private void Received(object sender, SocketAsyncEventArgs e) if (e.SocketError != SocketError.Success) { StartReceiveSentinel(); + ClientDisconnected?.Invoke(e.RemoteEndPoint as IPEndPoint); e.Dispose(); return; } diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index 86e3a72..7603cfc 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.Tracing; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -139,7 +140,7 @@ public void PipeTest() byte[] data = new byte[12800000]; channel1.Start(); - channel1.SendAsync(data, 0, data.Length); + channel1.Send(data, 0, data.Length); Thread.Sleep(100); mre.Set(); @@ -149,8 +150,8 @@ void PeerConnected(IChannel channel_) { mre.WaitOne();//emulate bad syncronisation var channel = (TcpChannel)channel_; - channel.BytesReceived += Channel_BytesReceived; - channel.Disconnected += Disconnected; + channel.OnBytesReceived += Channel_BytesReceived; + channel.OnDisconnected += Disconnected; channel.Start(); } @@ -214,7 +215,7 @@ public void SecurePipeTest() for (int i = 0; i < iter; i++) { data[0] = (byte)i; - channel1.SendAsync(data, 0, data.Length); + channel1.Send(data, 0, data.Length); if(i%2 ==0) Thread.Sleep(1000); } @@ -224,8 +225,8 @@ void PeerConnected(IChannel channel_) { // mre.WaitOne();//emulate bad syncronisation var channel = (SecureTcpChannel)channel_; - channel.BytesReceived += Channel_BytesReceived; - channel.Disconnected += Disconnected; + channel.OnBytesReceived += Channel_BytesReceived; + channel.OnDisconnected += Disconnected; channel.Start(); } @@ -286,7 +287,7 @@ public void PipeTestUdp() void Cl2_PeerConnected(IChannel obj) { var udpChannel = (UdpChannel)obj; - udpChannel.OnMessageReceived += (b,o,c) => + udpChannel.OnBytesReceived += (b,o,c) => { received = c; tcs.SetResult(true); }; udpChannel.Start(); @@ -335,7 +336,7 @@ public void PipeTestUdpSecure() void Cl2_PeerConnected(IChannel obj) { var udpChannel = (SecureUdpChannel)obj; - udpChannel.OnMessageReceived += (b, o, c) => + udpChannel.OnBytesReceived += (b, o, c) => { received = c; if(++cnt == 2) @@ -513,6 +514,7 @@ void Cl1_MessageReceived(MessageEnvelope obj) [TestMethod] public void Timesync() { + using var server = ArrangeServer(); var cl1 = GetClient(); Thread.Sleep(1337); @@ -524,6 +526,12 @@ public void Timesync() double time1 = cl1.GetTime(); double time2 = cl2.GetTime(); double time3 = server.GetTime(); + + + Console.WriteLine(time1); + Console.WriteLine(time2); + Console.WriteLine(time3); + Assert.IsTrue(Math.Abs(time1 - time2) < 1); Assert.IsTrue(Math.Abs(time1 - time3) < 1); } @@ -560,7 +568,7 @@ public void UdpHolepunch() void Cl2_PeerConnected(IChannel obj) { var ch = (UdpChannel)obj; - ch.OnMessageReceived += Ch_OnMessageReceived; + ch.OnBytesReceived += Ch_OnMessageReceived; ch.Start(); } @@ -609,7 +617,7 @@ public void UdpHolepunchSecure() void Cl2_PeerConnected(IChannel obj) { var ch = (SecureUdpChannel)obj; - ch.OnMessageReceived += Ch_OnMessageReceived; + ch.OnBytesReceived += Ch_OnMessageReceived; ch.Start(); } @@ -650,13 +658,13 @@ public void TcpHolepunch() var data = new byte[1280]; data[0] = 1; - channel1.SendAsync(data, 0, data.Length); + channel1.Send(data, 0, data.Length); Thread.Sleep(100); void Cl2_PeerConnected(IChannel obj) { var ch = (TcpChannel)obj; - ch.BytesReceived += Ch_OnMessageReceived; + ch.OnBytesReceived += Ch_OnMessageReceived; ch.Start(); } @@ -699,13 +707,13 @@ public void TcpHolepunchSecure() var data = new byte[12800000]; data[0] = 1; - channel1.SendAsync(data, 0, data.Length); + channel1.Send(data, 0, data.Length); Thread.Sleep(100); void Cl2_PeerConnected(IChannel obj) { var ch = (SecureTcpChannel)obj; - ch.BytesReceived += Ch_OnMessageReceived; + ch.OnBytesReceived += Ch_OnMessageReceived; ch.Start(); } @@ -755,13 +763,13 @@ public void TestTcpChannelParallelSend() Parallel.For(0, iter, (i) => { - channel1.SendAsync(data, 0, data.Length); + channel1.Send(data, 0, data.Length); }); void Cl2_PeerConnected(IChannel obj) { var ch = (TcpChannel)obj; - ch.BytesReceived += Ch_OnMessageReceived; + ch.OnBytesReceived += Ch_OnMessageReceived; ch.Start(); } @@ -816,7 +824,7 @@ public void TestTcpChannelOrder() for (int i = 0; i < iter; i++) { data[0] = (byte)i; - channel1.SendAsync(data, 0, data.Length); + channel1.Send(data, 0, data.Length); if(i%10 == 0) Thread.Sleep(1); }; @@ -824,7 +832,7 @@ public void TestTcpChannelOrder() void Cl2_PeerConnected(IChannel obj) { var ch = (TcpChannel)obj; - ch.BytesReceived += Ch_OnMessageReceived; + ch.OnBytesReceived += Ch_OnMessageReceived; ch.Start(); } void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) From a3e2188c065aeb63d0ad1314bc55531c5bbf52a3 Mon Sep 17 00:00:00 2001 From: dogancan ozturk Date: Tue, 15 Apr 2025 23:08:57 +0200 Subject: [PATCH 22/27] time sync optimisation, receive optimisations --- Benchmarks/SslBenchmark/Program.cs | 2 +- .../Channels/Components/UdpChannelBase.cs | 16 +- .../DistributedP2P/Channels/TcpChannel.cs | 51 ++- .../DistributedP2P/Channels/UdpChannel.cs | 2 +- .../Client/DistributedLobbyClient.cs | 30 +- .../StateManagement/ClientConnectionState.cs | 23 +- .../Client/StateManagement/ClientPipeState.cs | 13 +- .../ClientUdpHolepunchState.cs | 2 +- .../Components/ConversationStateBase.cs | 2 + .../DistributedP2P/Components/NTPClient.cs | 106 +++++ .../DistributedP2P/Components/NTPServer.cs | 124 ++++++ .../DistributedP2P/Components/StateManager.cs | 3 +- .../DistributedP2P/Components/TimeSync.cs | 420 +++++++++++++----- .../Server/DistributedLobbyServer.cs | 45 +- .../Server/StateManagement/ServerPipeState.cs | 2 +- .../SimpleRelay/RelayService.cs | 2 + NetworkLibrary/TCP/Base/TcpSession.cs | 82 ++-- NetworkLibrary/UDP/AsyncUdpClient.cs | 2 - NetworkLibrary/UDP/AsyncUdpServer.cs | 32 +- .../UDP/Reliable/Components/SenderModule.cs | 1 + NetworkLibrary/Utils/Statistics.cs | 4 +- .../DistributedP2P/DistP2PServerclientTest.cs | 132 +++--- 22 files changed, 817 insertions(+), 279 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Components/NTPClient.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/NTPServer.cs diff --git a/Benchmarks/SslBenchmark/Program.cs b/Benchmarks/SslBenchmark/Program.cs index 41688c4..a5ff34e 100644 --- a/Benchmarks/SslBenchmark/Program.cs +++ b/Benchmarks/SslBenchmark/Program.cs @@ -114,7 +114,7 @@ private static void InitializeClients() int j = 0; foreach (var client1 in clients) { - client1.Connect("172.28.255.247", port); + client1.Connect("127.0.0.1", port); j++; } Console.WriteLine("All Clients Connected"); diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs index d6f2da4..f37fc2c 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs @@ -34,7 +34,7 @@ public void Send(byte[] data, int offset, int count) { udpSocket.SendTo(data, offset, count, SocketFlags.None, associatedEndpoint); } - catch(Exception e) + catch (Exception e) { Log($"{e.Message}\n{e.StackTrace}"); } @@ -61,7 +61,7 @@ private void Receive() private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) { - try + while (true) { if (e.SocketError != SocketError.Success) { @@ -81,12 +81,14 @@ private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) } } - Receive(); - } - catch (Exception ex) - { - Console.WriteLine($"Error in receive completion: {ex}"); + //Receive(); + + if (udpSocket.ReceiveFromAsync(receiveArgs)) + { + return; + } } + } private void ProcessReceivedData(byte[] buffer, int offset, int bytesTransferred, EndPoint remoteEndPoint) diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs index 4647775..8e4df28 100644 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs @@ -243,31 +243,38 @@ private void Receive() private void Received(object sender, SocketAsyncEventArgs e) { - if (e.SocketError != SocketError.Success) - { - ErrorAndEnd($"While receiving a socket error occured {e.SocketError}"); - CloseChannel(); - return; - } - else if (e.BytesTransferred == 0) - { - Log("0 bytes"); - CloseChannel(); - return; - } - totalBytesReceived += e.BytesTransferred; - try + while (true) { - HandleReceived(e.Buffer, e.Offset, e.BytesTransferred); - } - catch (Exception ex) - { - ErrorAndEnd(ex.Message + "\n" + ex.StackTrace); - return; - } + if (e.SocketError != SocketError.Success) + { + ErrorAndEnd($"While receiving a socket error occured {e.SocketError}"); + CloseChannel(); + return; + } + else if (e.BytesTransferred == 0) + { + Log("0 bytes"); + CloseChannel(); + return; + } - Receive(); + totalBytesReceived += e.BytesTransferred; + try + { + HandleReceived(e.Buffer, e.Offset, e.BytesTransferred); + } + catch (Exception ex) + { + ErrorAndEnd(ex.Message + "\n" + ex.StackTrace); + throw; + } + //Receive(); + if (connectedSocket.ReceiveAsync(receiveArgs)) + { + return; + } + } } protected virtual void HandleReceivedBytes(byte[] buffer, int offset, int count) diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs index 64cf184..2f3a588 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs @@ -39,7 +39,7 @@ public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) JumboUdp.MessageReceived = HandleMessage; SenderModule sender = new SenderModule(); - + //sender.SoftwindowTrim = 1f; sender.MaxSegmentSize = 1280; sender.MinWindowSize = 1280 * 2; diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 111f05b..e685ea7 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -20,7 +20,7 @@ namespace NetworkLibrary.DistributedP2P.Client { - public class DistributedLobbyClient : IDistributedConnection where S : ISerializer, new() + public class DistributedLobbyClient : IDistributedConnection, IDisposable where S : ISerializer, new() { IClientDbConnection clientDbConnector; IClientAuthenticationProvider clientAuthProvider; @@ -48,6 +48,9 @@ public bool IsConnected } private EndpointData serverEndpoint= new EndpointData(); + + bool isDisposed = false; + public DistributedLobbyClient(IClientDbConnection clientDbConnector, IClientAuthenticationProvider clientAuthProvider, X509Certificate2 certificate = null) @@ -62,6 +65,9 @@ public DistributedLobbyClient(IClientDbConnection clientDbConnector, public async Task ConnectAsync(string ip, int port) { + if (isDisposed) + throw new ObjectDisposedException(this.ToString()); + if (IsConnected) return true; @@ -70,6 +76,9 @@ public async Task ConnectAsync(string ip, int port) bool res = await sslClient.ConnectAsync(ip, port); if (res) { + serverEndpoint = new EndpointData(ip, port); + timeSync.SetEndpoint(serverEndpoint); + Guid conversationId = Guid.NewGuid(); var conState = new ClientConnectionState(conversationId, this, clientDbConnector, authToken); stateManager.RegisterState(conState); @@ -79,12 +88,11 @@ public async Task ConnectAsync(string ip, int port) if (conState.IsSuccesful) { - serverEndpoint = new EndpointData(ip, port); SessionId = conState.SessionId; DiscoveryServerEndpoint = new EndpointData(ip, conState.EDSPort); Console.WriteLine($"Connected to server {ip}:{port} with session {SessionId} and discovery port {conState.EDSPort}"); IsConnected = true; - timeSync.StartAutoTimeSync(5000); + timeSync.StartAutoTimeSync(); return true; } @@ -202,9 +210,6 @@ public Dictionary GetPeerList() return copy; } - - - public async Task OpenRelayChannel(Guid destinationPeer, ChannelInfo Info) { var pipeState = new ClientPipeState(Guid.NewGuid(), this, serverEndpoint, Info); @@ -368,6 +373,19 @@ private void HandleDisconnected() Disconnected?.Invoke(); } + public void Dispose() + { + if (isDisposed) + return; + isDisposed = true; + try + { + sslClient?.Dispose(); + timeSync?.Dispose(); + } + catch { } + + } } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs index 9f6be3a..f9ecceb 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs @@ -1,6 +1,4 @@ using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.P2P.Components.HolePunch; -using NetworkLibrary.P2P.Generic; using System; using System.Collections.Generic; using System.Threading.Tasks; @@ -14,12 +12,11 @@ internal class ClientConnectionState : ConversationStateBase private readonly IClientDbConnection clientDbConnector; private readonly IClientAuthenticationToken authToken; - - private TaskCompletionSource Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private TaskCompletionSource timeSyncComplete = new TaskCompletionSource(); public Guid SessionId { get; private set; } public int EDSPort { get; private set; } - public ClientConnectionState(Guid stateId, IDistributedConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken):base(stateId,20000) + public ClientConnectionState(Guid stateId, IDistributedConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken) : base(stateId, 20000) { this.connection = connection; this.clientDbConnector = clientDbConnector; @@ -43,17 +40,26 @@ public override void HandleMessage(MessageEnvelope message) HandleConnectionFail(message); break; - + } } - private void SyncTime(MessageEnvelope message) + private async void SyncTime(MessageEnvelope message) { - var task = connection.SyncTime().ContinueWith(t => + bool res = await timeSyncComplete.Task; + if (res) { MessageEnvelope msg = CreateEnvelope(); msg.Header = InternalConstants.SyncTime; connection.SendAsyncMessage(msg); + } + } + + private void SyncTime() + { + var task = connection.SyncTime().ContinueWith(t => + { + timeSyncComplete.TrySetResult(true); }); } @@ -76,6 +82,7 @@ public void Start() } connection.SendAsyncMessage(msg); + SyncTime(); // now server will authenticate after this // may ask additional data to link, if we are first timer // then succes or fail diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index e746b6e..d8d1a5f 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -32,7 +32,8 @@ public ClientPipeState(Guid stateId, IDistributedConnection connection,EndpointD public ClientPipeState(MessageEnvelope message,IDistributedConnection connection, EndpointData serverEndpoint) : base(message.MessageId) { this.connection = connection; - + this.serverEndpoint = serverEndpoint; + } public void Start(Guid destinationPeer) @@ -157,7 +158,7 @@ private void GenerateSharedSecret(string otherPublic) sharedSecret = df.CalculateSharedSecret(Convert.FromBase64String(otherPublic)); } - private async Task TryConnectWithTimeout(EndpointData endpoint, int timeout = 500) + private async Task TryConnectWithTimeout(EndpointData endpoint, int timeout = 5000) { var clientSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); @@ -212,12 +213,11 @@ private async Task ConnectAsync(Socket socket, IPEndPoint endPoint) return await tcs.Task; } - private async Task TokenExchange(Socket connectedSocket, byte[] token, int timeoutMs = 500) + private async Task TokenExchange(Socket connectedSocket, byte[] token, int timeoutMs = 5000) { try { - connectedSocket.SendTimeout = timeoutMs; - connectedSocket.ReceiveTimeout = timeoutMs; + int bytesSent = await connectedSocket.SendAsync(new ArraySegment(token), SocketFlags.None); if (bytesSent != token.Length) @@ -238,8 +238,7 @@ private async Task TokenExchange(Socket connectedSocket, byte[] token, int int bytesReceived = receiveTask.Result; - connectedSocket.SendTimeout = -1; - connectedSocket.ReceiveTimeout = -1; + return bytesReceived == 1; } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 7d44e93..a071f48 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -217,7 +217,7 @@ private async Task StartUdpSocket() selfLocalEp = (IPEndPoint)Socket.LocalEndPoint; - var remoteEp = await EndpointDiscoveryClient.GetUdpPublicEndpoint(Socket, discoveryServerendPoint.ToIpEndpoint(), 5000); + var remoteEp = await EndpointDiscoveryClient.GetUdpPublicEndpoint(Socket, discoveryServerendPoint.ToIpEndpoint(), 3000); if (remoteEp == null) { Log("Failed to get public endpoint"); diff --git a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs index 6e0078c..7966f4a 100644 --- a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs +++ b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs @@ -17,12 +17,14 @@ internal abstract class ConversationStateBase:IConversationState public string ErrorMessage { get; protected set; } protected readonly object cancellationMutex = new object(); + protected readonly int timeout; private TaskCompletionSource Completion; private int isComplete = 0; public ConversationStateBase(Guid stateId, int timeout = -1) { this.StateId = stateId; + this.timeout = timeout; Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (timeout > 0) diff --git a/NetworkLibrary/DistributedP2P/Components/NTPClient.cs b/NetworkLibrary/DistributedP2P/Components/NTPClient.cs new file mode 100644 index 0000000..57ddc53 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/NTPClient.cs @@ -0,0 +1,106 @@ +using NetworkLibrary.UDP; +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Components +{ + public class TimeResult + { + public double PreciseTime { get; set; } + public TimeSpan DateTimeOffset { get; set; } + public bool Succes { get; set; } + public double RTT { get; set; } + public double Delay { get; set; } // Store calculated delay + public double AsymmetryEstimate { get; set; } // Store estimated path asymmetry + } + + + internal class NTPClient:IDisposable + { + + struct TimeData + { + public double ServersTime; + public double ArrivalTime; + } + + + ConcurrentDictionary> pending = new ConcurrentDictionary>(); + byte ctr = 0; + private AsyncUdpClient udpClient; + private Stopwatch clientClock; + + public NTPClient(string ip, int port, Stopwatch clientClock) + { + udpClient = new AsyncUdpClient(); + udpClient.OnBytesRecieved += HandleReceived; + udpClient.Connect(ip, port); + + this.clientClock = clientClock; + } + + private void HandleReceived(byte[] bytes, int offset, int count) + { + var arrivalTime = clientClock.Elapsed.TotalMilliseconds; + byte num = bytes[offset++]; + if (pending.TryRemove(num, out var tcs)) + { + double time = PrimitiveEncoder.ReadFixedDouble(bytes, offset); + tcs.SetResult(new TimeData() { ServersTime = time, ArrivalTime = arrivalTime }); + } + } + + public async Task GetServerTime(int delayMs) + { + var buff = new byte[1]; + var pollNum = ctr++; + buff[0] = pollNum; + var tcs = new TaskCompletionSource(); + pending[pollNum] = tcs; + + var t1 = clientClock.Elapsed.TotalMilliseconds; + udpClient.SendAsync(buff); + + var delayTask = Task.Delay(delayMs); + if (await Task.WhenAny(delayTask, tcs.Task).ConfigureAwait(false)== tcs.Task) + { + + var serverTime = tcs.Task.Result; + var t2 = serverTime.ServersTime; + var t3 = t2 + 0.005; + var t4 = serverTime.ArrivalTime; + + + var delay = (t4 - t1) - (t3 - t2); + var offset = ((t2 - t1) + (t3 - t4)) / 2; + + return new TimeResult() + { + PreciseTime = offset, + Succes = true, + RTT = t4 - t1, + Delay = delay, + }; + } + else + { + pending.TryRemove(pollNum, out _); + return new TimeResult() + { + Succes = false + }; + } + + + + + } + + public void Dispose() + { + udpClient?.Dispose(); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/NTPServer.cs b/NetworkLibrary/DistributedP2P/Components/NTPServer.cs new file mode 100644 index 0000000..6f0e29e --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/NTPServer.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace NetworkLibrary.DistributedP2P.Components +{ + internal class NTPServer : IDisposable + { + Socket udpListener; + Stopwatch sw; + + [ThreadStatic] + static byte[] buff = new byte[9]; + + int IsDisposed = 0; + + public NTPServer(int port, Stopwatch clock) + { + udpListener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + udpListener.Bind(new IPEndPoint(IPAddress.Any, port)); + sw = clock; + } + + + + internal void Start() + { + for (int i = 0; i < Environment.ProcessorCount; i++) + { + Receive(GetReceiveArgs()); + } + } + + private void Receive(SocketAsyncEventArgs sea) + { + if (!udpListener.ReceiveFromAsync(sea)) + { + ThreadPool.UnsafeQueueUserWorkItem(_ => Received(null, sea), null); + } + } + + private void Received(object _, SocketAsyncEventArgs e) + { + while (true) + { + try + { + + if (e.SocketError != SocketError.Success) + { + e.Dispose(); + Receive(GetReceiveArgs()); + return; + } + + var time = sw.Elapsed.TotalMilliseconds; + var buff = GetBuffer(); + buff[0] = e.Buffer[0]; + PrimitiveEncoder.WriteFixedDouble(buff, 1, time); + + udpListener.SendTo(buff, 0, buff.Length, SocketFlags.None, e.RemoteEndPoint); + + ((IPEndPoint)e.RemoteEndPoint).Address = IPAddress.Any; + ((IPEndPoint)e.RemoteEndPoint).Port = 0; + if (udpListener.ReceiveFromAsync(e)) + { + return; + } + } + catch (Exception ex) + { + if (Interlocked.CompareExchange(ref IsDisposed, 0, 0) == 0) + { + Console.WriteLine(ex.Message + "\n" + ex.StackTrace); + e.Dispose(); + Receive(GetReceiveArgs()); + return; + } + else + { + e.Dispose(); + return; + } + } + + } + + + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte[] GetBuffer() + { + if (buff == null) + { + buff = new byte[9]; + } + return buff; + } + + private SocketAsyncEventArgs GetReceiveArgs() + { + + var recArgs = new SocketAsyncEventArgs(); + recArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0); + recArgs.Completed += Received; + recArgs.SetBuffer(new byte[65555], 0, 65555); + + + return recArgs; + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref IsDisposed, 1, 0) == 0) + { + udpListener.Dispose(); + } + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/StateManager.cs b/NetworkLibrary/DistributedP2P/Components/StateManager.cs index f33230b..40f0a51 100644 --- a/NetworkLibrary/DistributedP2P/Components/StateManager.cs +++ b/NetworkLibrary/DistributedP2P/Components/StateManager.cs @@ -40,8 +40,9 @@ public bool HandleMessage(MessageEnvelope message) { state.HandleMessage(message); } - catch + catch (Exception e) { + Console.WriteLine(e); state.Cancel(); UnregisterState(stateId); } diff --git a/NetworkLibrary/DistributedP2P/Components/TimeSync.cs b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs index ee3b207..fd1e614 100644 --- a/NetworkLibrary/DistributedP2P/Components/TimeSync.cs +++ b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs @@ -7,40 +7,55 @@ using System.Threading.Tasks; using NetworkLibrary.Utils; using NetworkLibrary.P2P; +using static NetworkLibrary.DistributedP2P.Components.TimeSync; +using NetworkLibrary.P2P.Components.HolePunch; namespace NetworkLibrary.DistributedP2P.Components { - internal class TimeSync + internal class TimeSync:IDisposable { private Stopwatch clientClock = Stopwatch.StartNew(); private double timeOffset; private TimeSpan timeOffsetd; - private long syncCount = 0; - - private List timesHistory = new List(); - private List timesHistoryd = new List(); + private NTPClient client; private readonly SemaphoreSlim asyncLock = new SemaphoreSlim(1, 1); IDistributedConnection connection; + TimeSyncer timeSync = new TimeSyncer(); + bool cancel = false; + int maxPeriod = 30000; + Queue offsetHistory = new Queue(); public TimeSync(IDistributedConnection connection) { this.connection = connection; + } - AsyncDispatcher timesyncOperation; - public void StartAutoTimeSync(int periodMs, bool usePTP = false) + public void SetEndpoint(EndpointData endpointData) { - Interlocked.Exchange(ref timesyncOperation, new AsyncDispatcher())?.Abort(); + string ip = IPHelper.Byte2Sting(endpointData.Ip); + var cl = new NTPClient(ip, endpointData.Port, clientClock); + Interlocked.Exchange(ref client, cl)?.Dispose(); + } + + public async void StartAutoTimeSync() + { + int failureCount = 0; - timesyncOperation.LoopPeriodicTask(async () => + cancel=false; + int PeriodLocal = 2000; + while (!cancel) { try { + await Task.Delay(PeriodLocal); + if (cancel) + return; - bool result = await SyncTime(usePTP).ConfigureAwait(false); - if ( result == false) + bool result = await SyncTime().ConfigureAwait(false); + if (result == false) { if (++failureCount > 2) { @@ -52,19 +67,22 @@ public void StartAutoTimeSync(int periodMs, bool usePTP = false) { failureCount = 0; } - + + PeriodLocal = Math.Min(PeriodLocal * 2, maxPeriod); + } catch (Exception e) { StopAutoTimeSync(); + return; } - - }, periodMs).ConfigureAwait(false); + } } public void StopAutoTimeSync() { - timesyncOperation?.Abort(); + Console.WriteLine("SyncStopped"); + cancel = true; ClearData(); } @@ -73,10 +91,7 @@ private async void ClearData() try { await asyncLock.WaitAsync().ConfigureAwait(false); - //not sure - syncCount = 0; - timesHistory.Clear(); - timesHistoryd.Clear(); + offsetHistory.Clear(); } catch { } finally @@ -86,72 +101,44 @@ private async void ClearData() } - public async Task SyncTime(bool usePtp = false) + public async Task SyncTime() { try { await asyncLock.WaitAsync().ConfigureAwait(false); - //12 first, 3,2,1 int sampleSize = 12; - var sCnt = Interlocked.CompareExchange(ref syncCount, 0, 0); - if (sCnt > 50) - sampleSize = 4; - - var localHistory = new List(); - var localHistoryd = new List(); - for (int i = 0; i < sampleSize; i++) + var localHistory = new List(); + while(localHistory.Count x.RTT).Take((sampleSize / 2)).Select(x => x.PreciseTime).ToList(); - var bestSamplesd = localHistoryd.OrderBy(x => x.RTT).Take((sampleSize / 2)).Select(x=>x.DateTimeOffset).ToList(); - //var bestSamples = localHistory; - //var bestSamplesd = localHistoryd; - - timesHistory.AddRange(bestSamples); - timesHistoryd.AddRange(bestSamplesd); - - - if (timesHistory.Count < 4) - return false; - var times = Statistics.FilterOutliers(timesHistory); - var timesd = Statistics.FilterOutliers(timesHistoryd); + var filteredSample = timeSync.GetBestTimeOffset(localHistory, sampleSize); - if (timesHistory.Count > 100) + offsetHistory.Enqueue(filteredSample.PreciseTime); + if (offsetHistory.Count >= 4) { - timesHistory = timesHistory.Skip(10).ToList(); - timesHistoryd = timesHistoryd.Skip(10).ToList(); - } - - double average = times.Sum() / times.Count(); - double averaged = timesd.Sum(ts => ts.Ticks) / timesd.Count(); - timeOffset = average; - - if (sCnt > 0) - { - var calculatedAvg = TimeSpan.FromTicks((long)averaged); - if (timeOffsetd < calculatedAvg) - { - timeOffsetd = calculatedAvg; - } + var result= Statistics.FilterOutliers(offsetHistory); + timeOffset = result.Average(); } else { - timeOffsetd = TimeSpan.FromTicks((long)averaged); + timeOffset = offsetHistory.Average(); } - Interlocked.Increment(ref syncCount); + if (offsetHistory.Count>5) + offsetHistory.Dequeue(); + + if(filteredSample.DateTimeOffset > timeOffsetd) + timeOffsetd = filteredSample.DateTimeOffset; return true; } @@ -162,92 +149,309 @@ public async Task SyncTime(bool usePtp = false) } - class TimeResult { public double PreciseTime; public bool Succes; public DateTime ServerUTC; public TimeSpan DateTimeOffset; public double RTT; } + private Task GetOffsetNTPUdp() + { + return client.GetServerTime(2000); + } + + + + - private async Task GetOffsetNTP() + private async Task GetOffsetNTPTcp() { var msg = new MessageEnvelope() { Header = Constants.TimeSync, IsInternal = true, }; - var now = clientClock.Elapsed.TotalMilliseconds; - var nowd = DateTime.UtcNow; + + var t1 = clientClock.Elapsed.TotalMilliseconds; + var t1d = DateTime.UtcNow; var response = await connection.SendMessageAndWaitResponse(msg).ConfigureAwait(false); + + var t4 = clientClock.Elapsed.TotalMilliseconds; + var t4d = DateTime.UtcNow; + if (response.Header != MessageEnvelope.RequestTimeout) { - var now1 = clientClock.Elapsed.TotalMilliseconds; - var now1d = DateTime.UtcNow; + var t2 = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); + var t3 = t2 + 0.005; + + var t2d = response.TimeStamp; + var t3d = response.TimeStamp; - var serverTime = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); - var serverTimed = response.TimeStamp; + // Calculate delay and offset using the full NTP formula + var delay = (t4 - t1) - (t3 - t2); + var offset = ((t2 - t1) + (t3 - t4)) / 2; - var timeOffset = ((serverTime - now) + (serverTime - now1)) / 2; - var timeOffsetd = ((serverTimed - nowd) + (serverTimed - now1d)).TotalMilliseconds / 2; - TimeSpan offd = TimeSpan.FromMilliseconds(timeOffsetd); + var delayd = ((t4d - t1d) - (t3d - t2d)).TotalMilliseconds; + var offsetd = ((t2d - t1d) + (t3d - t4d)).TotalMilliseconds / 2; - return new TimeResult() { PreciseTime = timeOffset, DateTimeOffset = offd, Succes = true, RTT = now1-now }; + TimeSpan offd = TimeSpan.FromMilliseconds(offsetd); + + return new TimeResult() + { + PreciseTime = offset, + DateTimeOffset = offd, + Succes = true, + RTT = t4 - t1, + Delay = delay, + }; } - return new TimeResult(); + return new TimeResult(); } - private async Task GetOffsetPTP() + public double GetTime() { + return clientClock.Elapsed.TotalMilliseconds + timeOffset; + } - var t1 = await GetServerTime(); - if (t1.Succes) - { - var t2 = clientClock.Elapsed.TotalMilliseconds; - var t2d = DateTime.UtcNow; + public DateTime GetDateTime() + { + return DateTime.UtcNow.Add(timeOffsetd); + } - var t3 = t2; - var t3d = t2d; + public void Dispose() + { + client.Dispose(); + } + } - var t4 = await GetServerTime(); - var trtt = clientClock.Elapsed.TotalMilliseconds; + internal class TimeSyncer + { + public List timeHistory = new List(); - if (t4.Succes) + private TimeResult GetMinRtt(List timeHistory) + { + double min = int.MaxValue; + TimeResult result = null; + foreach (var item in timeHistory) + { + if (item.RTT < min) { - double offset = (((t4.PreciseTime - t3) - (t2 - t1.PreciseTime)) / 2); - double offsetd = (((t4.ServerUTC - t3d) - (t2d - t1.ServerUTC)).TotalMilliseconds / 2); - TimeSpan offd = TimeSpan.FromMilliseconds(offsetd); - return new TimeResult() { PreciseTime = offset, DateTimeOffset = offd, Succes = true,RTT = t2- trtt }; + result = item; + min = item.RTT; } - else - return new TimeResult(); } - else - return new TimeResult(); - + return result; } - private async Task GetServerTime() + + public TimeResult GetBestTimeOffset(List samples, int sampleSize) { - var msg = new MessageEnvelope() + if (samples.Count < 4) return GetMinRtt(samples); + + //var lowRttSamples = samples.OrderBy(x => x.RTT).Take(sampleSize / 2); + + var lowRttSamples = GetEnhancedFilteredSamples(samples, sampleSize / 2); + + + // Calculate the median delay + var medianDelay = GetMedian(lowRttSamples.Select(s => s.Delay)); + + // Calculate asymmetry estimates for each sample + foreach (var sample in lowRttSamples) { - Header = Constants.TimeSync, - IsInternal = true, + // Positive values indicate forward path > return path + sample.AsymmetryEstimate = sample.Delay - medianDelay; + } + + // Filter outliers based on asymmetry estimates + var asymmetryValues = lowRttSamples.Select(s => s.AsymmetryEstimate); + var filteredAsymmetry = Statistics.FilterOutliers(asymmetryValues); + + // Only keep samples with acceptable asymmetry estimates + var finalSamples = lowRttSamples + .Where(s => filteredAsymmetry.Contains(s.AsymmetryEstimate)); + + + // Calculate weighted average based on RTT (lower RTT = higher weight) + double totalWeight = 0; + double weightedSumPrecise = 0; + double weightedSumDateTime = 0; + + foreach (var sample in finalSamples) + { + // Use inverse of RTT as weight + double weight = 1.0 / sample.RTT; + totalWeight += weight; + weightedSumPrecise += weight * sample.PreciseTime; + weightedSumDateTime += weight * sample.DateTimeOffset.TotalMilliseconds; + } + + return new TimeResult + { + PreciseTime = weightedSumPrecise / totalWeight, + DateTimeOffset = TimeSpan.FromMilliseconds(weightedSumDateTime / totalWeight), + Succes = true, + RTT = finalSamples.Average(s => s.RTT) }; - var response = await connection.SendMessageAndWaitResponse(msg); - if (response.Header != MessageEnvelope.RequestTimeout) + } + + private double GetMedian(IEnumerable values) + { + var sortedValues = values.OrderBy(v => v).ToList(); + int count = sortedValues.Count; + + if (count % 2 == 0) { - var serverTime = PrimitiveEncoder.ReadFixedDouble(response.Payload, response.PayloadOffset); - return new TimeResult() { PreciseTime = serverTime, ServerUTC = response.TimeStamp, Succes = true }; + // Even count + return (sortedValues[count / 2 - 1] + sortedValues[count / 2]) / 2; } - return new TimeResult(); + else + { + // Odd count + return sortedValues[count / 2]; + } + } + + + + public IEnumerable GetEnhancedFilteredSamples(List samples, int desiredCount = 6) + { + if (samples.Count <= desiredCount) return samples; + + // Phase 1: Initial RTT filtering to eliminate extreme outliers + var rttValues = samples.Select(s => s.RTT).ToList(); + var filteredRtt = Statistics.FilterOutliers(rttValues).ToList(); + var initialFiltered = samples.Where(s => filteredRtt.Contains(s.RTT)); + if (initialFiltered.Count() <= desiredCount) return initialFiltered; + + // Phase 2: Calculate clustering of offset values + var clusters = ClusterSamples(initialFiltered); + + // Phase 3: Select best cluster based on size and consistency + var bestCluster = SelectBestCluster(clusters); + + // If best cluster has enough samples, use it + if (bestCluster.Count >= desiredCount) + { + return bestCluster.OrderBy(s => s.RTT).Take(desiredCount); + } + + // Otherwise, use hybrid approach combining RTT and consistency + return RankSamplesByQuality(initialFiltered).Take(desiredCount); } - public double GetTime() + // Cluster samples based on their offset values + private List> ClusterSamples(IEnumerable samples) { - return clientClock.Elapsed.TotalMilliseconds + timeOffset; + // Simple clustering algorithm based on offset proximity + const double clusterThreshold = 0.5; // ms - samples within 0.5ms of each other form a cluster + + var clusters = new List>(); + var remaining = new List(samples); + + while (remaining.Count > 0) + { + var currentSample = remaining[0]; + remaining.RemoveAt(0); + + var cluster = new List { currentSample }; + + // Find all samples close enough to this one to form a cluster + for (int i = remaining.Count - 1; i >= 0; i--) + { + if (Math.Abs(remaining[i].PreciseTime - currentSample.PreciseTime) <= clusterThreshold) + { + cluster.Add(remaining[i]); + remaining.RemoveAt(i); + } + } + + clusters.Add(cluster); + } + + return clusters; } - public DateTime GetDateTime() + // Select the best cluster based on size and internal consistency + private List SelectBestCluster(List> clusters) { - return DateTime.UtcNow.Add(timeOffsetd); + if (clusters.Count == 0) return new List(); + if (clusters.Count == 1) return clusters[0]; + + // Score each cluster + var scoredClusters = clusters.Select(cluster => new + { + Cluster = cluster, + // Size is important, but so is consistency of offset values + Size = cluster.Count, + Consistency = CalculateConsistency(cluster), + AvgRTT = cluster.Average(s => s.RTT) + }); + + // Prioritize larger clusters with better consistency and lower RTT + return scoredClusters + .OrderByDescending(c => c.Size * 5 + c.Consistency * 3 - c.AvgRTT) + .First() + .Cluster; + } + + // Calculate consistency score (lower is better) + private double CalculateConsistency(List cluster) + { + if (cluster.Count <= 1) return 0; + + var offsets = cluster.Select(s => s.PreciseTime); + double avg = offsets.Average(); + double variance = offsets.Sum(o => Math.Pow(o - avg, 2)) / cluster.Count; + + // Lower variance means higher consistency + return 1.0 / (1.0 + variance); + } + + // Rank samples by multiple quality indicators + private IEnumerable RankSamplesByQuality(IEnumerable samples) + { + int sampleCount = samples.Count(); + if (sampleCount <= 1) return samples; + + // Calculate average and standard deviation of all offset values + var offsets = samples.Select(s => s.PreciseTime); + double avgOffset = offsets.Average(); + double stdDev = Math.Sqrt(offsets.Sum(o => Math.Pow(o - avgOffset, 2)) / sampleCount); + + // Score each sample using multiple factors + var scoredSamples = samples.Select(sample => new + { + Sample = sample, + // How close to the average offset (lower is better) + OffsetDeviation = Math.Abs(sample.PreciseTime - avgOffset) / stdDev, + // RTT (lower is better) + NormalizedRTT = sample.RTT / samples.Average(s => s.RTT), + // If available, path asymmetry (lower is better) + AsymmetryScore = sample.AsymmetryEstimate != 0 ? + Math.Abs(sample.AsymmetryEstimate) / samples.Average(s => Math.Abs(s.AsymmetryEstimate)) : 1 + }); + + // Calculate composite score (lower is better) + var rankedSamples = scoredSamples + .Select(s => new + { + s.Sample, + Score = s.OffsetDeviation * 2 + s.NormalizedRTT + s.AsymmetryScore + }) + .OrderBy(s => s.Score) + .Select(s => s.Sample); + + + return rankedSamples; } + + + } + } + + + + + + + diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 4bc2056..8b8e8fb 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -4,6 +4,7 @@ using NetworkLibrary.DistributedP2P.SimpleRelay; using NetworkLibrary.MessageProtocol; using NetworkLibrary.P2P; +using NetworkLibrary.P2P.Components; using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.Utils; using System; @@ -30,7 +31,7 @@ public class ServerParameters public int UdpPort; public int DiscoveryServerPort; } - public class DistributedLobbyServerBase : IDistributedConnection, IDisposable where S : ISerializer, new() + public class DistributedLobbyServerBase: IDistributedConnection, IDisposable { public readonly int SSlPort; public readonly int TcpPort; @@ -39,7 +40,7 @@ public class ServerParameters private X509Certificate2 serverCertificate; - SecureMessageServer sslServer; + SecureMessageServer sslServer; IAuthenticator authenticator; @@ -52,7 +53,7 @@ public class ServerParameters RelayService relayService; RoomManager roomManager = new RoomManager(); - + NTPServer ntpServer; private byte[] serverKey = new byte[16]; EndpointDiscoveryServer discoveryServer; @@ -73,8 +74,10 @@ public void StartServer() { serverClock.Start(); - sslServer = new SecureMessageServer(SSlPort, serverCertificate); + sslServer = new SecureMessageServer(SSlPort, serverCertificate); + ntpServer = new NTPServer(SSlPort, serverClock); + ntpServer.Start(); relayService = new RelayService(TcpPort, UdpPort); @@ -223,24 +226,31 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) break; case Constants.TimeSync: - //if (ctr++ % 2 == 0) - { - //Thread.Sleep(80); - } - byte[] time = new byte[8]; - message.Payload = time; - PrimitiveEncoder.WriteFixedDouble(time, 0, serverClock.Elapsed.TotalMilliseconds); - message.TimeStamp = DateTime.UtcNow; - //if (ctr % 3 == 0) - { - //Thread.Sleep(100); - } - SendAsyncMessage(clientId, message); + HandleTimeSync(clientId, message); break; } } + + private void HandleTimeSync(Guid clientId, MessageEnvelope message) + { + ////if (ctr++ % 2 == 0) + //{ + // await Task.Delay(r.Next(50, 300)); + //} + byte[] time = new byte[8]; + message.Payload = time; + PrimitiveEncoder.WriteFixedDouble(time, 0, serverClock.Elapsed.TotalMilliseconds); + message.TimeStamp = DateTime.UtcNow; + ////if (ctr % 3 == 0) + //{ + // await Task.Delay(r.Next(50, 300)); + //} + SendAsyncMessage(clientId, message); + } + int ctr = 0; + Random r = new Random(42); private bool CreateRoom(string roomName, string roomPassword, RoomProtocol protocol) { if (roomManager.TryCreateRoom(roomName, roomPassword, out Guid RoomId)) @@ -313,6 +323,7 @@ public void Dispose() sslServer.ShutdownServer(); relayService.Dispose(); discoveryServer.Dispose(); + ntpServer.Dispose(); } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs index c66c8b0..24b3860 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs @@ -105,7 +105,7 @@ private Task ObtainPipeToken() byte[] token = piper.GetPipeToken(isTcpPipe); PipeData data = new PipeData(); data.Token = token; - data.PipeEndpoints = new List() { new EndpointData("127.0.0.1", isTcpPipe ? 20011 : 20012) }; + data.PipeEndpoints = new List() { new EndpointData("0.0.0.0", isTcpPipe ? 20011 : 20012) }; return Task.FromResult(data); diff --git a/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs index b45f0ff..94d575f 100644 --- a/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs @@ -201,6 +201,7 @@ private void ManagePipeToken(Guid ephemeralId, byte[] bytes, int offset, int cou { if (VerifyToken(storage.Token, token.Expiration)) { + Console.WriteLine("TokenVerified"); pipeState.RegisterClient(ephemeralId); if (pipeState.IsComplete()) @@ -213,6 +214,7 @@ private void ManagePipeToken(Guid ephemeralId, byte[] bytes, int offset, int cou } else { + Console.WriteLine("Token Rejected"); RemoveTcpClient(ephemeralId); } } diff --git a/NetworkLibrary/TCP/Base/TcpSession.cs b/NetworkLibrary/TCP/Base/TcpSession.cs index 277e5a3..7f47140 100644 --- a/NetworkLibrary/TCP/Base/TcpSession.cs +++ b/NetworkLibrary/TCP/Base/TcpSession.cs @@ -47,9 +47,9 @@ internal class TcpSession : IAsyncSession private long totalMessageReceived = 0; private long totalMsgReceivedPrev; private long totalMSgSentPrev; -//#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER -// Memory receiveMemory; -//#endif + //#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER + // Memory receiveMemory; + //#endif #endregion public IPEndPoint RemoteEndpoint => (IPEndPoint)sessionSocket.RemoteEndPoint; @@ -73,7 +73,7 @@ public virtual void StartSession() InitialiseSendArgs(); InitialiseReceiveArgs(); messageBuffer = CreateMessageQueue(); - ThreadPool.UnsafeQueueUserWorkItem((e) => Receive(),null); + ThreadPool.UnsafeQueueUserWorkItem((e) => Receive(), null); } protected virtual void ConfigureSocket() @@ -148,37 +148,51 @@ protected virtual void Receive() private void BytesRecieved(object sender, SocketAsyncEventArgs e) { - if (IsSessionClosing()) + while (true) { - ReleaseReceiveResourcesIdempotent(); - return; - } + if (IsSessionClosing()) + { + ReleaseReceiveResourcesIdempotent(); + return; + } - if (e.SocketError != SocketError.Success) - { - HandleError(e, "while recieving from "); - Disconnect(); - ReleaseReceiveResourcesIdempotent(); - return; - } - else if (e.BytesTransferred == 0) - { - Disconnect(); - ReleaseReceiveResourcesIdempotent(); - return; - } - totalBytesReceived += e.BytesTransferred; - try - { - HandleReceived(e.Buffer, e.Offset, e.BytesTransferred); - } - catch (Exception ex) - { - MiniLogger.Log(MiniLogger.LogLevel.Error, ex.Message + "\n" + ex.StackTrace); - EndSession(); - } - Receive(); + if (e.SocketError != SocketError.Success) + { + HandleError(e, "while recieving from "); + Disconnect(); + ReleaseReceiveResourcesIdempotent(); + return; + } + else if (e.BytesTransferred == 0) + { + Disconnect(); + ReleaseReceiveResourcesIdempotent(); + return; + } + totalBytesReceived += e.BytesTransferred; + try + { + HandleReceived(e.Buffer, e.Offset, e.BytesTransferred); + } + catch (Exception ex) + { + MiniLogger.Log(MiniLogger.LogLevel.Error, ex.Message + "\n" + ex.StackTrace); + EndSession(); + } + //Receive(); + try + { + ClientRecieveEventArg.SetBuffer(0, ClientRecieveEventArg.Buffer.Length); + if (sessionSocket.ReceiveAsync(ClientRecieveEventArg)) + { + return; + } + } + catch (Exception ex) + when (ex is ObjectDisposedException || ex is NullReferenceException) + { ReleaseReceiveResourcesIdempotent(); } + } } protected virtual void HandleReceived(byte[] buffer, int offset, int count) @@ -438,7 +452,7 @@ private void SendComplete(object ignored, SocketAsyncEventArgs e) { if (!IsSessionClosing()) { - MiniLogger.Log(MiniLogger.LogLevel.Error,"Error on sent callback tcp session"+ ex.Message); + MiniLogger.Log(MiniLogger.LogLevel.Error, "Error on sent callback tcp session" + ex.Message); EndSession(); } } @@ -531,7 +545,7 @@ protected virtual void ReleaseReceiveResources() BufferPool.ReturnBuffer(recieveBuffer); } catch { } - + } protected void DcAndDispose() diff --git a/NetworkLibrary/UDP/AsyncUdpClient.cs b/NetworkLibrary/UDP/AsyncUdpClient.cs index 67d9305..d3688d5 100644 --- a/NetworkLibrary/UDP/AsyncUdpClient.cs +++ b/NetworkLibrary/UDP/AsyncUdpClient.cs @@ -57,8 +57,6 @@ public AsyncUdpClient() clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true); - //clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - //clientSocket.SetSocketOption(SocketOptionLevel.Udp, SocketOptionName.PacketInformation, true); int dscpValue = 40; // Critical services byte tos = (byte)(dscpValue << 2); // Shift left by 2 bits diff --git a/NetworkLibrary/UDP/AsyncUdpServer.cs b/NetworkLibrary/UDP/AsyncUdpServer.cs index 7a1c082..eb10cbe 100644 --- a/NetworkLibrary/UDP/AsyncUdpServer.cs +++ b/NetworkLibrary/UDP/AsyncUdpServer.cs @@ -112,21 +112,31 @@ private void Receive(SocketAsyncEventArgs e) } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void Received(object sender, SocketAsyncEventArgs e) { - if (e.SocketError != SocketError.Success) + while (true) { - StartReceiveSentinel(); - ClientDisconnected?.Invoke(e.RemoteEndPoint as IPEndPoint); - e.Dispose(); - return; - } + if (e.SocketError != SocketError.Success) + { + StartReceiveSentinel(); + ClientDisconnected?.Invoke(e.RemoteEndPoint as IPEndPoint); + e.Dispose(); + return; + } - HandleMessage(e); - e.RemoteEndPoint = serverEndpoint; - e.SetBuffer(0, ClientReceiveBufferSize); - Receive(e); + HandleMessage(e); + e.RemoteEndPoint = serverEndpoint; + e.SetBuffer(0, ClientReceiveBufferSize); + //Receive(e); + try + { + if (ServerSocket.ReceiveFromAsync(e)) + { + return; + } + } + catch (Exception ex) when (ex is ObjectDisposedException) { } + } } private void HandleMessage(SocketAsyncEventArgs e) diff --git a/NetworkLibrary/UDP/Reliable/Components/SenderModule.cs b/NetworkLibrary/UDP/Reliable/Components/SenderModule.cs index 1be7116..5c9a74d 100644 --- a/NetworkLibrary/UDP/Reliable/Components/SenderModule.cs +++ b/NetworkLibrary/UDP/Reliable/Components/SenderModule.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace NetworkLibrary.UDP.Reliable.Components { diff --git a/NetworkLibrary/Utils/Statistics.cs b/NetworkLibrary/Utils/Statistics.cs index 570a74e..56f4a3c 100644 --- a/NetworkLibrary/Utils/Statistics.cs +++ b/NetworkLibrary/Utils/Statistics.cs @@ -7,9 +7,9 @@ namespace NetworkLibrary.Utils { public class Statistics { - public static IEnumerable FilterOutliers(List times) + public static IEnumerable FilterOutliers(IEnumerable times) { - if (times == null || times.Count < 4) + if (times == null || times.Count() < 4) { throw new ArgumentException("The list must contain at least 4 elements."); } diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index 7603cfc..4ad493e 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -3,10 +3,13 @@ using NetworkLibrary.DistributedP2P.Channels; using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Server; using Protobuff.Components.Serialiser; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Tracing; using System.Linq; using System.Threading; @@ -14,6 +17,7 @@ namespace UnitTests.DistributedP2P { + class ClientAuthToken : IClientAuthenticationToken { public string Token { get; } = "1234"; @@ -82,8 +86,10 @@ public byte[] GetClientPublicData() [TestClass] public class DistP2PServerclientTest { + private const string ServerIp = "127.0.0.1"; + //private const string ServerIp = "35.159.121.192"; - private static DistributedLobbyServerBase ArrangeServer() + private static DistributedLobbyServerBase ArrangeServer() { var dep = new Dependencies() { @@ -99,7 +105,7 @@ private static DistributedLobbyServerBase ArrangeServer() DiscoveryServerPort = 20013, }; - var server = new DistributedLobbyServerBase(dep, param); + var server = new DistributedLobbyServerBase(dep, param); server.StartServer(); return server; } @@ -109,15 +115,15 @@ public void ConnectTest() { DistributedLobbyClient distributedLobbyClient = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); using var server = ArrangeServer(); - var res = distributedLobbyClient.ConnectAsync("127.0.0.1", 20010).Result; + var res = distributedLobbyClient.ConnectAsync(ServerIp, 20010).Result; } [TestMethod] public void PipeTest() { - DistributedLobbyClient distributedLobbyClient = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); - DistributedLobbyClient distributedLobbyClient2 = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + DistributedLobbyClient cl = GetClient(); + DistributedLobbyClient cl2 = GetClient(); TaskCompletionSource tcs = new TaskCompletionSource(); ManualResetEvent mre = new ManualResetEvent(false); @@ -125,19 +131,21 @@ public void PipeTest() using var server = ArrangeServer(); - var res = distributedLobbyClient.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = distributedLobbyClient2.ConnectAsync("127.0.0.1", 20010).Result; - distributedLobbyClient2.PeerConnected += PeerConnected; + cl2.PeerConnected += PeerConnected; + + var res = cl.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; + var info = new ChannelInfo(); info.ChannelName = "Test"; info.ChannelType = ChannelType.Tcp; - var channel1 = (TcpChannel)distributedLobbyClient.OpenRelayChannel(distributedLobbyClient2.SessionId, info).Result; + var channel1 = (TcpChannel)cl.OpenRelayChannel(cl2.SessionId, info).Result; Assert.IsNotNull(channel1); - byte[] data = new byte[12800000]; + byte[] data = new byte[1280000]; channel1.Start(); channel1.Send(data, 0, data.Length); @@ -182,19 +190,19 @@ public void SecurePipeTest() TaskCompletionSource tcs = new TaskCompletionSource(); - Task.Delay(12000).ContinueWith(t => + Task.Delay(24000).ContinueWith(t => { tcs.TrySetResult(false); }); ManualResetEvent mre = new ManualResetEvent(false); - List received = new List(); + var received = new ConcurrentQueue(); int iter = 20; using var server = ArrangeServer(); - var res = distributedLobbyClient.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = distributedLobbyClient2.ConnectAsync("127.0.0.1", 20010).Result; + var res = distributedLobbyClient.ConnectAsync(ServerIp, 20010).Result; + var res2 = distributedLobbyClient2.ConnectAsync(ServerIp, 20010).Result; distributedLobbyClient2.PeerConnected += PeerConnected; @@ -205,7 +213,7 @@ public void SecurePipeTest() Assert.IsNotNull(channel1); - byte[] data = new byte[12800000]; + byte[] data = new byte[1280000]; channel1.Start(); @@ -238,7 +246,7 @@ void Disconnected() void Channel_BytesReceived(byte[] buff, int offset, int count) { - received.Add(buff[offset]); + received.Enqueue(buff[offset]); //if(received.Count == iter) // tcs.TrySetResult(true); @@ -250,9 +258,10 @@ void Channel_BytesReceived(byte[] buff, int offset, int count) var ss = tcs.Task.Result; Assert.IsTrue(received.Count == iter); + var rec = received.ToList(); for (int i = 0; i < received.Count; i++) { - Assert.IsTrue(received[i] == i); + Assert.IsTrue(rec[i] == i); } } @@ -268,8 +277,8 @@ public void PipeTestUdp() var cl1 = GetClient(); var cl2 = GetClient(); - var res = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; cl2.PeerConnected += Cl2_PeerConnected; @@ -280,7 +289,7 @@ public void PipeTestUdp() Assert.IsNotNull(channel1); channel1.Start(); - byte[] data = new byte[12800000]; + byte[] data = new byte[12800]; channel1.Send(data,0,data.Length); Thread.Sleep(100); @@ -310,8 +319,8 @@ public void PipeTestUdpSecure() var cl1 = GetClient(); var cl2 = GetClient(); - var res = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; cl2.PeerConnected += Cl2_PeerConnected; @@ -327,7 +336,7 @@ public void PipeTestUdpSecure() var ping = channel1.Ping().Result; Console.WriteLine(ping); - byte[] data = new byte[12800000]; + byte[] data = new byte[1280000]; channel1.SendReliable(data, 0, data.Length); Thread.Sleep(5000); channel1.SendReliable(data, 0, data.Length); @@ -362,13 +371,13 @@ public void MessageTest() using var server = ArrangeServer(); - var res = distributedLobbyClient.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = distributedLobbyClient2.ConnectAsync("127.0.0.1", 20010).Result; + var res = distributedLobbyClient.ConnectAsync(ServerIp, 20010).Result; + var res2 = distributedLobbyClient2.ConnectAsync(ServerIp, 20010).Result; distributedLobbyClient2.MessageReceived += MsgRec; MessageEnvelope msg = new MessageEnvelope(); msg.Header = "Greetings"; - msg.Payload = new byte[12800000]; + msg.Payload = new byte[1280000]; msg.To = distributedLobbyClient2.SessionId; distributedLobbyClient.SendAsyncMessage(msg); @@ -403,7 +412,7 @@ public void StatusPublishTest() for (int i = 0; i < 20; i++) { var cl = GetClient(); - Task task = cl.ConnectAsync("127.0.0.1", 20010).ContinueWith(t => + Task task = cl.ConnectAsync(ServerIp, 20010).ContinueWith(t => { clients.Add(cl); ids.Add(cl.SessionId); @@ -417,7 +426,7 @@ public void StatusPublishTest() Task.WhenAll(pending).Wait(); pending.Clear(); - Thread.Sleep(1500); + Thread.Sleep(2000); VerifyPeerList(clients, ids); for (int i = 0; i < 5; i++)//dc 5 @@ -427,13 +436,13 @@ public void StatusPublishTest() clients.RemoveAt(i); } - Thread.Sleep(1500); + Thread.Sleep(2000); VerifyPeerList(clients, ids); for (int i = 0; i < 15; i++) //+ 15 { var cl = GetClient(); - Task task = cl.ConnectAsync("127.0.0.1", 20010).ContinueWith(t => + Task task = cl.ConnectAsync(ServerIp, 20010).ContinueWith(t => { clients.Add(cl); ids.Add(cl.SessionId); @@ -447,14 +456,14 @@ public void StatusPublishTest() Task.WhenAll(pending).Wait(); pending.Clear(); - Thread.Sleep(1500); + Thread.Sleep(2000); VerifyPeerList(clients, ids); for (int i = 0; i < 15; i++) //+- 15 { var cl = GetClient(); - var res = cl.ConnectAsync("127.0.0.1", 20010).Result; + var res = cl.ConnectAsync(ServerIp, 20010).Result; clients.Add(cl); ids.Add(cl.SessionId); @@ -464,7 +473,7 @@ public void StatusPublishTest() } - Thread.Sleep(1500); + Thread.Sleep(2000); VerifyPeerList(clients, ids); } @@ -491,8 +500,8 @@ public void SendAndWait() var cl1 = GetClient(); var cl2 = GetClient(); - var res = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; @@ -520,8 +529,8 @@ public void Timesync() Thread.Sleep(1337); var cl2 = GetClient(); - var res = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; double time1 = cl1.GetTime(); double time2 = cl2.GetTime(); @@ -548,8 +557,8 @@ public void UdpHolepunch() var cl2 = GetClient(); using var server = ArrangeServer(); - var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res1 = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; cl2.PeerConnected += Cl2_PeerConnected; @@ -597,8 +606,8 @@ public void UdpHolepunchSecure() var cl2 = GetClient(); using var server = ArrangeServer(); - var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res1 = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; cl2.PeerConnected += Cl2_PeerConnected; @@ -644,8 +653,8 @@ public void TcpHolepunch() var cl2 = GetClient(); using var server = ArrangeServer(); - var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res1 = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; cl2.PeerConnected += Cl2_PeerConnected; @@ -693,8 +702,8 @@ public void TcpHolepunchSecure() var cl2 = GetClient(); using var server = ArrangeServer(); - var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res1 = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; cl2.PeerConnected += Cl2_PeerConnected; @@ -742,10 +751,10 @@ public void TestTcpChannelParallelSend() var cl2 = GetClient(); using var server = ArrangeServer(); - var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res1 = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; - var data = new byte[128000]; + var data = new byte[1280]; data[0] = 1; int iter = 1000; @@ -801,8 +810,8 @@ public void TestTcpChannelOrder() var cl2 = GetClient(); using var server = ArrangeServer(); - var res1 = cl1.ConnectAsync("127.0.0.1", 20010).Result; - var res2 = cl2.ConnectAsync("127.0.0.1", 20010).Result; + var res1 = cl1.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; var data = new byte[1280]; data[0] = 1; @@ -859,6 +868,29 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) } + [TestMethod] + public void NTPClientServer() + { + Stopwatch serverClock = Stopwatch.StartNew(); + using NTPServer server = new NTPServer(22222, serverClock); + server.Start(); + Thread.Sleep(1000); + Stopwatch clientClock = Stopwatch.StartNew(); + NTPClient cl = new NTPClient("127.0.0.1", 22222, clientClock); + + Parallel.For(0, 100, i => + { + _ = cl.GetServerTime(2000); + }); + + + var timeResult = cl.GetServerTime(2000).Result; + + Assert.IsTrue(timeResult.Succes); + Assert.IsTrue(timeResult.PreciseTime >1000); + + } + } } From 7a0184940e8e27a83b8f632c10dd588cb49e73d5 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Wed, 16 Apr 2025 17:00:06 +0200 Subject: [PATCH 23/27] cleaning, fixes, exceptions etc. --- .../Channels/Components/UdpChannelBase.cs | 36 ++- .../Channels/SecureUdpChannel.cs | 27 ++- .../DistributedP2P/Channels/TcpChannel.cs | 24 +- .../DistributedP2P/Channels/UdpChannel.cs | 150 ++++++++++-- .../StateManagement/ClientConnectionState.cs | 37 +-- .../Client/StateManagement/ClientPipeState.cs | 222 +++++++++++------- .../ClientSequentialTcpHolepunchState.cs | 4 +- .../ClientSimultaneousTcpHolepunchState.cs | 4 +- .../ClientUdpHolepunchState.cs | 11 +- .../Components/ConversationStateBase.cs | 5 + .../DistributedP2P/Components/NTPServer.cs | 13 +- .../DistributedP2P/Components/StateManager.cs | 2 +- .../Server/DistributedLobbyServer.cs | 26 +- .../Server/IServerConnection.cs | 15 ++ .../StateManagement/ServerConnectionState.cs | 57 +++-- .../Server/StateManagement/ServerPipeState.cs | 148 +++++++----- .../HolePunch/KnownTypeSerializer.cs | 108 +++++++-- NetworkLibrary/TCP/SSL/SslClient.cs | 2 +- .../UDP/Reliable/Components/ReceiverModule.cs | 1 + .../UDP/Reliable/Components/SenderModule.cs | 1 + NetworkLibrary/UDP/Reliable/RudpClient.cs | 2 +- .../DistributedP2P/DistP2PServerclientTest.cs | 16 +- 22 files changed, 624 insertions(+), 287 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Server/IServerConnection.cs diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs index f37fc2c..1fcee5a 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs @@ -11,10 +11,13 @@ internal class UdpChannelBase : IDisposable private Socket udpSocket; private SocketAsyncEventArgs receiveArgs; private readonly IPEndPoint associatedEndpoint; + int disposed = 0; + public ChannelInfo Info { get; private set; } public event Action OnBytesReceived; + public Action LogAvailable; public event Action OnDisconnected; public UdpChannelBase(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) { @@ -30,14 +33,7 @@ public void Start() public void Send(byte[] data, int offset, int count) { - try - { - udpSocket.SendTo(data, offset, count, SocketFlags.None, associatedEndpoint); - } - catch (Exception e) - { - Log($"{e.Message}\n{e.StackTrace}"); - } + udpSocket.SendTo(data, offset, count, SocketFlags.None, associatedEndpoint); } private void StartReceiver() @@ -77,12 +73,22 @@ private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) } catch (Exception ex) { - Console.WriteLine($"Error processing received data: {ex}"); + Log($"{ex.Message}\n{ex.StackTrace}"); + CloseChannel(); + throw; } } + else + { + CloseChannel(); + return; + } - //Receive(); + if (Interlocked.CompareExchange(ref disposed, 0, 0) == 1) + { + return; + } if (udpSocket.ReceiveFromAsync(receiveArgs)) { return; @@ -101,21 +107,21 @@ private void HandleSocketError(SocketError error) if (error != SocketError.Shutdown) Log($"Socket error occurred: {error}"); - OnDisconnected?.Invoke(); CloseChannel(); } private void Log(string err) { - Console.WriteLine(err); + LogAvailable?.Invoke(err); } public virtual void CloseChannel() { + Interlocked.Exchange(ref OnDisconnected, null)?.Invoke(); Dispose(); } - int disposed = 0; + public virtual void Dispose() { if (Interlocked.CompareExchange(ref disposed, 1, 0) == 0) @@ -132,12 +138,16 @@ public virtual void Dispose() if (udpSocket != null) { + udpSocket.Shutdown(SocketShutdown.Both); udpSocket.Close(); udpSocket.Dispose(); udpSocket = null; } } catch { } + LogAvailable = null; + OnBytesReceived = null; + OnDisconnected = null; } } diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs index fdfb0e5..1c9d795 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs @@ -4,6 +4,7 @@ using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.UDP.Reliable.Components; using NetworkLibrary.Utils; +using System; using System.Net; using System.Net.Sockets; @@ -55,7 +56,16 @@ protected override void BytesReceived(byte[] buffer, int offset, int count) keyManager.GetAlgorithm(keyNo, out var algo); - count = algo.DecryptInto(buffer, offset, count, decryptBuff, 0); + try + { + count = algo.DecryptInto(buffer, offset, count, decryptBuff, 0); + } + catch + { + Log("Decryption failed"); + return; + } + buffer = decryptBuff; offset = 0; @@ -69,13 +79,26 @@ protected override void HandleReivedMessage(byte[] buffer, int offset, int count case MessageFlags.KeyExchange: case MessageFlags.KeyExchangeAck: case MessageFlags.KeyExchangeFin: - keyManager.HandleMessage(flag, buffer, offset, count); + HandleKeyMessage(buffer, offset, count, flag); break; } base.HandleReivedMessage(buffer, offset, count, flag); } + private void HandleKeyMessage(byte[] buffer, int offset, int count, MessageFlags flag) + { + try + { + keyManager.HandleMessage(flag, buffer, offset, count); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + } + + } protected override void SendWithFlag(MessageFlags flag, byte[] buffer, int offset, int count) { diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs index 8e4df28..8c6d029 100644 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs @@ -249,12 +249,10 @@ private void Received(object sender, SocketAsyncEventArgs e) if (e.SocketError != SocketError.Success) { ErrorAndEnd($"While receiving a socket error occured {e.SocketError}"); - CloseChannel(); return; } else if (e.BytesTransferred == 0) { - Log("0 bytes"); CloseChannel(); return; } @@ -267,13 +265,28 @@ private void Received(object sender, SocketAsyncEventArgs e) catch (Exception ex) { ErrorAndEnd(ex.Message + "\n" + ex.StackTrace); - throw; + return; } + + if (IsSessionClosing()) + return; + //Receive(); - if (connectedSocket.ReceiveAsync(receiveArgs)) + try { - return; + if (connectedSocket.ReceiveAsync(receiveArgs)) + { + return; + } + } + catch (Exception ex) + { + if (IsSessionClosing()) + return; + + ErrorAndEnd(ex.Message + "\n" + ex.StackTrace); } + } } @@ -282,7 +295,6 @@ protected virtual void HandleReceivedBytes(byte[] buffer, int offset, int count) var flag = (MessageFlags)buffer[offset++]; count--; HandleReceivedMessage(buffer, offset, count, flag); - } protected virtual void HandleReceivedMessage(byte[] buffer, int offset, int count, MessageFlags flag) diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs index 2f3a588..5d769c2 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs @@ -35,23 +35,23 @@ public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) Info = info; innerchannel = new UdpChannelBase(udpSocket, receiveEp, info); + innerchannel.LogAvailable += Log; + JumboUdp.SendToSocket = SendJumboSegment; JumboUdp.MessageReceived = HandleMessage; SenderModule sender = new SenderModule(); - //sender.SoftwindowTrim = 1f; sender.MaxSegmentSize = 1280; sender.MinWindowSize = 1280 * 2; ReliableUdp = new ReliableModule(receiveEp, sender); - ReliableUdp.OnReceived += (e, b, o, c) => HandleMessage(b, o, c); ReliableUdp.OnSend += SendRudpSegment; SenderModule sender2 = new SenderModule(); - sender.MaxSegmentSize = 1280; sender.MinWindowSize = 1280 * 2; + internalReliableModule = new ReliableModule(receiveEp, sender2); internalReliableModule.OnReceived += (e, b, o, c) => HandleInternalReliableMessage(b, o, c); internalReliableModule.OnSend += SendInternalRudpSegment; @@ -66,8 +66,6 @@ public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) } - - public void Start() { innerchannel.OnBytesReceived += BytesReceived; @@ -85,6 +83,7 @@ protected virtual void BytesReceived(byte[] buffer, int offset, int count) protected virtual void HandleReivedMessage(byte[] buffer, int offset, int count, MessageFlags flag) { + switch (flag) { case MessageFlags.StandardMessage: @@ -100,7 +99,7 @@ protected virtual void HandleReivedMessage(byte[] buffer, int offset, int count, break; case MessageFlags.KeepAliveMessage: - keepAlive.HandleMessage(flag, buffer, offset, count); + HandleKeepAliveMessage(buffer, offset, count, flag); break; case MessageFlags.Kill: @@ -113,7 +112,7 @@ protected virtual void HandleReivedMessage(byte[] buffer, int offset, int count, case MessageFlags.Ping: case MessageFlags.Pong: - pinger.HandleMessage(flag, buffer, offset, count); + HandlePingMessage(buffer, offset, count, flag); break; } @@ -122,7 +121,7 @@ protected virtual void HandleReivedMessage(byte[] buffer, int offset, int count, } - protected virtual void HandleInternalReliableMessage(byte[] buffer, int offset, int count) + private void HandleInternalReliableMessage(byte[] buffer, int offset, int count) { var flag = (MessageFlags)buffer[offset++]; count--; @@ -130,38 +129,126 @@ protected virtual void HandleInternalReliableMessage(byte[] buffer, int offset, HandleReivedMessage(buffer, offset, count, flag); } - protected virtual void HandleMessage(byte[] buffer, int offset, int count) + private void HandleMessage(byte[] buffer, int offset, int count) { - OnBytesReceived?.Invoke(buffer, offset, count); + try + { + OnBytesReceived?.Invoke(buffer, offset, count); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + throw; + } } - protected void HandleJumboSegment(byte[] buffer, int offset, int count) + private void HandleJumboSegment(byte[] buffer, int offset, int count) { - JumboUdp.HandleReceivedSegment(buffer, offset, count); + try + { + JumboUdp.HandleReceivedSegment(buffer, offset, count); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + } } - protected virtual void SendJumboSegment(byte[] arg1, int arg2, int arg3) + private void SendJumboSegment(byte[] arg1, int arg2, int arg3) { - SendWithFlag(MessageFlags.JumboMessage, arg1, arg2, arg3); + try + { + SendWithFlag(MessageFlags.JumboMessage, arg1, arg2, arg3); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + } + } - protected void HandleRudpSegment(byte[] buffer, int offset, int count) + private void HandleRudpSegment(byte[] buffer, int offset, int count) + { + try + { + ReliableUdp.HandleBytes(buffer, offset, count); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + } + + } + private void HandleKeepAliveMessage(byte[] buffer, int offset, int count, MessageFlags flag) { - ReliableUdp.HandleBytes(buffer, offset, count); + try + { + keepAlive.HandleMessage(flag, buffer, offset, count); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + } } - protected void HandleIncomingInternalRudpSegment(byte[] buffer, int offset, int count) + private void HandleIncomingInternalRudpSegment(byte[] buffer, int offset, int count) { - internalReliableModule.HandleBytes(buffer, offset, count); + try + { + internalReliableModule.HandleBytes(buffer, offset, count); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + } + } + + private void HandlePingMessage(byte[] buffer, int offset, int count, MessageFlags flag) + { + try + { + pinger.HandleMessage(flag, buffer, offset, count); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + } + } - internal virtual void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) + + private void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) { - SendWithFlag(MessageFlags.ReliableMessage, buffer, offset, count); + try + { + SendWithFlag(MessageFlags.ReliableMessage, buffer, offset, count); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + } } + - internal virtual void SendInternalRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) + private void SendInternalRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) { - SendWithFlag(MessageFlags.InternalReliableMessage, buffer, offset, count); + try + { + SendWithFlag(MessageFlags.InternalReliableMessage, buffer, offset, count); + } + catch (Exception e) + { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + } + } public Task Ping() @@ -171,7 +258,6 @@ public Task Ping() public virtual void Send(byte[] buffer, int offset, int count) { - if (count > 64000) { JumboUdp.Send(buffer, offset, count); @@ -208,6 +294,7 @@ protected void SendInternalReliable(MessageFlags flag, byte[] buffer, int offset SharerdMemoryStreamPool.ReturnStreamStatic(stream); } + protected void SendInternal(byte[] bytes, int offset, int count) { try @@ -216,8 +303,10 @@ protected void SendInternal(byte[] bytes, int offset, int count) } catch (Exception e) { + Log($"{e.Message}\n{e.StackTrace}"); + CloseChannel(); + throw; } - } @@ -226,6 +315,13 @@ public void CloseChannel() try { SendWithFlag(MessageFlags.Kill, new byte[1], 0, 1); + + } + catch { } + + try + { + innerchannel.CloseChannel(); } catch { } @@ -237,6 +333,7 @@ protected void HandleDisconnect() { if (Interlocked.CompareExchange(ref isClosed, 1, 0) == 0) { + Log("Udp Channel disconnected"); OnDisconnected?.Invoke(); Dispose(); } @@ -264,6 +361,9 @@ protected virtual void ReleseResources() } - + protected virtual void Log(string v) + { + Console.WriteLine(v); + } } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs index f9ecceb..b92e4c6 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs @@ -27,9 +27,6 @@ public override void HandleMessage(MessageEnvelope message) { switch (message.Header) { - case InternalConstants.SyncTime: - SyncTime(message); - break; case InternalConstants.ConnectionGetClientPublicData: SendClientPublicData(message); break; @@ -39,31 +36,9 @@ public override void HandleMessage(MessageEnvelope message) case InternalConstants.Error: HandleConnectionFail(message); break; - - } } - private async void SyncTime(MessageEnvelope message) - { - bool res = await timeSyncComplete.Task; - if (res) - { - MessageEnvelope msg = CreateEnvelope(); - msg.Header = InternalConstants.SyncTime; - connection.SendAsyncMessage(msg); - } - } - - private void SyncTime() - { - var task = connection.SyncTime().ContinueWith(t => - { - timeSyncComplete.TrySetResult(true); - }); - - } - public void Start() { MessageEnvelope msg = CreateEnvelope(); @@ -88,6 +63,18 @@ public void Start() // then succes or fail } + private void SyncTime() + { + var task = connection.SyncTime().ContinueWith(t => + { + MessageEnvelope msg = CreateEnvelope(); + msg.Header = InternalConstants.SyncTime; + connection.SendAsyncMessage(msg); + + }, TaskScheduler.Default); + + } + private void SendClientPublicData(MessageEnvelope message) { MessageEnvelope response = CreateEnvelope(); diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index d8d1a5f..706ed72 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -1,15 +1,19 @@ -using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.Components.Crypto.DiffieHellman; +using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.P2P.Components.HolePunch; +using NetworkLibrary.Utils; using System; -using System.Collections.Generic; using System.Net; using System.Net.Sockets; -using System.Text; using System.Threading.Tasks; -using NetworkLibrary.Components.Crypto.DiffieHellman; namespace NetworkLibrary.DistributedP2P.Client.StateManagement { + class ClientPipeData + { + public ChannelInfo ChannelInfo { get; set; } + public byte[] DHPublic; + } internal class ClientPipeState : ConversationStateBase { private readonly IDistributedConnection connection; @@ -22,14 +26,17 @@ internal class ClientPipeState : ConversationStateBase public ChannelInfo ChannelInfo; private DiffieHellman df; - public ClientPipeState(Guid stateId, IDistributedConnection connection,EndpointData serverEndpoint,ChannelInfo info) : base(stateId, 20000) + private bool isInitiator; + + public ClientPipeState(Guid stateId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 20000) { this.connection = connection; this.serverEndpoint = serverEndpoint; this.ChannelInfo = info; + isInitiator = true; } - public ClientPipeState(MessageEnvelope message,IDistributedConnection connection, EndpointData serverEndpoint) : base(message.MessageId) + public ClientPipeState(MessageEnvelope message, IDistributedConnection connection, EndpointData serverEndpoint) : base(message.MessageId) { this.connection = connection; this.serverEndpoint = serverEndpoint; @@ -42,33 +49,31 @@ public void Start(Guid destinationPeer) this.destinationPeer = destinationPeer; var msg = CreateEnvelope(); - msg.Header = tcp?InternalConstants.PipeRequestTcp:InternalConstants.PipeRequestUdp; + msg.Header = tcp ? InternalConstants.PipeRequestTcp : InternalConstants.PipeRequestUdp; msg.To = destinationPeer; - msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["Type"] = ((int)ChannelInfo.ChannelType).ToString(); - msg.KeyValuePairs["Name"] = ChannelInfo.ChannelName; - if (ChannelInfo.RequiresKeyExchange()) - { - df = new DiffieHellman(); - byte[] key = df.GetPublicKey(); - string keyStr = Convert.ToBase64String(key); - msg.KeyValuePairs["DH"] = keyStr; - } + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + var data = GetPipeData(); + + KnownTypeSerializer.SerializeClientPipeData(stream, data); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(msg); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + + Log("Requested Pipe"); } public override void HandleMessage(MessageEnvelope message) { - switch(message.Header) + switch (message.Header) { // the listener case InternalConstants.PipeRequestTcp: case InternalConstants.PipeRequestUdp: HandleConnectionRequest(message); break; - + case InternalConstants.PipeTokenDeliveryTcp: HandlePipeTokenTcp(message); break; @@ -89,79 +94,88 @@ public override void HandleMessage(MessageEnvelope message) private void HandleConnectionRequest(MessageEnvelope message) { - ChannelInfo = new ChannelInfo(); - ChannelInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); - ChannelInfo.ChannelName = message.KeyValuePairs["Name"]; + Log("Connection Request Received"); + int offs = message.PayloadOffset; + var pipeData = KnownTypeSerializer.DeserializeClientPipeData(message.Payload, ref offs); + ChannelInfo = pipeData.ChannelInfo; + + var myData = GetPipeData(); - var msg = CreateEnvelope(); - msg.Header = InternalConstants.PipeReqAck; - if (ChannelInfo.RequiresKeyExchange()) { - df = new DiffieHellman(); - byte[] key = df.GetPublicKey(); - string keyStr = Convert.ToBase64String(key); - - msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["DH"] = keyStr; + GenerateSharedSecret(pipeData.DHPublic); } + var msg = CreateEnvelope(); + msg.Header = InternalConstants.PipeReqAck; + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + + myData.ChannelInfo = null; + + KnownTypeSerializer.SerializeClientPipeData(stream, myData); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + connection.SendAsyncMessage(msg); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } private async void HandlePipeTokenTcp(MessageEnvelope message) { + Log("Handling Tcp Token"); try { int off = message.PayloadOffset; var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); - if (ChannelInfo.RequiresKeyExchange()) - GenerateSharedSecret(message.KeyValuePairs["DH"]); + if (pipeData.DHPublic != null) + GenerateSharedSecret(pipeData.DHPublic); - foreach (EndpointData endpoint in pipeData.PipeEndpoints) - { - if (IPHelper.IsZero(endpoint.Ip)) - endpoint.Ip = serverEndpoint.Ip; + EndpointData endpoint = pipeData.PipeEndpoint; + + if (IPHelper.IsZero(endpoint.Ip)) + endpoint.Ip = serverEndpoint.Ip; - Socket connected = await TryConnectWithTimeout(endpoint); - if (connected != null) + Socket connected = await TryConnectWithTimeout(endpoint).ConfigureAwait(false); + if (connected != null) + { + bool success = await TokenExchange(connected, pipeData.Token); + if (success) + { + OnConnectionSuccessful(endpoint, connected); + return; + } + else { - bool success = await TokenExchange(connected, pipeData.Token); - if (success) - { - OnConnectionSuccessful(endpoint, connected); - return; - } - else - { - try { connected.Close(); connected.Dispose(); } catch { } - } + try { connected.Close(); connected.Dispose(); } catch { } } - // connect send token - //wait a data to come - //then send ack } + // connect send token + //wait a data to come + //then send ack + Log($"Failed to exchange Tcp Token"); OnConnectionFail(); return; } - catch + catch (Exception e) { + Log($"An Error occured while handling Tcp Token{e.Message}\n{e.StackTrace}"); OnConnectionFail(); } - + } - private void GenerateSharedSecret(string otherPublic) + private void GenerateSharedSecret(byte[] otherPublic) { - sharedSecret = df.CalculateSharedSecret(Convert.FromBase64String(otherPublic)); + sharedSecret = df.CalculateSharedSecret(otherPublic); } private async Task TryConnectWithTimeout(EndpointData endpoint, int timeout = 5000) { var clientSocket = new Socket(SocketType.Stream, ProtocolType.Tcp); - + var connectTask = ConnectAsync(clientSocket, endpoint.ToIpEndpoint()); var timeoutTask = Task.Delay(timeout); @@ -169,24 +183,26 @@ private async Task TryConnectWithTimeout(EndpointData endpoint, int time if (completedTask == timeoutTask) { + Log("Connection Failed"); try { clientSocket.Close(); clientSocket.Dispose(); } catch { } return null; } - bool res = connectTask.Result; - if(res) + bool res = connectTask.Result; + if (res) { return clientSocket; } else { + Log("Connection Failed"); try { clientSocket.Close(); clientSocket.Dispose(); } catch { } return null; } } - private async Task ConnectAsync(Socket socket, IPEndPoint endPoint) + private Task ConnectAsync(Socket socket, IPEndPoint endPoint) { var tcs = new TaskCompletionSource(); @@ -207,21 +223,21 @@ private async Task ConnectAsync(Socket socket, IPEndPoint endPoint) if (!socket.ConnectAsync(sa)) { - return sa.SocketError == SocketError.Success; + if (sa.SocketError == SocketError.Success) + tcs.TrySetResult(true); } - return await tcs.Task; + return tcs.Task; } private async Task TokenExchange(Socket connectedSocket, byte[] token, int timeoutMs = 5000) { try { - - int bytesSent = await connectedSocket.SendAsync(new ArraySegment(token), SocketFlags.None); if (bytesSent != token.Length) { + Log("Tcp Token Send Failure"); return false; } @@ -233,17 +249,17 @@ private async Task TokenExchange(Socket connectedSocket, byte[] token, int if (completedTask == timeoutTask) { + Log("Tcp Token Response Timeout"); return false; } int bytesReceived = receiveTask.Result; - - - return bytesReceived == 1; + } - catch + catch (Exception e) { + Log($"An Error occured while exchanging Tcp Token{e.Message}\n{e.StackTrace}"); return false; } } @@ -252,25 +268,29 @@ private async Task TokenExchange(Socket connectedSocket, byte[] token, int private async void HandlePipeTokenUdp(MessageEnvelope message) { + try + { + int off = message.PayloadOffset; + var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); - if (ChannelInfo.RequiresKeyExchange()) - GenerateSharedSecret(message.KeyValuePairs["DH"]); + if (pipeData.DHPublic != null) + GenerateSharedSecret(pipeData.DHPublic); - int off = message.PayloadOffset; - var pipeData = KnownTypeSerializer.DeserializePipeData(message.Payload, ref off); - var connected = new Socket(SocketType.Dgram, ProtocolType.Udp); - connected.SendBufferSize = 12800000; - connected.ReceiveBufferSize = 12800000; + var connected = new Socket(SocketType.Dgram, ProtocolType.Udp); + connected.SendBufferSize = 12800000; + connected.ReceiveBufferSize = 12800000; + + EndpointData endpoint = pipeData.PipeEndpoint; + - foreach (EndpointData endpoint in pipeData.PipeEndpoints) - { if (IPHelper.IsZero(endpoint.Ip)) endpoint.Ip = serverEndpoint.Ip; bool success = await UdpTokenExchange(connected, pipeData.Token, endpoint.ToIpEndpoint()); if (success) { + Log($"Connected"); OnConnectionSuccessful(endpoint, connected); return; } @@ -278,17 +298,25 @@ private async void HandlePipeTokenUdp(MessageEnvelope message) { try { connected.Close(); connected.Dispose(); } catch { } } - + // connect send token //wait a data to come //then send ack + + + Log($"Failed to exchange Udp Token"); + OnConnectionFail(); + return; + } + catch (Exception e) + { + Log($"An Error occured while handling Udp Token{e.Message}\n{e.StackTrace}"); + OnConnectionFail(); } - OnConnectionFail(); - return; } - private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndPoint remoteEndPoint, int timeoutMs = 500) + private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndPoint remoteEndPoint, int timeoutMs = 3000) { try { @@ -300,6 +328,7 @@ private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndP if (await Task.WhenAny(sendTask, sendTimeout) == sendTimeout || sendTask.Result != token.Length) { + Log("Udp Token Send Timeout"); return false; } @@ -309,16 +338,18 @@ private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndP if (await Task.WhenAny(receiveTask, receiveTimeout) == receiveTimeout) { + Log("Udp Token Receive Timeout"); return false; } return receiveTask.Result == 1; } - catch + catch (Exception e) { + Log($"An Error occured while exchanging Udp Token{e.Message}\n{e.StackTrace}"); return false; } - + } @@ -330,6 +361,8 @@ private void OnConnectionSuccessful(EndpointData endpoint, Socket socket) this.ConnectedSocket = socket; this.SuccesfullEndpoint = endpoint; + Log("Completed"); + var msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionAckGood; connection.SendAsyncMessage(msg); @@ -341,12 +374,14 @@ private void OnConnectionFail() msg.Header = InternalConstants.ConnectionAckBad; connection.SendAsyncMessage(msg); + Log("Failed"); + Completed(false); } private void HandleGoodAck(MessageEnvelope message) { - Completed(true); + Completed(true); } private void HandleBadAck(MessageEnvelope message) @@ -354,5 +389,24 @@ private void HandleBadAck(MessageEnvelope message) Completed(false); } + private ClientPipeData GetPipeData() + { + ClientPipeData hpd = new ClientPipeData(); + hpd.ChannelInfo = ChannelInfo; + + if (ChannelInfo.RequiresKeyExchange()) + { + df = new DiffieHellman(); + hpd.DHPublic = df.GetPublicKey(); + } + return hpd; + } + protected override void Log(string log) + { + //return; + string prefix = isInitiator ? "A: " : "B: "; + base.Log(prefix + log); + } + } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs index 54fc6a5..03ea614 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs @@ -484,11 +484,11 @@ protected override void Completed(bool succes) } - private void Log(string log) + protected override void Log(string log) { //return; string prefix = isInitiator ? "A: " : "B: "; - Console.WriteLine(prefix + log); + base.Log(prefix + log); } private ClientHolepunchData GetHpData() diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs index 47f853c..3ed8b49 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs @@ -368,11 +368,11 @@ protected override void Completed(bool succes) } - private void Log(string log) + protected override void Log(string log) { //return; string prefix = isInitiator ? "A: " : "B: "; - Console.WriteLine(prefix + log); + base.Log(prefix + log); } private ClientHolepunchData GetHpData() diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index a071f48..d444031 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -5,7 +5,6 @@ using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.Utils; using System; -using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; @@ -304,7 +303,7 @@ private void ReceivedBidirectional(EndPoint remoteEndPoint) if (Interlocked.CompareExchange(ref SuccesfulEndpoint, ipep, null) != null) return; - + var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchSucces; @@ -346,7 +345,7 @@ private void HandleRemoteSucces(MessageEnvelope message) private void SignalCompletionCondition() { - if(Interlocked.Increment(ref conditionCount) == 3) + if (Interlocked.Increment(ref conditionCount) == 3) { if (ChannelInfo.RequiresKeyExchange()) SharedSecret = df.CalculateSharedSecret(otherPublicKey); @@ -357,7 +356,7 @@ private void SignalCompletionCondition() Log("Punched"); Completed(true); } - + } protected override void Completed(bool succes) @@ -374,11 +373,11 @@ protected override void Completed(bool succes) } } - private void Log(string log) + protected override void Log(string log) { //return; string prefix = isInitiator ? "A: " : "B: "; - Console.WriteLine(prefix + log); + base.Log(prefix + log); } diff --git a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs index 7966f4a..b343710 100644 --- a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs +++ b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs @@ -92,6 +92,11 @@ protected virtual void Completed(bool succes) } } + protected virtual void Log(string log) + { + Console.WriteLine(log); + } + } } diff --git a/NetworkLibrary/DistributedP2P/Components/NTPServer.cs b/NetworkLibrary/DistributedP2P/Components/NTPServer.cs index 6f0e29e..cc29e66 100644 --- a/NetworkLibrary/DistributedP2P/Components/NTPServer.cs +++ b/NetworkLibrary/DistributedP2P/Components/NTPServer.cs @@ -63,8 +63,9 @@ private void Received(object _, SocketAsyncEventArgs e) udpListener.SendTo(buff, 0, buff.Length, SocketFlags.None, e.RemoteEndPoint); - ((IPEndPoint)e.RemoteEndPoint).Address = IPAddress.Any; - ((IPEndPoint)e.RemoteEndPoint).Port = 0; + var ep = (IPEndPoint)e.RemoteEndPoint; + ep.Address = IPAddress.Any; + ep.Port = 0; if (udpListener.ReceiveFromAsync(e)) { return; @@ -74,7 +75,7 @@ private void Received(object _, SocketAsyncEventArgs e) { if (Interlocked.CompareExchange(ref IsDisposed, 0, 0) == 0) { - Console.WriteLine(ex.Message + "\n" + ex.StackTrace); + Log(ex.Message + "\n" + ex.StackTrace); e.Dispose(); Receive(GetReceiveArgs()); return; @@ -91,6 +92,7 @@ private void Received(object _, SocketAsyncEventArgs e) } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private byte[] GetBuffer() { @@ -113,6 +115,11 @@ private SocketAsyncEventArgs GetReceiveArgs() return recArgs; } + private void Log(string v) + { + Console.WriteLine(v); + } + public void Dispose() { if (Interlocked.CompareExchange(ref IsDisposed, 1, 0) == 0) diff --git a/NetworkLibrary/DistributedP2P/Components/StateManager.cs b/NetworkLibrary/DistributedP2P/Components/StateManager.cs index 40f0a51..d6efbae 100644 --- a/NetworkLibrary/DistributedP2P/Components/StateManager.cs +++ b/NetworkLibrary/DistributedP2P/Components/StateManager.cs @@ -42,7 +42,7 @@ public bool HandleMessage(MessageEnvelope message) } catch (Exception e) { - Console.WriteLine(e); + Console.WriteLine($"State Management failed{e.Message}\n{e.StackTrace}"); state.Cancel(); UnregisterState(stateId); } diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 8b8e8fb..3d791da 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -12,7 +12,6 @@ using System.Diagnostics; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; -using System.Threading; using System.Threading.Tasks; namespace NetworkLibrary.DistributedP2P.Server @@ -31,7 +30,7 @@ public class ServerParameters public int UdpPort; public int DiscoveryServerPort; } - public class DistributedLobbyServerBase: IDistributedConnection, IDisposable + public class DistributedLobbyServerBase : IServerConnection, IDisposable { public readonly int SSlPort; public readonly int TcpPort; @@ -192,7 +191,7 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) case InternalConstants.PipeRequestTcp: - var pipeState = new ServerPipeState(message.MessageId, this, relayService); + var pipeState = new ServerPipeState(message.MessageId, this); stateManager.RegisterState(pipeState); pipeState.HandleMessage(message); @@ -201,7 +200,7 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) case InternalConstants.PipeRequestUdp: - var pipeState1 = new ServerPipeState(message.MessageId, this, relayService); + var pipeState1 = new ServerPipeState(message.MessageId, this); stateManager.RegisterState(pipeState1); pipeState1.HandleMessage(message); @@ -231,8 +230,10 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) break; } } + int ctr = 0; + Random r = new Random(42); - private void HandleTimeSync(Guid clientId, MessageEnvelope message) + private void HandleTimeSync(Guid clientId, MessageEnvelope message) { ////if (ctr++ % 2 == 0) //{ @@ -249,8 +250,19 @@ private void HandleTimeSync(Guid clientId, MessageEnvelope message) SendAsyncMessage(clientId, message); } - int ctr = 0; - Random r = new Random(42); + + void IServerConnection.GetPipeToken(bool isTcpPipe, Guid fromEphemeral, Guid toEphemeral, Action onReady) + { + // so we need to find a service here, on the relay services, prioritize locality then load. + var res = new PipeResult(); + res.IsSuccesfull = true; + res.Token = relayService.GetPipeToken(isTcpPipe); + res.PipeEndpoint = new EndpointData("0.0.0.0", isTcpPipe ? 20011 : 20012); + + onReady?.Invoke(res); + } + + private bool CreateRoom(string roomName, string roomPassword, RoomProtocol protocol) { if (roomManager.TryCreateRoom(roomName, roomPassword, out Guid RoomId)) diff --git a/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs b/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs new file mode 100644 index 0000000..369341c --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs @@ -0,0 +1,15 @@ +using NetworkLibrary.DistributedP2P.Components; +using NetworkLibrary.DistributedP2P.Server.StateManagement; +using NetworkLibrary.P2P.Components.HolePunch; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace NetworkLibrary.DistributedP2P.Server +{ + internal interface IServerConnection : IDistributedConnection + { + void GetPipeToken(bool isTcpPipe, Guid fromEphemeral, Guid toEphemeral, Action onReady); + } +} diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs index 7980201..103834f 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs @@ -1,8 +1,6 @@ using NetworkLibrary.DistributedP2P.Components; using System; using System.Collections.Generic; -using System.Net; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,21 +15,24 @@ internal class ServerConnectionState : ConversationStateBase private readonly IDistributedConnection connection; private readonly IAuthenticator authenticator; private readonly IServerDbConnector dbConnector; - private readonly int eDSPort; + private readonly int endpointDiscoveryServerPort; private IAuthenticationResult tokenResult; public List clientLocalIps; - public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection connection, IAuthenticator authenticator, IServerDbConnector dbConnector,int EDSPort):base(stateId,20000) + int timeSynced; + int handShakeComplete; + + public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection connection, IAuthenticator authenticator, IServerDbConnector dbConnector, int EDSPort) : base(stateId, 20000) { this.EphemeralClientId = clientId; this.connection = connection; this.authenticator = authenticator; this.dbConnector = dbConnector; - eDSPort = EDSPort; + endpointDiscoveryServerPort = EDSPort; } - + public override void HandleMessage(MessageEnvelope message) { if (IsCompleted()) @@ -52,10 +53,7 @@ public override void HandleMessage(MessageEnvelope message) } } - private void HandleTimeSyncComplete(MessageEnvelope message) - { - SendGood(); - } + private void HandleInitialConnectionRequest(MessageEnvelope message) { @@ -76,7 +74,7 @@ private void HandleInitialConnectionRequest(MessageEnvelope message) } message.KeyValuePairs.Remove("AuthToken"); message.KeyValuePairs.Remove("AuthMethod"); - if(message.KeyValuePairs.ContainsKey("AdditionalData")) + if (message.KeyValuePairs.ContainsKey("AdditionalData")) message.KeyValuePairs.Remove("AdditionalData"); clientLocalIps = new List(); @@ -86,7 +84,7 @@ private void HandleInitialConnectionRequest(MessageEnvelope message) clientLocalIps.Add(kv.Key); } - authenticator.Authenticate(token, method, additionalData).ContinueWith(HandleAuthentication); + authenticator.Authenticate(token, method, additionalData).ContinueWith(HandleAuthentication, TaskScheduler.Default); } private void HandleAuthentication(Task task) @@ -108,7 +106,7 @@ private void HandleAuthentication(Task task) private void FindClientDBLink(IAuthenticationResult tokenResult) { - dbConnector.GetClientData(tokenResult).ContinueWith(t => HandleClientData(t.Result, tokenResult)); + dbConnector.GetClientData(tokenResult).ContinueWith(t => HandleClientData(t.Result, tokenResult), TaskScheduler.Default); } private void HandleClientData(IClientDbInfo dbResult, IAuthenticationResult tokenResult) @@ -119,7 +117,7 @@ private void HandleClientData(IClientDbInfo dbResult, IAuthenticationResult toke if (dbResult.IsValid) { clientDbInfo = dbResult; - SyncTime(); + HandShakecomplete(); } else { @@ -128,6 +126,8 @@ private void HandleClientData(IClientDbInfo dbResult, IAuthenticationResult toke } } + + private void RegisterClient() { var msg = CreateEnvelope(); @@ -138,7 +138,7 @@ private void RegisterClient() private void HandleClientPublicData(MessageEnvelope message) { message.LockBytes(); - dbConnector.RegisterClient(tokenResult, message.Payload).ContinueWith(HandleDbRegistration); + dbConnector.RegisterClient(tokenResult, message.Payload).ContinueWith(HandleDbRegistration, TaskScheduler.Default); } private void HandleDbRegistration(Task task) @@ -150,7 +150,7 @@ private void HandleDbRegistration(Task task) if (dbResult.IsValid) { clientDbInfo = dbResult; - SyncTime(); + HandShakecomplete(); } else { @@ -158,16 +158,23 @@ private void HandleDbRegistration(Task task) } } - private void SyncTime() + private void HandleTimeSyncComplete(MessageEnvelope message) { - var msg = CreateEnvelope(); - msg.Header = InternalConstants.SyncTime; - msg.To = EphemeralClientId; + if (Interlocked.Exchange(ref timeSynced, 1) == 0) + { + if (Interlocked.CompareExchange(ref handShakeComplete, 0, 0) == 1) + SendGood(); + } - if (IsCompleted()) - return; + } - connection.SendAsyncMessage(EphemeralClientId, msg); + private void HandShakecomplete() + { + if (Interlocked.Exchange(ref handShakeComplete, 1) == 0) + { + if (Interlocked.CompareExchange(ref timeSynced, 0, 0) == 1) + SendGood(); + } } private void SendGood() @@ -176,7 +183,7 @@ private void SendGood() msg.Header = InternalConstants.ConnectionAckGood; msg.To = EphemeralClientId; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["EDSPort"] = eDSPort.ToString(); + msg.KeyValuePairs["EDSPort"] = endpointDiscoveryServerPort.ToString(); lock (cancellationMutex) { if (IsCompleted()) @@ -193,6 +200,6 @@ private void ReplyError(string err) Completed(false); } - + } } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs index 24b3860..2d5d68e 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs @@ -1,10 +1,8 @@ using NetworkLibrary.DistributedP2P.Client; using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.DistributedP2P.SimpleRelay; using NetworkLibrary.P2P.Components.HolePunch; using NetworkLibrary.Utils; using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -14,26 +12,31 @@ class PipeData { public const int TokenLength = 32 + 24;//32 bytes signature, 24 bytes token public byte[] Token { get; set; } + public byte[] DHPublic { get; set; } // localhost, localip, publicip - public List PipeEndpoints { get; set; } = new List(); + public EndpointData PipeEndpoint { get; set; } + } + class PipeResult + { + public byte[] Token { get; set; } + public EndpointData PipeEndpoint { get; set; } + public bool IsSuccesfull { get; internal set; } } internal class ServerPipeState : ConversationStateBase { - private RelayService piper; private Guid from, to; private int ackCount = 0; - private readonly IDistributedConnection connection; + private readonly IServerConnection connection; private string ChannelType; bool isTcpPipe = false; - private string destinationDhPublicKey; - private string requesterDhPublicKey; - private ChannelInfo chInfo = new ChannelInfo(); - public ServerPipeState(Guid stateId, IDistributedConnection connection, RelayService piper) : base(stateId, 20000) + private byte[] destinationsDhPublicKey; + + private ChannelInfo chInfo = new ChannelInfo(); + public ServerPipeState(Guid stateId, IServerConnection connection) : base(stateId, 20000) { this.connection = connection; - this.piper = piper; } /* @@ -48,100 +51,118 @@ public ServerPipeState(Guid stateId, IDistributedConnection connection, RelaySer public override void HandleMessage(MessageEnvelope message) { - switch (message.Header) + try { - case InternalConstants.PipeRequestTcp: - HandlePipeRequest(message, true); - break; - case InternalConstants.PipeRequestUdp: - HandlePipeRequest(message, false); - break; + switch (message.Header) + { + case InternalConstants.PipeRequestTcp: + HandlePipeRequest(message, true); + break; + case InternalConstants.PipeRequestUdp: + HandlePipeRequest(message, false); + break; - case InternalConstants.PipeReqAck://do nack also - HandlePipeReqAck(message); - break; + case InternalConstants.PipeReqAck://do nack also + HandlePipeReqAck(message); + break; - case InternalConstants.ConnectionAckGood: - HandleGoodAck(message); - break; + case InternalConstants.ConnectionAckGood: + HandleGoodAck(message); + break; - case InternalConstants.ConnectionAckBad: - HandleBadAck(message); - break; + case InternalConstants.ConnectionAckBad: + HandleBadAck(); + break; + + } } - } + catch (Exception ex) + { + Log($"Exception occured on server pipe state: {ex.Message}\n{ex.StackTrace}"); + HandleBadAck(); + } - private void HandlePipeReqAck(MessageEnvelope message) - { - if (chInfo.RequiresKeyExchange()) - destinationDhPublicKey = message.KeyValuePairs["DH"]; - ObtainPipeToken().ContinueWith(HandlePipeToken); } - + //[A] private void HandlePipeRequest(MessageEnvelope message, bool tcp) { this.from = message.From; this.to = message.To; - ChannelType = message.KeyValuePairs["Type"]; - chInfo.ChannelType = (ChannelType)int.Parse(message.KeyValuePairs["Type"]); - chInfo.ChannelName = message.KeyValuePairs["Name"]; + int offs = message.PayloadOffset; + var clientPipeData = KnownTypeSerializer.DeserializeClientPipeData(message.Payload, ref offs); - if (chInfo.RequiresKeyExchange()) - { - requesterDhPublicKey = message.KeyValuePairs["DH"]; - message.KeyValuePairs.Remove("DH"); - } + chInfo = clientPipeData.ChannelInfo; - connection.SendAsyncMessage(message); + connection.SendAsyncMessage(message);// dest will know dh token in this. isTcpPipe = tcp; } - - private Task ObtainPipeToken() + //[B] + private void HandlePipeReqAck(MessageEnvelope message) { - //do only local for now - byte[] token = piper.GetPipeToken(isTcpPipe); - PipeData data = new PipeData(); - data.Token = token; - data.PipeEndpoints = new List() { new EndpointData("0.0.0.0", isTcpPipe ? 20011 : 20012) }; + int offs = message.PayloadOffset; + var clientPipeData = KnownTypeSerializer.DeserializeClientPipeData(message.Payload, ref offs); + if (chInfo.RequiresKeyExchange()) + destinationsDhPublicKey = clientPipeData.DHPublic;// requester will now this on pipetoken - return Task.FromResult(data); + connection.GetPipeToken(isTcpPipe, from, to, HandlePipeResult); + } + private void HandlePipeResult(PipeResult result) + { + if (result.IsSuccesfull) + { + var data = new PipeData + { + Token = result.Token, + PipeEndpoint = result.PipeEndpoint + }; + HandlePipeToken(data); + } + else + { + Log("Unable to obtain pipe token"); + HandleBadAck(); + } + + } - private void HandlePipeToken(Task task) + + private void HandlePipeToken(PipeData data) { - var data = task.Result; var msg = CreateEnvelope(); msg.Header = isTcpPipe ? InternalConstants.PipeTokenDeliveryTcp : InternalConstants.PipeTokenDeliveryUdp; + var stream = SharerdMemoryStreamPool.RentStreamStatic(); - stream.Position32 = 0; + // destination already knows the public key KnownTypeSerializer.SerializePipeData(stream, data); msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); - msg.KeyValuePairs = new Dictionary(); + connection.SendAsyncMessage(to, msg); - if(chInfo.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = destinationDhPublicKey; + // requester will know from this message + data.DHPublic = destinationsDhPublicKey; + stream.Position32 = 0; + KnownTypeSerializer.SerializePipeData(stream, data); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); connection.SendAsyncMessage(from, msg); - if (chInfo.RequiresKeyExchange()) - msg.KeyValuePairs["DH"] = requesterDhPublicKey; - - connection.SendAsyncMessage(to, msg); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); } - private void HandleBadAck(MessageEnvelope message) + private void HandleBadAck() { lock (cancellationMutex) { + if (IsCompleted()) + return; + var msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionAckBad; connection.SendAsyncMessage(from, msg); @@ -156,6 +177,9 @@ private void HandleGoodAck(MessageEnvelope message) { lock (cancellationMutex) { + if (IsCompleted()) + return; + var msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionAckGood; connection.SendAsyncMessage(from, msg); diff --git a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs index 78cfaf9..1ea4250 100644 --- a/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs +++ b/NetworkLibrary/P2P/Components/HolePunch/KnownTypeSerializer.cs @@ -6,12 +6,62 @@ using NetworkLibrary.Utils; using System; using System.Collections.Generic; -using System.Reflection; namespace NetworkLibrary.P2P.Components.HolePunch { public class KnownTypeSerializer { + #region ClientPipeData + internal static void SerializeClientPipeData(PooledMemoryStream stream, ClientPipeData hpData) + { + byte index = 0; + int oldPos = stream.Position32; + stream.WriteByte(index); + + if (hpData.ChannelInfo != null) + { + SerializeChannelInfo(stream, hpData.ChannelInfo); + index = 1; + + } + if (hpData.DHPublic != null) + { + PrimitiveEncoder.WriteInt32(stream, hpData.DHPublic.Length); + stream.Write(hpData.DHPublic, 0, hpData.DHPublic.Length); + index += 2; + } + + var buf = stream.GetBuffer(); + buf[oldPos] = index; + + } + + + internal static ClientPipeData DeserializeClientPipeData(byte[] buffer, ref int offset) + { + var hpData = new ClientPipeData(); + var index = buffer[offset++]; + + + if ((index & 1) != 0) + { + hpData.ChannelInfo = DeserializeChannelInfo(buffer, ref offset); + } + if ((index & 1 << 1) != 0) + { + int Dhlen = PrimitiveEncoder.ReadInt32(buffer, ref offset); + if (Dhlen > 0) + { + hpData.DHPublic = ByteCopy.ToArray(buffer, offset, Dhlen); + offset += Dhlen; + } + else + hpData.DHPublic = new byte[0]; + } + + return hpData; + } + #endregion #region ClientHolepunchData @@ -36,7 +86,7 @@ internal static void SerializeHolepunchData(PooledMemoryStream stream, ClientHol if (hpData.DHPublic != null) { PrimitiveEncoder.WriteInt32(stream, hpData.DHPublic.Length); - stream.Write(hpData.DHPublic,0,hpData.DHPublic.Length); + stream.Write(hpData.DHPublic, 0, hpData.DHPublic.Length); index += 4; } @@ -48,7 +98,7 @@ internal static void SerializeHolepunchData(PooledMemoryStream stream, ClientHol internal static ClientHolepunchData DeserializeHolepunchData(byte[] buffer, ref int offset) { - var hpData = new ClientHolepunchData(); + var hpData = new ClientHolepunchData(); var index = buffer[offset++]; @@ -69,7 +119,7 @@ internal static ClientHolepunchData DeserializeHolepunchData(byte[] buffer, ref offset += Dhlen; } else - hpData.DHPublic = new byte[0]; + hpData.DHPublic = new byte[0]; } return hpData; @@ -183,30 +233,52 @@ internal static PeerStatus DeserializePeerStatus(byte[] buffer, ref int offset) internal static void SerializePipeData(PooledMemoryStream stream, PipeData pipeData) { - stream.Write(pipeData.Token, 0, PipeData.TokenLength); + byte index = 0; + int oldPos = stream.Position32; + stream.WriteByte(index); - if (pipeData.PipeEndpoints != null && pipeData.PipeEndpoints.Count > 0) + if (pipeData.Token != null) { - PrimitiveEncoder.WriteInt32(stream, pipeData.PipeEndpoints.Count); - foreach (EndpointData ep in pipeData.PipeEndpoints) - { - SerializeEndpointData(stream, ep); - } + PrimitiveEncoder.WriteInt32(stream, PipeData.TokenLength); + stream.Write(pipeData.Token, 0, PipeData.TokenLength); + index = 1; } - else - throw new InvalidOperationException("PipeData.PipeEndpoints is null or empty"); + if (pipeData.DHPublic != null) + { + PrimitiveEncoder.WriteInt32(stream, pipeData.DHPublic.Length); + stream.Write(pipeData.DHPublic, 0, pipeData.DHPublic.Length); + index += 2; + } + if (pipeData.PipeEndpoint != null) + { + SerializeEndpointData(stream, pipeData.PipeEndpoint); + index += 4; + } + + var buf = stream.GetBuffer(); + buf[oldPos] = index; } internal static PipeData DeserializePipeData(byte[] buffer, ref int offset) { PipeData pipeData = new PipeData(); - pipeData.Token = ByteCopy.ToArray(buffer, offset, PipeData.TokenLength); - offset += PipeData.TokenLength; + var index = buffer[offset++]; - int count = PrimitiveEncoder.ReadInt32(buffer, ref offset); - for (int i = 0; i < count; i++) + if ((index & 1) != 0) + { + int len = PrimitiveEncoder.ReadInt32(buffer, ref offset); + pipeData.Token = ByteCopy.ToArray(buffer, offset, len); + offset += len; + } + if ((index & 1 << 1) != 0) + { + int len = PrimitiveEncoder.ReadInt32(buffer, ref offset); + pipeData.DHPublic = ByteCopy.ToArray(buffer, offset, len); + offset += len; + } + if ((index & 1 << 2) != 0) { - pipeData.PipeEndpoints.Add(DeserializeEndpointData(buffer, ref offset)); + pipeData.PipeEndpoint = DeserializeEndpointData(buffer, ref offset); } return pipeData; diff --git a/NetworkLibrary/TCP/SSL/SslClient.cs b/NetworkLibrary/TCP/SSL/SslClient.cs index 22a3e13..054b162 100644 --- a/NetworkLibrary/TCP/SSL/SslClient.cs +++ b/NetworkLibrary/TCP/SSL/SslClient.cs @@ -104,7 +104,7 @@ public override Task ConnectAsyncAwaitable(string ip, int port) catch { } tcs.SetResult(false); } - }); + }, TaskScheduler.Default); if (!clientSocket.ConnectAsync(earg)) { diff --git a/NetworkLibrary/UDP/Reliable/Components/ReceiverModule.cs b/NetworkLibrary/UDP/Reliable/Components/ReceiverModule.cs index fa7b01f..638a6c7 100644 --- a/NetworkLibrary/UDP/Reliable/Components/ReceiverModule.cs +++ b/NetworkLibrary/UDP/Reliable/Components/ReceiverModule.cs @@ -156,6 +156,7 @@ private void ConsumerLoop(object state = null) { MiniLogger.Log(MiniLogger.LogLevel.Error, $"Critical errror occured on reliable udp receiver module: {ex.Message} Trace:{ex.StackTrace}"); + throw; } } } diff --git a/NetworkLibrary/UDP/Reliable/Components/SenderModule.cs b/NetworkLibrary/UDP/Reliable/Components/SenderModule.cs index 5c9a74d..8d36e19 100644 --- a/NetworkLibrary/UDP/Reliable/Components/SenderModule.cs +++ b/NetworkLibrary/UDP/Reliable/Components/SenderModule.cs @@ -373,6 +373,7 @@ private void StartProducer() { MiniLogger.Log(MiniLogger.LogLevel.Error, $"Error occured on rudp sender execution loop: {ex.Message}"); + throw; } } diff --git a/NetworkLibrary/UDP/Reliable/RudpClient.cs b/NetworkLibrary/UDP/Reliable/RudpClient.cs index 4b6d3a3..554bb69 100644 --- a/NetworkLibrary/UDP/Reliable/RudpClient.cs +++ b/NetworkLibrary/UDP/Reliable/RudpClient.cs @@ -62,7 +62,7 @@ public Task ConnectAsync(IPEndPoint ep) connected.TrySetException(new TimeoutException()); module.Close(); } - }); + }, TaskScheduler.Default); return connected.Task; } diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index 4ad493e..79872c3 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -248,8 +248,8 @@ void Channel_BytesReceived(byte[] buff, int offset, int count) { received.Enqueue(buff[offset]); - //if(received.Count == iter) - // tcs.TrySetResult(true); + if (received.Count == iter) + tcs.TrySetResult(true); } @@ -297,8 +297,10 @@ void Cl2_PeerConnected(IChannel obj) { var udpChannel = (UdpChannel)obj; udpChannel.OnBytesReceived += (b,o,c) => - { - received = c; tcs.SetResult(true); }; + { + received = c; + tcs.SetResult(true); + }; udpChannel.Start(); } @@ -349,7 +351,10 @@ void Cl2_PeerConnected(IChannel obj) { received = c; if(++cnt == 2) + { tcs.SetResult(true); + + } }; udpChannel.Start(); } @@ -357,6 +362,8 @@ void Cl2_PeerConnected(IChannel obj) var ss = tcs.Task.Result; Assert.AreEqual(received, data.Length); + Thread.Sleep(1000); + } @@ -525,6 +532,7 @@ public void Timesync() { using var server = ArrangeServer(); + Thread.Sleep(1337); var cl1 = GetClient(); Thread.Sleep(1337); var cl2 = GetClient(); From d7f7ff9408fdf7c6039af6bba806fc90d453b97a Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Thu, 17 Apr 2025 10:51:18 +0200 Subject: [PATCH 24/27] logger and injection of logger --- .../Channels/Components/ChannelFactory.cs | 27 +-- .../Components/EphemeralKeyManager.cs | 8 + .../Channels/Components/KeepAlive.cs | 2 - .../Channels/Components/UdpChannelBase.cs | 15 +- .../Channels/SecureTcpChannel.cs | 18 +- .../Channels/SecureUdpChannel.cs | 7 +- .../DistributedP2P/Channels/TcpChannel.cs | 22 ++- .../DistributedP2P/Channels/UdpChannel.cs | 46 +++--- .../Client/DistributedLobbyClient.cs | 42 +++-- .../StateManagement/ClientConnectionState.cs | 3 +- .../Client/StateManagement/ClientPipeState.cs | 49 +++--- .../ClientSequentialTcpHolepunchState.cs | 49 +++--- .../ClientSimultaneousTcpHolepunchState.cs | 45 +++-- .../ClientUdpHolepunchState.cs | 37 ++--- .../Components/ConversationStateBase.cs | 8 +- .../DistributedP2P/Components/ILogger.cs | 14 ++ .../DistributedP2P/Components/Logger.cs | 155 ++++++++++++++++++ .../DistributedP2P/Components/StateManager.cs | 11 +- .../DistributedP2P/Components/TimeSync.cs | 1 - .../Server/DistributedLobbyServer.cs | 19 ++- .../StateManagement/ServerConnectionState.cs | 2 +- .../Server/StateManagement/ServerPipeState.cs | 6 +- .../ServerSequentialTcpHolepunchState.cs | 4 +- .../ServerSimultaneousTcpHolepunchState.cs | 2 +- .../ServerUdpHolepunchState.cs | 2 +- .../DistributedP2P/DistP2PServerclientTest.cs | 101 ++++++------ 26 files changed, 460 insertions(+), 235 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Components/ILogger.cs create mode 100644 NetworkLibrary/DistributedP2P/Components/Logger.cs diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs index 401e393..86a9d3d 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/ChannelFactory.cs @@ -8,12 +8,13 @@ using NetworkLibrary.Components.Crypto.KeyDerivation; using NetworkLibrary.Components.Crypto; using NetworkLibrary.TCP.AES; +using NetworkLibrary.DistributedP2P.Components; namespace NetworkLibrary.DistributedP2P.Channels.Components { internal class ChannelFactory { - public static IChannel CreateChannel(ClientSimultaneousTcpHolepunchState TcpHpstate, bool isInitiator) + public static IChannel CreateChannel(ClientSimultaneousTcpHolepunchState TcpHpstate, bool isInitiator, ILogger logger) { ChannelInfo info = TcpHpstate.ChannelInfo; Socket connectedSocket = TcpHpstate.Socket; @@ -21,10 +22,10 @@ public static IChannel CreateChannel(ClientSimultaneousTcpHolepunchState TcpHpst IPEndPoint endpoint = TcpHpstate.SuccesfulEndpoint; ChannelType channelType = TcpHpstate.ChannelInfo.ChannelType; - return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator); + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator, logger); } - public static IChannel CreateChannel(ClientSequentialTcpHolepunchState TcpHpstate, bool isInitiator) + public static IChannel CreateChannel(ClientSequentialTcpHolepunchState TcpHpstate, bool isInitiator, ILogger logger) { ChannelInfo info = TcpHpstate.ChannelInfo; Socket connectedSocket = TcpHpstate.Socket; @@ -32,10 +33,10 @@ public static IChannel CreateChannel(ClientSequentialTcpHolepunchState TcpHpstat IPEndPoint endpoint = TcpHpstate.SuccesfulEndpoint; ChannelType channelType = TcpHpstate.ChannelInfo.ChannelType; - return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator); + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator, logger); } - public static IChannel CreateChannel(ClientUdpHolepunchState udpHpstate, bool isInitiator) + public static IChannel CreateChannel(ClientUdpHolepunchState udpHpstate, bool isInitiator, ILogger logger) { ChannelInfo info = udpHpstate.ChannelInfo; Socket connectedSocket = udpHpstate.Socket; @@ -43,10 +44,10 @@ public static IChannel CreateChannel(ClientUdpHolepunchState udpHpstate, bool is IPEndPoint endpoint = udpHpstate.SuccesfulEndpoint; ChannelType channelType = udpHpstate.ChannelInfo.ChannelType; - return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator); + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator, logger); } - public static IChannel CreateChannel(ClientPipeState pipeState, bool isInitiator) + public static IChannel CreateChannel(ClientPipeState pipeState, bool isInitiator, ILogger logger) { ChannelInfo info = pipeState.ChannelInfo; Socket connectedSocket = pipeState.ConnectedSocket; @@ -54,10 +55,10 @@ public static IChannel CreateChannel(ClientPipeState pipeState, bool isInitiator IPEndPoint endpoint = pipeState.SuccesfullEndpoint.ToIpEndpoint(); ChannelType channelType = pipeState.ChannelInfo.ChannelType; - return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator); + return CreateChannel(info, connectedSocket, sharedSecret, endpoint, channelType, isInitiator, logger); } - public static IChannel CreateChannel(ChannelInfo info, Socket connectedSocket, byte[] sharedSecret, IPEndPoint endpoint, ChannelType channelType, bool isInitiator) + public static IChannel CreateChannel(ChannelInfo info, Socket connectedSocket, byte[] sharedSecret, IPEndPoint endpoint, ChannelType channelType, bool isInitiator, ILogger logger) { IChannel channel = null; @@ -65,21 +66,21 @@ public static IChannel CreateChannel(ChannelInfo info, Socket connectedSocket, b { case ChannelType.Tcp: - channel = new TcpChannel(info, connectedSocket); + channel = new TcpChannel(info, connectedSocket, logger); break; case ChannelType.SecureTcp: var symetricKey = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); var algo = AesManager.Create(AesMode.GCM, symetricKey, HKDFLite.DeriveKey(info.ChannelName,outputLength:16) ); - channel = new SecureTcpChannel(algo, info, connectedSocket, isInitiator); + channel = new SecureTcpChannel(algo, info, connectedSocket, isInitiator, logger); break; case ChannelType.Udp: - channel = new UdpChannel(connectedSocket, endpoint, info); + channel = new UdpChannel(connectedSocket, endpoint, info, logger); break; case ChannelType.SecureUdp: var symetricKey2 = HKDFLite.DeriveKey(sharedSecret, outputLength: 16); var algo2 = AesManager.Create(AesMode.GCM, symetricKey2, HKDFLite.DeriveKey(info.ChannelName,outputLength: 16)); - channel = new SecureUdpChannel(connectedSocket, endpoint, algo2, info, isInitiator); + channel = new SecureUdpChannel(connectedSocket, endpoint, algo2, info, isInitiator, logger); break; } diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs index 57c4bcb..9efc3e7 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/EphemeralKeyManager.cs @@ -143,6 +143,14 @@ internal void Close() internal void SetKeyRotationTime(int timeMs) { TimerService.CancelTimeout(timerGuid); + if (timeMs < 0) + { + keyRotationPeriod = timeMs; + return; + } + if (timeMs < 1000) + timeMs = 1000; + keyRotationPeriod = timeMs; TimedKeyExchange(); } diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs index f88061b..5cdc07a 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/KeepAlive.cs @@ -48,13 +48,11 @@ private void SendKeepAlive() { r.GetBytes(innerBuff, 0, 16); SendData?.Invoke(MessageFlags.KeepAliveMessage, innerBuff, 0, 16); - //Console.WriteLine("Keep alive sent"); } private void HandleKeepAlive(byte[] buffer, int offset, int count) { lastReceived = DateTime.Now; - //Console.WriteLine("Keep alive received"); } internal void Close() diff --git a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs index 1fcee5a..494f529 100644 --- a/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs +++ b/NetworkLibrary/DistributedP2P/Channels/Components/UdpChannelBase.cs @@ -1,4 +1,5 @@ using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; using System; using System.Net; using System.Net.Sockets; @@ -11,19 +12,20 @@ internal class UdpChannelBase : IDisposable private Socket udpSocket; private SocketAsyncEventArgs receiveArgs; private readonly IPEndPoint associatedEndpoint; + private readonly ILogger logger; int disposed = 0; public ChannelInfo Info { get; private set; } public event Action OnBytesReceived; - public Action LogAvailable; public event Action OnDisconnected; - public UdpChannelBase(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) + public UdpChannelBase(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info, ILogger logger) { this.udpSocket = udpSocket; associatedEndpoint = receiveEp; Info = info; + this.logger = logger; } public void Start() @@ -73,7 +75,7 @@ private void OnReceiveCompleted(object sender, SocketAsyncEventArgs e) } catch (Exception ex) { - Log($"{ex.Message}\n{ex.StackTrace}"); + Log(LogType.Debug,$"{ex.Message}\n{ex.StackTrace}"); CloseChannel(); throw; } @@ -105,14 +107,14 @@ private void ProcessReceivedData(byte[] buffer, int offset, int bytesTransferred private void HandleSocketError(SocketError error) { if (error != SocketError.Shutdown) - Log($"Socket error occurred: {error}"); + Log(LogType.Debug,$"Socket error occurred: {error}"); CloseChannel(); } - private void Log(string err) + private void Log(LogType type,string err) { - LogAvailable?.Invoke(err); + logger?.Log(type, err); } public virtual void CloseChannel() @@ -145,7 +147,6 @@ public virtual void Dispose() } } catch { } - LogAvailable = null; OnBytesReceived = null; OnDisconnected = null; } diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs index d7aeff6..685290c 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureTcpChannel.cs @@ -9,7 +9,9 @@ using NetworkLibrary.Components.Crypto.Algorithms; using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.TCP.AES; +using NetworkLibrary.Utils; namespace NetworkLibrary.DistributedP2P.Channels { @@ -25,7 +27,7 @@ public class SecureTcpChannel : TcpChannel /// public int KeyRotationPeriodMs { get; private set; } = 1000; - public SecureTcpChannel(IAesAlgorithm algo,ChannelInfo info, Socket connectedSocket, bool isInitiator) : base(info, connectedSocket) + public SecureTcpChannel(IAesAlgorithm algo,ChannelInfo info, Socket connectedSocket, bool isInitiator, ILogger logger) : base(info, connectedSocket, logger) { this.isInitiator = isInitiator; encBuff = BufferPool.RentBuffer(128000); @@ -78,13 +80,25 @@ protected override void HandleReceivedBytes(byte[] buffer, int offset, int count keyManager.GetAlgorithm(keyNo, out var algo); EnsureCapacityDec(count); - count = algo.DecryptInto(buffer, offset, count, decBuff, 0); // not sure if try catch this. + try + { + count = algo.DecryptInto(buffer, offset, count, decBuff, 0); // not sure if try catch this. + } + catch(Exception e) + { + Log(e); + CloseChannel(); + return; + } + buffer = decBuff; offset = 0; HandleReceivedMessage(buffer, offset, count, flag); } + + protected override void HandleReceivedMessage(byte[] buffer, int offset, int count, MessageFlags flag) { switch (flag) diff --git a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs index 1c9d795..a7de4f9 100644 --- a/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/SecureUdpChannel.cs @@ -2,6 +2,7 @@ using NetworkLibrary.Components.Crypto.Algorithms; using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.UDP.Reliable.Components; using NetworkLibrary.Utils; using System; @@ -18,7 +19,7 @@ public class SecureUdpChannel : UdpChannel public int KeyRotationPeriodMs { get; private set; } = 1000;//every minute - public SecureUdpChannel(Socket udpSocket, IPEndPoint receiveEp, IAesAlgorithm algo, ChannelInfo info, bool isInitiator) : base(udpSocket, receiveEp, info) + public SecureUdpChannel(Socket udpSocket, IPEndPoint receiveEp, IAesAlgorithm algo, ChannelInfo info, bool isInitiator, ILogger logger) : base(udpSocket, receiveEp, info, logger) { keyManager = new EphemeralKeyManager(algo, isInitiator ? KeyRotationPeriodMs : -1); keyManager.SendData += SendKeyMsg; @@ -62,7 +63,7 @@ protected override void BytesReceived(byte[] buffer, int offset, int count) } catch { - Log("Decryption failed"); + Log(LogType.Error, "Decryption failed"); return; } @@ -94,7 +95,7 @@ private void HandleKeyMessage(byte[] buffer, int offset, int count, MessageFlags } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); } diff --git a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs index 8c6d029..ef58de3 100644 --- a/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/TcpChannel.cs @@ -1,6 +1,7 @@ using NetworkLibrary.Components; using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; using System; using System.Net.Sockets; using System.Threading; @@ -17,6 +18,7 @@ public class TcpChannel : IChannel public event Action OnDisconnected; private readonly Socket connectedSocket; + private readonly ILogger logger; private int totalBytesReceived; private SocketAsyncEventArgs receiveArgs; private int Closing = 0; @@ -35,11 +37,11 @@ public class TcpChannel : IChannel private KeepAlive keepAlive; private Pinger pinger; - public TcpChannel(ChannelInfo info, Socket connectedSocket) + public TcpChannel(ChannelInfo info, Socket connectedSocket, ILogger logger) { Info = info; this.connectedSocket = connectedSocket; - + this.logger = logger; InitializeReceiver(); sendArgs = new SocketAsyncEventArgs(); @@ -166,7 +168,7 @@ private void Sent(object sender, SocketAsyncEventArgs e) } else if (e.BytesTransferred == 0) { - Log("0 bytes Sent"); + Log(LogType.Error, "0 bytes Sent"); CloseChannel(); return; } @@ -212,7 +214,7 @@ private void Sent(object sender, SocketAsyncEventArgs e) } catch (Exception ex) { - Log(ex.StackTrace); + Log(ex); CloseChannel(); } @@ -351,13 +353,19 @@ private void HandleReceived(byte[] buffer, int offset, int bytesTransferred) protected void ErrorAndEnd(string errMsg) { - Log(errMsg); + Log(LogType.Debug,errMsg); CloseChannel(); } - private void Log(string v) + + protected virtual void Log(LogType logType, string v) + { + logger?.Log(logType, v); + } + + protected virtual void Log(Exception e) { - Console.WriteLine(v); + logger?.Log(e); } diff --git a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs index 5d769c2..e8fcf75 100644 --- a/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs +++ b/NetworkLibrary/DistributedP2P/Channels/UdpChannel.cs @@ -1,5 +1,6 @@ using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Client; +using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.UDP.Jumbo; using NetworkLibrary.UDP.Reliable.Components; using NetworkLibrary.Utils; @@ -28,14 +29,14 @@ public class UdpChannel : IChannel private int isClosed = 0; private int isDisposed = 0; + private readonly ILogger logger; - - public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info) + public UdpChannel(Socket udpSocket, IPEndPoint receiveEp, ChannelInfo info, ILogger logger) { Info = info; - innerchannel = new UdpChannelBase(udpSocket, receiveEp, info); - innerchannel.LogAvailable += Log; + this.logger = logger; + innerchannel = new UdpChannelBase(udpSocket, receiveEp, info, logger); JumboUdp.SendToSocket = SendJumboSegment; JumboUdp.MessageReceived = HandleMessage; @@ -137,7 +138,7 @@ private void HandleMessage(byte[] buffer, int offset, int count) } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); throw; } @@ -151,7 +152,7 @@ private void HandleJumboSegment(byte[] buffer, int offset, int count) } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); } } @@ -164,7 +165,7 @@ private void SendJumboSegment(byte[] arg1, int arg2, int arg3) } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); } @@ -178,20 +179,20 @@ private void HandleRudpSegment(byte[] buffer, int offset, int count) } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); } } private void HandleKeepAliveMessage(byte[] buffer, int offset, int count, MessageFlags flag) { - try - { + try + { keepAlive.HandleMessage(flag, buffer, offset, count); } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); } } @@ -204,7 +205,7 @@ private void HandleIncomingInternalRudpSegment(byte[] buffer, int offset, int co } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); } } @@ -217,7 +218,7 @@ private void HandlePingMessage(byte[] buffer, int offset, int count, MessageFlag } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); } @@ -231,11 +232,11 @@ private void SendRudpSegment(ReliableModule module, byte[] buffer, int offset, i } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); } } - + private void SendInternalRudpSegment(ReliableModule module, byte[] buffer, int offset, int count) { @@ -245,7 +246,7 @@ private void SendInternalRudpSegment(ReliableModule module, byte[] buffer, int o } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); } @@ -303,7 +304,7 @@ protected void SendInternal(byte[] bytes, int offset, int count) } catch (Exception e) { - Log($"{e.Message}\n{e.StackTrace}"); + Log(e); CloseChannel(); throw; } @@ -333,7 +334,7 @@ protected void HandleDisconnect() { if (Interlocked.CompareExchange(ref isClosed, 1, 0) == 0) { - Log("Udp Channel disconnected"); + Log(LogType.Debug,"Udp Channel disconnected"); OnDisconnected?.Invoke(); Dispose(); } @@ -361,9 +362,14 @@ protected virtual void ReleseResources() } - protected virtual void Log(string v) + protected virtual void Log(LogType logType, string v) + { + logger?.Log(logType, v); + } + + protected virtual void Log(Exception e) { - Console.WriteLine(v); + logger?.Log(e); } } } diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index e685ea7..2cfe735 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -25,7 +25,7 @@ namespace NetworkLibrary.DistributedP2P.Client IClientDbConnection clientDbConnector; IClientAuthenticationProvider clientAuthProvider; SecureMessageClient sslClient; - StateManager stateManager = new StateManager(); + StateManager stateManager; TimeSync timeSync; private ConcurrentDictionary onlinePeers = new ConcurrentDictionary(); @@ -50,13 +50,19 @@ public bool IsConnected private EndpointData serverEndpoint= new EndpointData(); bool isDisposed = false; + ILogger logger; public DistributedLobbyClient(IClientDbConnection clientDbConnector, IClientAuthenticationProvider clientAuthProvider, + ILogger logger, X509Certificate2 certificate = null) { this.clientDbConnector = clientDbConnector; this.clientAuthProvider = clientAuthProvider; + this.logger=logger; + + stateManager = new StateManager(logger); + sslClient = new SecureMessageClient(certificate); sslClient.OnMessageReceived += HandleServerMsg; sslClient.OnDisconnected += HandleDisconnected; @@ -80,7 +86,7 @@ public async Task ConnectAsync(string ip, int port) timeSync.SetEndpoint(serverEndpoint); Guid conversationId = Guid.NewGuid(); - var conState = new ClientConnectionState(conversationId, this, clientDbConnector, authToken); + var conState = new ClientConnectionState(conversationId, this, clientDbConnector, authToken, logger); stateManager.RegisterState(conState); conState.Start(); @@ -138,7 +144,7 @@ private void HandleServerMsg(MessageEnvelope envelope) case InternalConstants.PipeRequestTcp: case InternalConstants.PipeRequestUdp: - var pipeState = new ClientPipeState(envelope, this, serverEndpoint); + var pipeState = new ClientPipeState(envelope, this, serverEndpoint, logger); pipeState.OnComplete += HandlePipeCreated; stateManager.RegisterState(pipeState); pipeState.HandleMessage(envelope); @@ -212,7 +218,7 @@ public Dictionary GetPeerList() public async Task OpenRelayChannel(Guid destinationPeer, ChannelInfo Info) { - var pipeState = new ClientPipeState(Guid.NewGuid(), this, serverEndpoint, Info); + var pipeState = new ClientPipeState(Guid.NewGuid(), this, serverEndpoint, Info, logger); stateManager.RegisterState(pipeState); pipeState.Start(destinationPeer); @@ -220,7 +226,7 @@ public async Task OpenRelayChannel(Guid destinationPeer, ChannelInfo I if (pipeState.IsSuccesful) { - IChannel channel = ChannelFactory.CreateChannel(pipeState, true); + IChannel channel = ChannelFactory.CreateChannel(pipeState, true, logger); return channel; } return null; @@ -231,7 +237,7 @@ private void HandlePipeCreated(IConversationState state) if (state.IsSuccesful) { var pipeState = (ClientPipeState)state; - IChannel channel = ChannelFactory.CreateChannel(pipeState, false); + IChannel channel = ChannelFactory.CreateChannel(pipeState, false, logger); if (channel != null) PeerConnected?.Invoke(channel); @@ -246,7 +252,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp if(info.ChannelType == ChannelType.Udp || info.ChannelType == ChannelType.SecureUdp) { - var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info); + var state = new ClientUdpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info, logger); stateManager.RegisterState(state); state.Start(); @@ -254,7 +260,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp if (state.IsSuccesful) { - return ChannelFactory.CreateChannel(state, true); + return ChannelFactory.CreateChannel(state, true, logger); } return null; } @@ -262,7 +268,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp { if (strategy == TcpHolePunchStrategy.Sequential) { - var state = new ClientSequentialTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info); + var state = new ClientSequentialTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info, logger); stateManager.RegisterState(state); state.Start(); @@ -270,13 +276,13 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp if (state.IsSuccesful) { - return ChannelFactory.CreateChannel(state, true); + return ChannelFactory.CreateChannel(state, true, logger); } return null; } else { - var state = new ClientSimultaneousTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info); + var state = new ClientSimultaneousTcpHolepunchState(Guid.NewGuid(), destination, this, serverEndpoint, DiscoveryServerEndpoint, info, logger); stateManager.RegisterState(state); state.Start(); @@ -284,7 +290,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp if (state.IsSuccesful) { - return ChannelFactory.CreateChannel(state, true); + return ChannelFactory.CreateChannel(state, true, logger); } return null; } @@ -295,7 +301,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp private void ManageUdpHolepunchRequest(MessageEnvelope envelope) { - var state = new ClientUdpHolepunchState(envelope.MessageId, envelope.From, this,serverEndpoint,DiscoveryServerEndpoint, null); + var state = new ClientUdpHolepunchState(envelope.MessageId, envelope.From, this,serverEndpoint,DiscoveryServerEndpoint, null, logger); stateManager.RegisterState(state); state.OnComplete += State_OnComplete; state.HandleMessage(envelope); @@ -304,7 +310,7 @@ void State_OnComplete(IConversationState obj) { if (obj.IsSuccesful) { - var ch = ChannelFactory.CreateChannel(state, false); + var ch = ChannelFactory.CreateChannel(state, false, logger); PeerConnected?.Invoke(ch); } @@ -314,7 +320,7 @@ void State_OnComplete(IConversationState obj) private void ManageSimyltaneousTcpHolepunchReq(MessageEnvelope envelope) { - var state = new ClientSimultaneousTcpHolepunchState(envelope.MessageId, envelope.From, this, serverEndpoint, DiscoveryServerEndpoint, null); + var state = new ClientSimultaneousTcpHolepunchState(envelope.MessageId, envelope.From, this, serverEndpoint, DiscoveryServerEndpoint, null, logger); stateManager.RegisterState(state); state.OnComplete += State_OnComplete; state.HandleMessage(envelope); @@ -323,7 +329,7 @@ void State_OnComplete(IConversationState obj) { if (obj.IsSuccesful) { - var ch = ChannelFactory.CreateChannel(state, false); + var ch = ChannelFactory.CreateChannel(state, false, logger); PeerConnected?.Invoke(ch); } } @@ -331,7 +337,7 @@ void State_OnComplete(IConversationState obj) private void ManageSequentialTcpHolepunchReq(MessageEnvelope envelope) { - var state = new ClientSequentialTcpHolepunchState(envelope.MessageId, envelope.From, this, serverEndpoint, DiscoveryServerEndpoint, null); + var state = new ClientSequentialTcpHolepunchState(envelope.MessageId, envelope.From, this, serverEndpoint, DiscoveryServerEndpoint, null, logger); stateManager.RegisterState(state); state.OnComplete += State_OnComplete; state.HandleMessage(envelope); @@ -340,7 +346,7 @@ void State_OnComplete(IConversationState obj) { if (obj.IsSuccesful) { - var ch = ChannelFactory.CreateChannel(state, false); + var ch = ChannelFactory.CreateChannel(state, false, logger); PeerConnected?.Invoke(ch); } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs index b92e4c6..908dfd3 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs @@ -11,12 +11,11 @@ internal class ClientConnectionState : ConversationStateBase private readonly IDistributedConnection connection; private readonly IClientDbConnection clientDbConnector; private readonly IClientAuthenticationToken authToken; - private TaskCompletionSource timeSyncComplete = new TaskCompletionSource(); public Guid SessionId { get; private set; } public int EDSPort { get; private set; } - public ClientConnectionState(Guid stateId, IDistributedConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken) : base(stateId, 20000) + public ClientConnectionState(Guid stateId, IDistributedConnection connection, IClientDbConnection clientDbConnector, IClientAuthenticationToken authToken, ILogger logger) : base(stateId, 20000, logger) { this.connection = connection; this.clientDbConnector = clientDbConnector; diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index 706ed72..8c86d71 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -28,7 +28,7 @@ internal class ClientPipeState : ConversationStateBase private DiffieHellman df; private bool isInitiator; - public ClientPipeState(Guid stateId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info) : base(stateId, 20000) + public ClientPipeState(Guid stateId, IDistributedConnection connection, EndpointData serverEndpoint, ChannelInfo info, ILogger logger) : base(stateId, 20000, logger) { this.connection = connection; this.serverEndpoint = serverEndpoint; @@ -36,11 +36,10 @@ public ClientPipeState(Guid stateId, IDistributedConnection connection, Endpoint isInitiator = true; } - public ClientPipeState(MessageEnvelope message, IDistributedConnection connection, EndpointData serverEndpoint) : base(message.MessageId) + public ClientPipeState(MessageEnvelope message, IDistributedConnection connection, EndpointData serverEndpoint, ILogger logger) : base(message.MessageId, 20000, logger) { this.connection = connection; this.serverEndpoint = serverEndpoint; - } public void Start(Guid destinationPeer) @@ -61,7 +60,7 @@ public void Start(Guid destinationPeer) connection.SendAsyncMessage(msg); SharerdMemoryStreamPool.ReturnStreamStatic(stream); - Log("Requested Pipe"); + Log(LogType.Debug, "Requested Pipe"); } public override void HandleMessage(MessageEnvelope message) @@ -94,7 +93,7 @@ public override void HandleMessage(MessageEnvelope message) private void HandleConnectionRequest(MessageEnvelope message) { - Log("Connection Request Received"); + Log(LogType.Debug, "Connection Request Received"); int offs = message.PayloadOffset; var pipeData = KnownTypeSerializer.DeserializeClientPipeData(message.Payload, ref offs); ChannelInfo = pipeData.ChannelInfo; @@ -123,7 +122,7 @@ private void HandleConnectionRequest(MessageEnvelope message) private async void HandlePipeTokenTcp(MessageEnvelope message) { - Log("Handling Tcp Token"); + Log(LogType.Debug, "Handling Tcp Token"); try { int off = message.PayloadOffset; @@ -155,13 +154,13 @@ private async void HandlePipeTokenTcp(MessageEnvelope message) //wait a data to come //then send ack - Log($"Failed to exchange Tcp Token"); + Log(LogType.Warning, $"Failed to exchange Tcp Token"); OnConnectionFail(); return; } catch (Exception e) { - Log($"An Error occured while handling Tcp Token{e.Message}\n{e.StackTrace}"); + Log(LogType.Exception, $"An Error occured while handling Tcp Token{e.Message}\n{e.StackTrace}"); OnConnectionFail(); } @@ -183,7 +182,7 @@ private async Task TryConnectWithTimeout(EndpointData endpoint, int time if (completedTask == timeoutTask) { - Log("Connection Failed"); + Log(LogType.Warning, "Connection Failed"); try { clientSocket.Close(); clientSocket.Dispose(); } catch { } return null; } @@ -195,7 +194,7 @@ private async Task TryConnectWithTimeout(EndpointData endpoint, int time } else { - Log("Connection Failed"); + Log(LogType.Warning, "Connection Failed"); try { clientSocket.Close(); clientSocket.Dispose(); } catch { } return null; } @@ -237,7 +236,7 @@ private async Task TokenExchange(Socket connectedSocket, byte[] token, int int bytesSent = await connectedSocket.SendAsync(new ArraySegment(token), SocketFlags.None); if (bytesSent != token.Length) { - Log("Tcp Token Send Failure"); + Log(LogType.Warning, "Tcp Token Send Failure"); return false; } @@ -249,7 +248,7 @@ private async Task TokenExchange(Socket connectedSocket, byte[] token, int if (completedTask == timeoutTask) { - Log("Tcp Token Response Timeout"); + Log(LogType.Warning, "Tcp Token Response Timeout"); return false; } @@ -259,7 +258,7 @@ private async Task TokenExchange(Socket connectedSocket, byte[] token, int } catch (Exception e) { - Log($"An Error occured while exchanging Tcp Token{e.Message}\n{e.StackTrace}"); + Log(LogType.Exception, $"An Error occured while exchanging Tcp Token{e.Message}\n{e.StackTrace}"); return false; } } @@ -290,7 +289,7 @@ private async void HandlePipeTokenUdp(MessageEnvelope message) bool success = await UdpTokenExchange(connected, pipeData.Token, endpoint.ToIpEndpoint()); if (success) { - Log($"Connected"); + Log(LogType.Debug, $"Connected"); OnConnectionSuccessful(endpoint, connected); return; } @@ -304,13 +303,13 @@ private async void HandlePipeTokenUdp(MessageEnvelope message) //then send ack - Log($"Failed to exchange Udp Token"); + Log(LogType.Warning, $"Failed to exchange Udp Token"); OnConnectionFail(); return; } catch (Exception e) { - Log($"An Error occured while handling Udp Token{e.Message}\n{e.StackTrace}"); + Log(LogType.Exception, $"An Error occured while handling Udp Token{e.Message}\n{e.StackTrace}"); OnConnectionFail(); } @@ -328,7 +327,7 @@ private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndP if (await Task.WhenAny(sendTask, sendTimeout) == sendTimeout || sendTask.Result != token.Length) { - Log("Udp Token Send Timeout"); + Log(LogType.Warning, "Udp Token Send Timeout"); return false; } @@ -338,7 +337,7 @@ private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndP if (await Task.WhenAny(receiveTask, receiveTimeout) == receiveTimeout) { - Log("Udp Token Receive Timeout"); + Log(LogType.Exception,"Udp Token Receive Timeout"); return false; } @@ -346,7 +345,7 @@ private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndP } catch (Exception e) { - Log($"An Error occured while exchanging Udp Token{e.Message}\n{e.StackTrace}"); + Log(LogType.Exception,$"An Error occured while exchanging Udp Token{e.Message}\n{e.StackTrace}"); return false; } @@ -361,7 +360,7 @@ private void OnConnectionSuccessful(EndpointData endpoint, Socket socket) this.ConnectedSocket = socket; this.SuccesfullEndpoint = endpoint; - Log("Completed"); + Log(LogType.Debug, "Completed"); var msg = CreateEnvelope(); msg.Header = InternalConstants.ConnectionAckGood; @@ -374,7 +373,7 @@ private void OnConnectionFail() msg.Header = InternalConstants.ConnectionAckBad; connection.SendAsyncMessage(msg); - Log("Failed"); + Log(LogType.Debug,"Failed"); Completed(false); } @@ -401,11 +400,11 @@ private ClientPipeData GetPipeData() } return hpd; } - protected override void Log(string log) + protected override void Log(LogType type,string log) { - //return; - string prefix = isInitiator ? "A: " : "B: "; - base.Log(prefix + log); + string prefix = $"[PipeState]: "; + prefix += isInitiator ? "A: " : "B: "; + base.Log(type,prefix + log); } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs index 03ea614..514e6d5 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs @@ -32,7 +32,6 @@ internal class ClientSequentialTcpHolepunchState : ConversationStateBase private byte[] othersPublicKey; public byte[] SharedSecret; public ChannelInfo ChannelInfo; - private Socket listeningSocket; private Socket acceptedSocket; private Socket connectedSocket; @@ -53,7 +52,7 @@ internal class ClientSequentialTcpHolepunchState : ConversationStateBase private bool IsEstablished => Interlocked.CompareExchange(ref established, 0, 0) == 1; - public ClientSequentialTcpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerEp, ChannelInfo info) : base(stateId, 10000) + public ClientSequentialTcpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerEp, ChannelInfo info, ILogger logger) : base(stateId, 10000, logger) { this.destId = destId; this.connection = connection; @@ -66,10 +65,10 @@ public ClientSequentialTcpHolepunchState(Guid stateId, Guid destId, IDistributed public async void Start() { isInitiator = true; - Log(StateId.ToString()); + Log(LogType.Debug, StateId.ToString()); await BindPort(); - Log($"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); + Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); var msg = CreateEnvelope(); msg.To = destId; @@ -114,16 +113,16 @@ public override void HandleMessage(MessageEnvelope message) // the destination peer of hp private async void HandleRemoteHpRequest(MessageEnvelope message) { - Log(StateId.ToString()); + Log(LogType.Debug,StateId.ToString()); int offs = message.PayloadOffset; var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); ChannelInfo = hpData.ChannelInfo; await BindPort(); - Log($"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); + Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); StartTcpListener(); - Log("listening"); + Log(LogType.Debug, "listening"); var msg = CreateEnvelope(); msg.To = destId; @@ -234,7 +233,7 @@ private void Swap() { if (swapCnt++ > 3) { - Log("Max attempts reached"); + Log(LogType.Debug, "Max attempts reached"); Cancel(); return; } @@ -243,13 +242,13 @@ private void Swap() if (isListening) { - Log("Swapping to Sender"); + Log(LogType.Debug, "Swapping to Sender"); StopListener(); ThreadPool.UnsafeQueueUserWorkItem(_ => TryPunch(), null); } else { - Log("Swapping to Listener"); + Log(LogType.Debug, "Swapping to Listener"); StartTcpListener(); } } @@ -260,7 +259,7 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) try { - Log("Connecting to " + endpoint.ToIpEndpoint().ToString()); + Log(LogType.Debug,"Connecting to " + endpoint.ToIpEndpoint().ToString()); connectSocket.Bind(selfLocalEp); var connectTask = connectSocket.ConnectAsync(endpoint.ToIpEndpoint()); @@ -273,13 +272,13 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) throw connectTask.Exception; } HandleConnectedSocket(connectSocket); - Log($"Successfully connected to {endpoint.ToIpEndpoint()}"); + Log(LogType.Debug, $"Successfully connected to {endpoint.ToIpEndpoint()}"); return true; } else { - Log($"Connection to {endpoint.ToIpEndpoint()} timed out after {timeoutMs}ms"); + Log(LogType.Debug, $"Connection to {endpoint.ToIpEndpoint()} timed out after {timeoutMs}ms"); connectSocket.Close(); connectSocket.Dispose(); return false; @@ -287,7 +286,7 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) } catch (Exception ex) { - Log($"Connect attempt to {endpoint.ToIpEndpoint()} failed: {ex.Message}"); + Log(LogType.Debug, $"Connect attempt to {endpoint.ToIpEndpoint()} failed: {ex.Message}"); connectSocket.Close(); return false; } @@ -302,7 +301,7 @@ private async Task BindPort() var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket, discoveryServerEp.ToIpEndpoint(), 5000); if (remoteEp == null) { - Log("Failed to get public endpoint"); + Log(LogType.Warning, "Failed to get public endpoint"); remoteEp = new EndpointData("0.0.0.0", selfLocalEp.Port); } @@ -351,7 +350,7 @@ private void AcceptCallback(IAsyncResult ar) } catch (Exception e) { - Log("Failed Accept: " + e.Message); + Log(LogType.Debug, "Failed Accept: " + e.Message); } } @@ -396,7 +395,7 @@ private void HandleAcceptedSocket(Socket socket) if (Interlocked.Exchange(ref accepted, 1) == 1) return; - Log($"Successfully accepted {(IPEndPoint)socket.RemoteEndPoint}"); + Log(LogType.Debug,$"Successfully accepted {(IPEndPoint)socket.RemoteEndPoint}"); acceptedSocket = socket; @@ -410,7 +409,7 @@ private void HandleAcceptedSocket(Socket socket) private void HandleFailure() { - Log("Failed Punch"); + Log(LogType.Debug, "Failed Punch"); Completed(false); } @@ -436,12 +435,12 @@ private void SignalCompletionCondition() if (Socket == null) { - Log("Failed to get socket"); + Log(LogType.Warning, "Failed to get socket"); Completed(false); return; } SuccesfulEndpoint = (IPEndPoint)Socket.RemoteEndPoint; - Log("Punched"); + Log(LogType.Debug, "Punched"); Completed(true); } } @@ -453,7 +452,7 @@ public override void Cancel() { if (!IsCompleted()) { - Log("Cancelled"); + Log(LogType.Debug, "Cancelled"); var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchFail; connection.SendAsyncMessage(msg); @@ -484,11 +483,11 @@ protected override void Completed(bool succes) } - protected override void Log(string log) + protected override void Log(LogType type,string log) { - //return; - string prefix = isInitiator ? "A: " : "B: "; - base.Log(prefix + log); + string prefix =$"[TcpHolepunchState]: "; + prefix += isInitiator ? "A: " : "B: "; + base.Log(type,prefix + log); } private ClientHolepunchData GetHpData() diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs index 3ed8b49..d6f90ec 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs @@ -27,7 +27,6 @@ internal class ClientSimultaneousTcpHolepunchState : ConversationStateBase public byte[] SharedSecret; public ChannelInfo ChannelInfo; - private int localPort; private Socket listeningSocket; private Socket acceptedSocket; @@ -42,7 +41,7 @@ internal class ClientSimultaneousTcpHolepunchState : ConversationStateBase private bool IsEstablished => Interlocked.CompareExchange(ref established, 0, 0) == 1; - public ClientSimultaneousTcpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerEp, ChannelInfo info) : base(stateId, 5000) + public ClientSimultaneousTcpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerEp, ChannelInfo info, ILogger logger) : base(stateId, 5000, logger) { this.destId = destId; this.connection = connection; @@ -56,13 +55,13 @@ public async void Start() { isInitiator = true; - Log(StateId.ToString()); + Log(LogType.Debug, StateId.ToString()); await BindPort(); - Log($"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); + Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); StartListening(); - Log("listening"); + Log(LogType.Debug, "listening"); var msg = CreateEnvelope(); msg.To = destId; @@ -105,16 +104,16 @@ public override void HandleMessage(MessageEnvelope message) // the destination peer of hp private async void HandleRemoteHpRequest(MessageEnvelope message) { - Log(StateId.ToString()); + Log(LogType.Debug, StateId.ToString()); int offs = message.PayloadOffset; var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); ChannelInfo = hpData.ChannelInfo; await BindPort(); - Log($"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); + Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); StartListening(); - Log("listening"); + Log(LogType.Debug, "listening"); var msg = CreateEnvelope(); msg.To = destId; @@ -142,7 +141,7 @@ private async Task BindPort() var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket, discoveryServerEp.ToIpEndpoint(), 5000); if (remoteEp == null) { - Log("Failed to get public endpoint"); + Log(LogType.Warning, "Failed to get public endpoint"); remoteEp = new EndpointData("0.0.0.0", selfLocalEp.Port); } @@ -191,7 +190,7 @@ private void StartHolepunchRoutine(MessageEnvelope message) var now = connection.GetTime(); var delay = time - now; - Log("Delay: " + delay.ToString() + "ms"); + Log(LogType.Debug, "Delay: " + delay.ToString() + "ms"); PreciseTimeAwaiter.Wait(delay); if (IsEstablished) return; @@ -212,7 +211,7 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) try { - Log("Connecting to " + endpoint.ToIpEndpoint().ToString()); + Log(LogType.Debug, "Connecting to " + endpoint.ToIpEndpoint().ToString()); connectSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); connectSocket.Bind(new IPEndPoint(IPAddress.Any, localPort)); @@ -224,20 +223,20 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) connectSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); HandleConnectedSocket(connectSocket); - Log($"Successfully connected to {endpoint.ToIpEndpoint()}"); + Log(LogType.Debug, $"Successfully connected to {endpoint.ToIpEndpoint()}"); return true; } else { - Log($"Connection to {endpoint.ToIpEndpoint()} timed out after {timeoutMs}ms"); + Log(LogType.Debug, $"Connection to {endpoint.ToIpEndpoint()} timed out after {timeoutMs}ms"); connectSocket.Close(); return false; } } catch (Exception ex) { - Log($"Connect attempt to {endpoint.ToIpEndpoint()} failed: {ex.Message}"); + Log(LogType.Debug, $"Connect attempt to {endpoint.ToIpEndpoint()} failed: {ex.Message}"); connectSocket.Close(); return false; } @@ -275,7 +274,7 @@ private void AcceptCallback(IAsyncResult ar) } catch (Exception e) { - Log("Failed Accept: " + e.Message); + Log(LogType.Debug, "Failed Accept: " + e.Message); } } @@ -301,7 +300,7 @@ private void HandleAcceptedSocket(Socket socket) if (Interlocked.Exchange(ref accepted, 1) == 1) return; - Log($"Successfully accepted {(IPEndPoint)socket.RemoteEndPoint}"); + Log(LogType.Debug, $"Successfully accepted {(IPEndPoint)socket.RemoteEndPoint}"); acceptedSocket = socket; @@ -314,7 +313,7 @@ private void HandleAcceptedSocket(Socket socket) private void TimedOut() { - Log("Timed out"); + Log(LogType.Debug, "Timed out"); var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchFail; connection.SendAsyncMessage(msg); @@ -323,7 +322,7 @@ private void TimedOut() private void HandleFailure() { - Log("Failed Punch"); + Log(LogType.Debug, "Failed Punch"); Completed(false); } @@ -343,7 +342,7 @@ private void HandleRemoteSucces(MessageEnvelope message) } SuccesfulEndpoint = (IPEndPoint)Socket.RemoteEndPoint; - Log("Punched"); + Log(LogType.Debug,"Punched"); Completed(true); } @@ -368,11 +367,11 @@ protected override void Completed(bool succes) } - protected override void Log(string log) + protected override void Log(LogType type,string log) { - //return; - string prefix = isInitiator ? "A: " : "B: "; - base.Log(prefix + log); + string prefix = $"[TcpHolepunchState]: "; + prefix += isInitiator ? "A: " : "B: "; + base.Log(type,prefix + log); } private ClientHolepunchData GetHpData() diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index d444031..43b1352 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -28,13 +28,12 @@ internal class ClientUdpHolepunchState : ConversationStateBase private byte[] otherPublicKey; public byte[] SharedSecret; public ChannelInfo ChannelInfo; - private IPEndPoint selfRemoteEp; private IPEndPoint selfLocalEp; private int conditionCount = 0; - public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerendPoint, ChannelInfo info) : base(stateId, 20000) + public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerendPoint, ChannelInfo info, ILogger logger) : base(stateId, 20000, logger) { this.destId = destId; this.connection = connection; @@ -47,7 +46,7 @@ public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection public async void Start() { isInitiator = true; - Log(StateId.ToString()); + Log(LogType.Debug, StateId.ToString()); await StartUdpSocket(); @@ -99,7 +98,7 @@ public override void HandleMessage(MessageEnvelope message) // the destination peer of hp private async void HandleRemoteHpRequest(MessageEnvelope message) { - Log(StateId.ToString()); + Log(LogType.Debug, StateId.ToString()); int offs = message.PayloadOffset; var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); @@ -166,7 +165,7 @@ private void StartHolepunchRoutine(MessageEnvelope message) if (delay > 500) delay = 0; - Log("Delay: " + delay.ToString() + "ms"); + Log(LogType.Debug, "Delay: " + delay.ToString() + "ms"); PreciseTimeAwaiter.Wait(delay); if (IsCompleted()) return; @@ -192,7 +191,7 @@ private void TryPunch(IPEndPoint ep, MessageFlags flag) lock (m) { - Log($"Sending {flag.ToString()}To " + ep.ToString()); + Log(LogType.Debug, $"Sending {flag.ToString()}To " + ep.ToString()); stream.Position = 0; stream.WriteByte((byte)flag); @@ -219,7 +218,7 @@ private async Task StartUdpSocket() var remoteEp = await EndpointDiscoveryClient.GetUdpPublicEndpoint(Socket, discoveryServerendPoint.ToIpEndpoint(), 3000); if (remoteEp == null) { - Log("Failed to get public endpoint"); + Log(LogType.Warning, "Failed to get public endpoint"); remoteEp = new EndpointData("0.0.0.0", selfLocalEp.Port); } selfRemoteEp = remoteEp.ToIpEndpoint(); @@ -251,14 +250,14 @@ private async void Receive() if (Interlocked.CompareExchange(ref receivedOnce, 1, 0) == 0) { var ipep = (IPEndPoint)received.RemoteEndPoint; - Log("[-]Received 0xFF from " + ipep.ToString()); + Log(LogType.Debug, "[-]Received 0xFF from " + ipep.ToString()); TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); } } else if (buffer[0] == (byte)MessageFlags.HPAck) { - Log("[+]Received 0x0F From " + ((IPEndPoint)received.RemoteEndPoint).ToString()); + Log(LogType.Debug, "[+]Received 0x0F From " + ((IPEndPoint)received.RemoteEndPoint).ToString()); if (Interlocked.CompareExchange(ref receivedAck, 1, 0) == 0) { @@ -269,7 +268,7 @@ private async void Receive() } else { - Log("Cancel1"); + Log(LogType.Debug, "Cancel1"); Cancel(); return; } @@ -282,7 +281,7 @@ private async void Receive() } catch (Exception e) { - Log("ERROR" + e.Message); + Log(LogType.Debug, "ERROR" + e.Message); Cancel(); return; } @@ -314,7 +313,7 @@ private void ReceivedBidirectional(EndPoint remoteEndPoint) private void TimedOut() { - Log("Timed out"); + Log(LogType.Debug, "Timed out"); var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchFail; connection.SendAsyncMessage(msg); @@ -323,7 +322,7 @@ private void TimedOut() private void OnError(string error) { - Log("Exception :" + error); + Log(LogType.Debug, "Exception :" + error); var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchFail; connection.SendAsyncMessage(msg); @@ -333,7 +332,7 @@ private void OnError(string error) private void HandleFailure() { - Log("Failed Punch"); + Log(LogType.Debug, "Failed Punch"); Completed(false); } @@ -353,7 +352,7 @@ private void SignalCompletionCondition() if (SuccesfulEndpoint == null) throw new Exception("Endpont is null"); - Log("Punched"); + Log(LogType.Debug, "Punched"); Completed(true); } @@ -373,11 +372,11 @@ protected override void Completed(bool succes) } } - protected override void Log(string log) + protected override void Log(LogType type, string log) { - //return; - string prefix = isInitiator ? "A: " : "B: "; - base.Log(prefix + log); + string prefix = $"[UdpHolepunchState]: "; + prefix += isInitiator ? "A: " : "B: "; + base.Log(type, prefix + log); } diff --git a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs index b343710..5f4359c 100644 --- a/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs +++ b/NetworkLibrary/DistributedP2P/Components/ConversationStateBase.cs @@ -18,13 +18,15 @@ internal abstract class ConversationStateBase:IConversationState protected readonly object cancellationMutex = new object(); protected readonly int timeout; + private readonly ILogger logger; private TaskCompletionSource Completion; private int isComplete = 0; - public ConversationStateBase(Guid stateId, int timeout = -1) + public ConversationStateBase(Guid stateId, int timeout, ILogger logger) { this.StateId = stateId; this.timeout = timeout; + this.logger = logger; Completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); if (timeout > 0) @@ -92,9 +94,9 @@ protected virtual void Completed(bool succes) } } - protected virtual void Log(string log) + protected virtual void Log(LogType logType,string log) { - Console.WriteLine(log); + logger?.Log(logType,log); } diff --git a/NetworkLibrary/DistributedP2P/Components/ILogger.cs b/NetworkLibrary/DistributedP2P/Components/ILogger.cs new file mode 100644 index 0000000..f2f74f4 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/ILogger.cs @@ -0,0 +1,14 @@ +using System; + +namespace NetworkLibrary.DistributedP2P.Components +{ + public interface ILogger + { + event Action LogAvailable; + + void Log(LogType logType, string log); + void Log(Exception ex); + void SetAllowedOptions(LogType allowedLogTypes); + string Stringify(LogData data); + } +} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Components/Logger.cs b/NetworkLibrary/DistributedP2P/Components/Logger.cs new file mode 100644 index 0000000..ba26265 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Components/Logger.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; + +namespace NetworkLibrary.DistributedP2P.Components +{ + [Flags] + public enum LogType + { + Debug = 1, + Info = 2, + Warning = 4, + Error = 8, + Exception = 16 + } + + // readonly structs does not make defensive copies. + public readonly struct LogData + { + public readonly long Id; + public readonly LogType LogType; + public readonly DateTime TimeStamp; + public readonly string Log; + + public LogData(long id, LogType logType, DateTime timeStamp, string log) + { + Id = id; + LogType = logType; + TimeStamp = timeStamp; + Log = log; + } + } + + /* + * Concurrent Persistent Logger + * Log are guarantied to spesify correct order + * Log are guarantied to be persisted to disk on unexpected app exit or crash + * (Except for hardcore exceptions like Access Violation, they are not catchable in .NetCore). + */ + public class Logger:ILogger + { + public event Action LogAvailable; + + private LogType allowedLogTypes = LogType.Debug | LogType.Error | LogType.Warning | LogType.Info | LogType.Exception; + private Dictionary logTypeLookup; + private object logLocker = new object(); + private long logId = 0; + + public void Log(LogType logType, string log) + { + if (!(allowedLogTypes.HasFlag(logType))) + { + return; + } + + + var id = Interlocked.Increment(ref logId); + lock (logLocker) + { + LogData data = new LogData(id: id, + timeStamp: DateTime.UtcNow, + logType: logType, + log: log); + + try + { + LogAvailable?.Invoke(data); + } + catch (Exception e) + { + Trace.WriteLine($"#[Critical Error] Logger failed! : \n{e.ToString()}"); + } + } + } + + public void Log(Exception ex) + { + if (!(allowedLogTypes.HasFlag(LogType.Exception))) + { + return; + } + + + var id = Interlocked.Increment(ref logId); + lock (logLocker) + { + LogData data = new LogData(id: id, + timeStamp: DateTime.UtcNow, + logType: LogType.Exception, + log: $"{ex.Message}\n{ex.StackTrace}"); + + try + { + LogAvailable?.Invoke(data); + } + catch (Exception e) + { + Trace.WriteLine($"#[Critical Error] Logger failed! : \n{e.ToString()}"); + } + } + } + + public string Stringify(LogData data) + { + return $"#[{data.Id}][{data.TimeStamp}][{logTypeLookup[data.LogType]}] : {data.Log}"; + } + + + /// + /// allowedLogTypes = LogType.Debug | LogType.Error | LogType.Warning | LogType.Info | LogType.Exception + /// + /// + public void SetAllowedOptions(LogType allowedLogTypes) + { + this.allowedLogTypes = allowedLogTypes; + } + + // Constructed only once if anyone acesses this class(Thread safe). + public Logger() + { + InitializeLookUpTable(); + + AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionExit; + AppDomain.CurrentDomain.ProcessExit += OnExit; + } + + private void InitializeLookUpTable() + { + logTypeLookup = new Dictionary() + { + {LogType.Info,"Info" }, + {LogType.Debug,"Debug" }, + {LogType.Warning,"Warning" }, + {LogType.Error,"Error" }, + {LogType.Exception,"Exception" }, + }; + } + + private void OnExit(object sender, EventArgs e) + { + Log(LogType.Info, "Application Exiting.."); + } + + private void UnhandledExceptionExit(object sender, UnhandledExceptionEventArgs e) + { + Log(LogType.Exception, $"Application Exit with an unhandled exception:" + + $"\n{((Exception)e.ExceptionObject).Message}" + + $"\nStack Trace:{((Exception)e.ExceptionObject).StackTrace}"); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/StateManager.cs b/NetworkLibrary/DistributedP2P/Components/StateManager.cs index d6efbae..da3b0bd 100644 --- a/NetworkLibrary/DistributedP2P/Components/StateManager.cs +++ b/NetworkLibrary/DistributedP2P/Components/StateManager.cs @@ -7,6 +7,13 @@ namespace NetworkLibrary.DistributedP2P.Components internal class StateManager { ConcurrentDictionary states = new ConcurrentDictionary(); + private readonly ILogger logger; + + public StateManager(ILogger logger) + { + this.logger = logger; + } + public void RegisterState(IConversationState state) { state.OnComplete += HandleComplete; @@ -41,8 +48,8 @@ public bool HandleMessage(MessageEnvelope message) state.HandleMessage(message); } catch (Exception e) - { - Console.WriteLine($"State Management failed{e.Message}\n{e.StackTrace}"); + { + logger?.Log(LogType.Exception,$"State Management failed : {e.Message}\n{e.StackTrace}"); state.Cancel(); UnregisterState(stateId); } diff --git a/NetworkLibrary/DistributedP2P/Components/TimeSync.cs b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs index fd1e614..1ac9c0e 100644 --- a/NetworkLibrary/DistributedP2P/Components/TimeSync.cs +++ b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs @@ -81,7 +81,6 @@ public async void StartAutoTimeSync() public void StopAutoTimeSync() { - Console.WriteLine("SyncStopped"); cancel = true; ClearData(); } diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 3d791da..c943526 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -21,6 +21,7 @@ public class Dependencies { public IAuthenticator Authenticator; public IServerDbConnector DbConnector; + public ILogger logger; } public class ServerParameters { @@ -44,9 +45,10 @@ public class DistributedLobbyServerBase : IServerConnection, IDisposable IAuthenticator authenticator; IServerDbConnector dbConnector; + ILogger logger; SessionManager sessionManager; - Components.StateManager stateManager = new Components.StateManager(); + StateManager stateManager; RelayService piper; Stopwatch serverClock = new Stopwatch(); @@ -60,11 +62,12 @@ public DistributedLobbyServerBase(Dependencies dependencies, ServerParameters pa { authenticator = dependencies.Authenticator; dbConnector = dependencies.DbConnector; + logger = dependencies.logger; SSlPort = parameters.SSlPort; TcpPort = parameters.TcpPort; UdpPort = parameters.UdpPort; DiscoveryServerPort = parameters.DiscoveryServerPort; - + stateManager = new StateManager(logger); serverCertificate = parameters.certificate ?? CertificateGenerator.GenerateSelfSignedCertificate(); } @@ -118,7 +121,7 @@ private void HandleConnRequest(MessageEnvelope msg) { Guid stateId = msg.MessageId; - var state = new ServerConnectionState(stateId, msg.From, this, authenticator, dbConnector, DiscoveryServerPort); + var state = new ServerConnectionState(stateId, msg.From, this, authenticator, dbConnector, DiscoveryServerPort, logger); stateManager.RegisterState(state); state.HandleMessage(msg); @@ -191,7 +194,7 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) case InternalConstants.PipeRequestTcp: - var pipeState = new ServerPipeState(message.MessageId, this); + var pipeState = new ServerPipeState(message.MessageId, this, logger); stateManager.RegisterState(pipeState); pipeState.HandleMessage(message); @@ -200,26 +203,26 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) case InternalConstants.PipeRequestUdp: - var pipeState1 = new ServerPipeState(message.MessageId, this); + var pipeState1 = new ServerPipeState(message.MessageId, this, logger); stateManager.RegisterState(pipeState1); pipeState1.HandleMessage(message); break; case InternalConstants.RequestHolepunchUdp: - var state = new ServerUdpHolepunchState(message.MessageId, this, sessionManager); + var state = new ServerUdpHolepunchState(message.MessageId, this, sessionManager, logger); stateManager.RegisterState(state); state.HandleMessage(message); break; case InternalConstants.RequestSimultaneousHolepunchTcp: - var state2 = new ServerSimultaneousTcpHolepunchState(message.MessageId, this, sessionManager); + var state2 = new ServerSimultaneousTcpHolepunchState(message.MessageId, this, sessionManager, logger); stateManager.RegisterState(state2); state2.HandleMessage(message); break; case InternalConstants.RequestSequentialHolepunchTcp: - var state3 = new ServerSequentialTcpHolepunchState(message.MessageId, this, sessionManager); + var state3 = new ServerSequentialTcpHolepunchState(message.MessageId, this, sessionManager, logger); stateManager.RegisterState(state3); state3.HandleMessage(message); break; diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs index 103834f..c430876 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs @@ -23,7 +23,7 @@ internal class ServerConnectionState : ConversationStateBase int timeSynced; int handShakeComplete; - public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection connection, IAuthenticator authenticator, IServerDbConnector dbConnector, int EDSPort) : base(stateId, 20000) + public ServerConnectionState(Guid stateId, Guid clientId, IDistributedConnection connection, IAuthenticator authenticator, IServerDbConnector dbConnector, int EDSPort, ILogger logger) : base(stateId, 20000, logger) { this.EphemeralClientId = clientId; this.connection = connection; diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs index 2d5d68e..08675f7 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerPipeState.cs @@ -34,7 +34,7 @@ internal class ServerPipeState : ConversationStateBase private byte[] destinationsDhPublicKey; private ChannelInfo chInfo = new ChannelInfo(); - public ServerPipeState(Guid stateId, IServerConnection connection) : base(stateId, 20000) + public ServerPipeState(Guid stateId, IServerConnection connection, ILogger logger) : base(stateId, 20000, logger) { this.connection = connection; } @@ -79,7 +79,7 @@ public override void HandleMessage(MessageEnvelope message) } catch (Exception ex) { - Log($"Exception occured on server pipe state: {ex.Message}\n{ex.StackTrace}"); + Log(LogType.Exception, $"Exception occured on server pipe state: {ex.Message}\n{ex.StackTrace}"); HandleBadAck(); } @@ -125,7 +125,7 @@ private void HandlePipeResult(PipeResult result) } else { - Log("Unable to obtain pipe token"); + Log(LogType.Error, "Unable to obtain pipe token"); HandleBadAck(); } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSequentialTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSequentialTcpHolepunchState.cs index dd7c88e..e5df059 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSequentialTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSequentialTcpHolepunchState.cs @@ -25,7 +25,7 @@ internal class ServerSequentialTcpHolepunchState : ConversationStateBase ChannelInfo info; private int succesCount; - public ServerSequentialTcpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) + public ServerSequentialTcpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager, ILogger logger) : base(stateId, 20000, logger) { this.connection = connection; this.sessionManager = sessionManager; @@ -149,7 +149,7 @@ private void HandleHolepunchRequestAck(MessageEnvelope message) protected override void Completed(bool succes) { - Console.WriteLine("Server Finalized"); + Log(LogType.Debug, "Server Tcp Holepunch Finalized"); base.Completed(succes); } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSimultaneousTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSimultaneousTcpHolepunchState.cs index 8a4276f..fa89c7a 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSimultaneousTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerSimultaneousTcpHolepunchState.cs @@ -26,7 +26,7 @@ internal class ServerSimultaneousTcpHolepunchState : ConversationStateBase ChannelInfo info; private int succesCount; - public ServerSimultaneousTcpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) + public ServerSimultaneousTcpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager, ILogger logger) : base(stateId, 20000, logger) { this.connection = connection; this.sessionManager = sessionManager; diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs index 320a1ce..611c813 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerUdpHolepunchState.cs @@ -26,7 +26,7 @@ internal class ServerUdpHolepunchState : ConversationStateBase ChannelInfo info; private int succesCount; - public ServerUdpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager) : base(stateId, 20000) + public ServerUdpHolepunchState(Guid stateId, IDistributedConnection connection, SessionManager sessionManager, ILogger logger) : base(stateId, 20000, logger) { this.connection = connection; this.sessionManager = sessionManager; diff --git a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs index 79872c3..49d4ff8 100644 --- a/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs +++ b/Tests/UnitTests/DistributedP2P/DistP2PServerclientTest.cs @@ -10,14 +10,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.Tracing; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace UnitTests.DistributedP2P { - + class ClientAuthToken : IClientAuthenticationToken { public string Token { get; } = "1234"; @@ -94,8 +93,11 @@ private static DistributedLobbyServerBase ArrangeServer() var dep = new Dependencies() { Authenticator = new ServerAuth(), - DbConnector = new ServerDb() + DbConnector = new ServerDb(), + logger = new Logger() + }; + dep.logger.LogAvailable += (data) => { Console.WriteLine(dep.logger.Stringify(data)); }; var param = new ServerParameters() { certificate = null, @@ -103,17 +105,27 @@ private static DistributedLobbyServerBase ArrangeServer() TcpPort = 20011, UdpPort = 20012, DiscoveryServerPort = 20013, - + }; var server = new DistributedLobbyServerBase(dep, param); server.StartServer(); return server; } + private static DistributedLobbyClient GetClient() + { + var logger = new Logger(); + logger.LogAvailable += (data) => { Console.WriteLine(logger.Stringify(data)); }; + return new DistributedLobbyClient(new ClientDB(), new ClientAuth(), logger); + } + + [TestMethod] public void ConnectTest() { - DistributedLobbyClient distributedLobbyClient = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + + + var distributedLobbyClient = GetClient(); using var server = ArrangeServer(); var res = distributedLobbyClient.ConnectAsync(ServerIp, 20010).Result; } @@ -122,8 +134,8 @@ public void ConnectTest() [TestMethod] public void PipeTest() { - DistributedLobbyClient cl = GetClient(); - DistributedLobbyClient cl2 = GetClient(); + var cl = GetClient(); + var cl2 = GetClient(); TaskCompletionSource tcs = new TaskCompletionSource(); ManualResetEvent mre = new ManualResetEvent(false); @@ -185,8 +197,8 @@ void Channel_BytesReceived(byte[] buff, int offset, int count) [TestMethod] public void SecurePipeTest() { - DistributedLobbyClient distributedLobbyClient = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); - DistributedLobbyClient distributedLobbyClient2 = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + var cl = GetClient(); + var cl2 = GetClient(); TaskCompletionSource tcs = new TaskCompletionSource(); @@ -201,15 +213,15 @@ public void SecurePipeTest() using var server = ArrangeServer(); - var res = distributedLobbyClient.ConnectAsync(ServerIp, 20010).Result; - var res2 = distributedLobbyClient2.ConnectAsync(ServerIp, 20010).Result; + var res = cl.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; - distributedLobbyClient2.PeerConnected += PeerConnected; + cl2.PeerConnected += PeerConnected; var info = new ChannelInfo(); info.ChannelName = "Test"; info.ChannelType = ChannelType.SecureTcp; - var channel1 = (SecureTcpChannel)distributedLobbyClient.OpenRelayChannel(distributedLobbyClient2.SessionId, info).Result; + var channel1 = (SecureTcpChannel)cl.OpenRelayChannel(cl2.SessionId, info).Result; Assert.IsNotNull(channel1); @@ -217,21 +229,21 @@ public void SecurePipeTest() channel1.Start(); - + var ping = channel1.Ping().Result; for (int i = 0; i < iter; i++) { data[0] = (byte)i; channel1.Send(data, 0, data.Length); - if(i%2 ==0) + if (i % 2 == 0) Thread.Sleep(1000); } void PeerConnected(IChannel channel_) { - // mre.WaitOne();//emulate bad syncronisation + // mre.WaitOne();//emulate bad syncronisation var channel = (SecureTcpChannel)channel_; channel.OnBytesReceived += Channel_BytesReceived; channel.OnDisconnected += Disconnected; @@ -253,7 +265,7 @@ void Channel_BytesReceived(byte[] buff, int offset, int count) } - + var ss = tcs.Task.Result; Assert.IsTrue(received.Count == iter); @@ -290,13 +302,13 @@ public void PipeTestUdp() channel1.Start(); byte[] data = new byte[12800]; - channel1.Send(data,0,data.Length); + channel1.Send(data, 0, data.Length); Thread.Sleep(100); void Cl2_PeerConnected(IChannel obj) { var udpChannel = (UdpChannel)obj; - udpChannel.OnBytesReceived += (b,o,c) => + udpChannel.OnBytesReceived += (b, o, c) => { received = c; tcs.SetResult(true); @@ -349,8 +361,8 @@ void Cl2_PeerConnected(IChannel obj) var udpChannel = (SecureUdpChannel)obj; udpChannel.OnBytesReceived += (b, o, c) => { - received = c; - if(++cnt == 2) + received = c; + if (++cnt == 2) { tcs.SetResult(true); @@ -370,23 +382,23 @@ void Cl2_PeerConnected(IChannel obj) [TestMethod] public void MessageTest() { - DistributedLobbyClient distributedLobbyClient = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); - DistributedLobbyClient distributedLobbyClient2 = new DistributedLobbyClient(new ClientDB(), new ClientAuth()); + var cl = GetClient(); + var cl2 = GetClient(); TaskCompletionSource tcs = new TaskCompletionSource(); int received = 0; using var server = ArrangeServer(); - var res = distributedLobbyClient.ConnectAsync(ServerIp, 20010).Result; - var res2 = distributedLobbyClient2.ConnectAsync(ServerIp, 20010).Result; + var res = cl.ConnectAsync(ServerIp, 20010).Result; + var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; - distributedLobbyClient2.MessageReceived += MsgRec; + cl2.MessageReceived += MsgRec; MessageEnvelope msg = new MessageEnvelope(); msg.Header = "Greetings"; msg.Payload = new byte[1280000]; - msg.To = distributedLobbyClient2.SessionId; - distributedLobbyClient.SendAsyncMessage(msg); + msg.To = cl2.SessionId; + cl.SendAsyncMessage(msg); void MsgRec(NetworkLibrary.MessageEnvelope msg) { @@ -403,11 +415,6 @@ void MsgRec(NetworkLibrary.MessageEnvelope msg) } - private static DistributedLobbyClient GetClient() - { - return new DistributedLobbyClient(new ClientDB(), new ClientAuth()); - } - [TestMethod] public void StatusPublishTest() { @@ -419,7 +426,7 @@ public void StatusPublishTest() for (int i = 0; i < 20; i++) { var cl = GetClient(); - Task task = cl.ConnectAsync(ServerIp, 20010).ContinueWith(t => + Task task = cl.ConnectAsync(ServerIp, 20010).ContinueWith(t => { clients.Add(cl); ids.Add(cl.SessionId); @@ -427,12 +434,12 @@ public void StatusPublishTest() task.ConfigureAwait(false); pending.Add(task); - + } Task.WhenAll(pending).Wait(); pending.Clear(); - + Thread.Sleep(2000); VerifyPeerList(clients, ids); @@ -510,13 +517,13 @@ public void SendAndWait() var res = cl1.ConnectAsync(ServerIp, 20010).Result; var res2 = cl2.ConnectAsync(ServerIp, 20010).Result; - + cl1.MessageReceived += Cl1_MessageReceived; void Cl1_MessageReceived(MessageEnvelope obj) { obj.To = cl2.SessionId; - cl1.SendAsyncMessage(obj); + cl1.SendAsyncMessage(obj); } var msg = new MessageEnvelope(); @@ -524,7 +531,7 @@ void Cl1_MessageReceived(MessageEnvelope obj) msg.To = cl1.SessionId; var response = cl2.SendMessageAndWaitResponse(msg).Result; - Assert.IsTrue (response.Header != MessageEnvelope.RequestTimeout); + Assert.IsTrue(response.Header != MessageEnvelope.RequestTimeout); } [TestMethod] @@ -543,7 +550,7 @@ public void Timesync() double time1 = cl1.GetTime(); double time2 = cl2.GetTime(); double time3 = server.GetTime(); - + Console.WriteLine(time1); Console.WriteLine(time2); @@ -628,7 +635,7 @@ public void UdpHolepunchSecure() var data = new byte[1280000]; data[0] = 1; - channel1.SendReliable(data,0,data.Length); + channel1.SendReliable(data, 0, data.Length); Thread.Sleep(100); void Cl2_PeerConnected(IChannel obj) @@ -693,7 +700,7 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) var ss = tcs.Task.Result; Assert.AreEqual(data.Length, received); - + } @@ -795,7 +802,7 @@ void Ch_OnMessageReceived(byte[] arg1, int arg2, int arg3) if (arg3 != data.Length) throw new Exception(); - if(Interlocked.Increment(ref numReceived) == iter) + if (Interlocked.Increment(ref numReceived) == iter) tcs.TrySetResult(true); } @@ -842,7 +849,7 @@ public void TestTcpChannelOrder() { data[0] = (byte)i; channel1.Send(data, 0, data.Length); - if(i%10 == 0) + if (i % 10 == 0) Thread.Sleep(1); }; @@ -890,12 +897,12 @@ public void NTPClientServer() { _ = cl.GetServerTime(2000); }); - - + + var timeResult = cl.GetServerTime(2000).Result; Assert.IsTrue(timeResult.Succes); - Assert.IsTrue(timeResult.PreciseTime >1000); + Assert.IsTrue(timeResult.PreciseTime > 1000); } From 8136ce05a78a8f25d093a57e24d4bf29e077aab4 Mon Sep 17 00:00:00 2001 From: Dogancan Ozturk Date: Thu, 17 Apr 2025 16:16:59 +0200 Subject: [PATCH 25/27] started room development --- .../Client/DistributedLobbyClient.cs | 6 ++ .../DistributedP2P/Client/RoomConnection.cs | 79 +++++++++++++++++ .../Client/StateManagement/ClientRoomState.cs | 67 +++++++++++++++ .../Components/InternalConstants.cs | 4 + .../DistributedP2P/Components/Logger.cs | 67 ++++++--------- .../DistributedP2P/Components/NTPServer.cs | 1 + .../Server/DistributedLobbyServer.cs | 27 ++++-- .../Server/IServerConnection.cs | 2 + .../DistributedP2P/Server/RoomManager.cs | 86 +++++++++++++++++-- .../Server/StateManagement/ServerRoomState.cs | 43 ++++++++++ 10 files changed, 331 insertions(+), 51 deletions(-) create mode 100644 NetworkLibrary/DistributedP2P/Client/RoomConnection.cs create mode 100644 NetworkLibrary/DistributedP2P/Client/StateManagement/ClientRoomState.cs create mode 100644 NetworkLibrary/DistributedP2P/Server/StateManagement/ServerRoomState.cs diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 2cfe735..db9cb54 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -352,6 +352,11 @@ void State_OnComplete(IConversationState obj) } } + //public Task JoinRoom() + //{ + // return Task.FromResult(new RoomConnection()); + //} + public double GetTime() { return timeSync.GetTime(); @@ -361,6 +366,7 @@ public DateTime GetDateTime() { return timeSync.GetDateTime(); } + public Task SyncTime() { return timeSync.SyncTime(); diff --git a/NetworkLibrary/DistributedP2P/Client/RoomConnection.cs b/NetworkLibrary/DistributedP2P/Client/RoomConnection.cs new file mode 100644 index 0000000..5a6a56f --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/RoomConnection.cs @@ -0,0 +1,79 @@ +using NetworkLibrary.DistributedP2P.Channels.Components; +using NetworkLibrary.Utils; +using System; +using System.Collections.Concurrent; + +namespace NetworkLibrary.DistributedP2P.Client +{ + public class RoomConnection + { + internal IChannel communicationChannel; + internal byte assignedId; + internal Guid roomId; + + ConcurrentDictionary aliasMap = new ConcurrentDictionary(); + + public event Action ConnectionLost; + public event Action MessageReceived; + public event Action PeerJoined; + public event Action PeerLeft; + + internal event Action LeaveRequested; + + public RoomConnection(IChannel communicationChannel, Guid roomId, byte assignedId) + { + this.communicationChannel = communicationChannel; + this.roomId = roomId; + this.assignedId = assignedId; + + communicationChannel.OnBytesReceived += BytesReceived; + } + // server should call this directly + internal void PeerJoinedRoom(Guid peerId, byte alias) + { + aliasMap.TryAdd(alias, peerId); + PeerJoined?.Invoke(peerId); + } + + internal void PeerLeftRoom(Guid peerId, byte alias) + { + aliasMap.TryRemove(alias, out _); + PeerLeft?.Invoke(peerId); + } + + private void BytesReceived(byte[] buffer, int offset, int count) + { + byte alias = buffer[offset++]; + count--; + + if (aliasMap.TryGetValue(alias, out var guid)) + { + MessageReceived?.Invoke(guid, buffer, offset, count); + } + } + + public void Send(byte[] buffer, int offset, int count) + { + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + stream.WriteByte(assignedId); + stream.Write(buffer, offset, count); + + communicationChannel.Send(stream.GetBuffer(), 0, stream.Position32); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + + //public void SendReliable(byte[] buffer, int offset, int count) + //{ + // if(communicationChannel.Info.ChannelType == ChannelType.Udp) + // ((UdpChannel)communicationChannel).SendReliable() + //} + + + public void LeaveRoom() + { + + } + + } +} \ No newline at end of file diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientRoomState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientRoomState.cs new file mode 100644 index 0000000..aa48fa1 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientRoomState.cs @@ -0,0 +1,67 @@ +using NetworkLibrary.DistributedP2P.Components; +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetworkLibrary.DistributedP2P.Client.StateManagement +{ + internal class ClientRoomState : ConversationStateBase + { + private readonly IDistributedConnection connection; + + public ClientRoomState(Guid stateId, int timeout,IDistributedConnection connection, ILogger logger) : base(stateId, timeout, logger) + { + this.connection = connection; + } + + public void Start(string roomName,string passWord) + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.RequestCreateOrJointRoom; + msg.KeyValuePairs = new Dictionary(); + + msg.KeyValuePairs["RoomName"] = roomName; + msg.KeyValuePairs["Pass"] = passWord; + + connection.SendAsyncMessage(msg); + } + + public override void HandleMessage(MessageEnvelope message) + { + switch (message.Header) + { + case InternalConstants.ResponseCreateOrJointRoom: + HandleResponse(message); + break; + } + } + + private void HandleResponse(MessageEnvelope message) + { + string succesStr = message.KeyValuePairs["Status"]; + bool success = bool.Parse(succesStr); + if (success) + { + string token = message.KeyValuePairs["Token"]; + ExchangeToken(token); + } + else + { + Completed(false); + } + + } + + private void ExchangeToken(string token) + { + TokenExchangeComplete(); + } + + private void TokenExchangeComplete() + { + var msg = CreateEnvelope(); + msg.Header = InternalConstants.JoinedRoom; + connection.SendAsyncMessage(msg); + } + } +} diff --git a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs index a78aad5..b162dff 100644 --- a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs +++ b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs @@ -32,5 +32,9 @@ internal class InternalConstants public const string PunchSwap = "m"; public const string RequestSimultaneousHolepunchTcp = "n"; + public const string RequestCreateOrJointRoom = "o"; + public const string ResponseCreateOrJointRoom = "p"; + public const string JoinedRoom = "r"; + } } diff --git a/NetworkLibrary/DistributedP2P/Components/Logger.cs b/NetworkLibrary/DistributedP2P/Components/Logger.cs index ba26265..6a80da5 100644 --- a/NetworkLibrary/DistributedP2P/Components/Logger.cs +++ b/NetworkLibrary/DistributedP2P/Components/Logger.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; using System.Threading; namespace NetworkLibrary.DistributedP2P.Components @@ -35,19 +32,13 @@ public LogData(long id, LogType logType, DateTime timeStamp, string log) } } - /* - * Concurrent Persistent Logger - * Log are guarantied to spesify correct order - * Log are guarantied to be persisted to disk on unexpected app exit or crash - * (Except for hardcore exceptions like Access Violation, they are not catchable in .NetCore). - */ - public class Logger:ILogger + + public class Logger : ILogger { public event Action LogAvailable; private LogType allowedLogTypes = LogType.Debug | LogType.Error | LogType.Warning | LogType.Info | LogType.Exception; private Dictionary logTypeLookup; - private object logLocker = new object(); private long logId = 0; public void Log(LogType logType, string log) @@ -59,22 +50,21 @@ public void Log(LogType logType, string log) var id = Interlocked.Increment(ref logId); - lock (logLocker) + + LogData data = new LogData(id: id, + timeStamp: DateTime.UtcNow, + logType: logType, + log: log); + + try { - LogData data = new LogData(id: id, - timeStamp: DateTime.UtcNow, - logType: logType, - log: log); - - try - { - LogAvailable?.Invoke(data); - } - catch (Exception e) - { - Trace.WriteLine($"#[Critical Error] Logger failed! : \n{e.ToString()}"); - } + LogAvailable?.Invoke(data); } + catch (Exception e) + { + Trace.WriteLine($"#[Critical Error] Logger failed! : \n{e.ToString()}"); + } + } public void Log(Exception ex) @@ -86,22 +76,21 @@ public void Log(Exception ex) var id = Interlocked.Increment(ref logId); - lock (logLocker) + + LogData data = new LogData(id: id, + timeStamp: DateTime.UtcNow, + logType: LogType.Exception, + log: $"{ex.Message}\n{ex.StackTrace}"); + + try { - LogData data = new LogData(id: id, - timeStamp: DateTime.UtcNow, - logType: LogType.Exception, - log: $"{ex.Message}\n{ex.StackTrace}"); - - try - { - LogAvailable?.Invoke(data); - } - catch (Exception e) - { - Trace.WriteLine($"#[Critical Error] Logger failed! : \n{e.ToString()}"); - } + LogAvailable?.Invoke(data); } + catch (Exception e) + { + Trace.WriteLine($"#[Critical Error] Logger failed! : \n{e.ToString()}"); + } + } public string Stringify(LogData data) diff --git a/NetworkLibrary/DistributedP2P/Components/NTPServer.cs b/NetworkLibrary/DistributedP2P/Components/NTPServer.cs index cc29e66..46c4312 100644 --- a/NetworkLibrary/DistributedP2P/Components/NTPServer.cs +++ b/NetworkLibrary/DistributedP2P/Components/NTPServer.cs @@ -66,6 +66,7 @@ private void Received(object _, SocketAsyncEventArgs e) var ep = (IPEndPoint)e.RemoteEndPoint; ep.Address = IPAddress.Any; ep.Port = 0; + if (udpListener.ReceiveFromAsync(e)) { return; diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index c943526..80a14bf 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -266,16 +266,33 @@ void IServerConnection.GetPipeToken(bool isTcpPipe, Guid fromEphemeral, Guid toE } - private bool CreateRoom(string roomName, string roomPassword, RoomProtocol protocol) + RoomResult IServerConnection.CreateOrJoinRoom(string roomName, string roomPassword, Guid peerId, RoomProtocol protocol) { - if (roomManager.TryCreateRoom(roomName, roomPassword, out Guid RoomId)) + var roomResult = roomManager.CreateOrJoinRoom(roomName, roomPassword,peerId, protocol); + + if (roomResult.IsCreated) + { + if (relayService.CreateRoom(roomResult.RoomInfo.RoomId, protocol)) + { + if(GetRoomToken(peerId, roomResult.RoomInfo.RoomId, out byte[] roomToken)) + { + roomResult.RoomToken = roomToken; + return roomResult; + } + return null; + } + + } + else { - if (relayService.CreateRoom(RoomId, protocol)) + if (GetRoomToken(peerId, roomResult.RoomInfo.RoomId, out byte[] roomToken)) { - return true; + roomResult.RoomToken = roomToken; + return roomResult; } } - return false; + + return null; } private bool GetRoomToken(Guid peerId, Guid roomId, out byte[] token) diff --git a/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs b/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs index 369341c..05a7baf 100644 --- a/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs +++ b/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs @@ -5,11 +5,13 @@ using System.Collections.Generic; using System.Text; using System.Threading.Tasks; +using NetworkLibrary.DistributedP2P.SimpleRelay; namespace NetworkLibrary.DistributedP2P.Server { internal interface IServerConnection : IDistributedConnection { void GetPipeToken(bool isTcpPipe, Guid fromEphemeral, Guid toEphemeral, Action onReady); + RoomResult CreateOrJoinRoom(string roomName, string roomPassword, Guid peerId, RoomProtocol protocol); } } diff --git a/NetworkLibrary/DistributedP2P/Server/RoomManager.cs b/NetworkLibrary/DistributedP2P/Server/RoomManager.cs index 24aa39b..e9ca362 100644 --- a/NetworkLibrary/DistributedP2P/Server/RoomManager.cs +++ b/NetworkLibrary/DistributedP2P/Server/RoomManager.cs @@ -1,16 +1,88 @@ -using System; -using System.Collections.Generic; -using System.Text; +using NetworkLibrary.DistributedP2P.SimpleRelay; +using System; +using System.Collections.Concurrent; +using System.Security.Cryptography; namespace NetworkLibrary.DistributedP2P.Server { - internal class RoomManager + public class RoomInfo { - + public Guid RoomId { get; set; } + public string Name { get; set; } + + public byte[] RoomKey; + + public ConcurrentDictionary Peers = new ConcurrentDictionary(); + private ConcurrentQueue idStack = new ConcurrentQueue(); + + public RoomInfo() + { + for (byte i = 0; i < byte.MaxValue; i++) + { + idStack.Enqueue(i); + } + } - internal bool TryCreateRoom(string roomName, string roomPassword, out Guid roomId) + internal bool AddPeer(Guid peerId, out byte idAlias) { - throw new NotImplementedException(); + idAlias = 0; + if (idStack.TryDequeue(out byte alias)) + { + if (Peers.TryAdd(peerId, alias)) + { + idAlias = alias; + return true; + } + else + { + idStack.Enqueue(alias); + return false; + } + } + return false; + + } + } + class RoomResult + { + public RoomInfo RoomInfo; + public bool IsCreated; + public byte idAlias; + public byte[] RoomToken; + } + internal class RoomManager + { + ConcurrentDictionary rooms = new ConcurrentDictionary(); + RandomNumberGenerator rng = RandomNumberGenerator.Create(); + internal RoomResult CreateOrJoinRoom(string roomName, string roomPassword, Guid peerId, RoomProtocol protocol) + { + RoomResult result = new RoomResult(); + if (rooms.TryGetValue(roomName, out var roomInfo)) + { + if (roomInfo.AddPeer(peerId, out var Idalias)) + { + result.RoomInfo = roomInfo; + result.IsCreated = false; + result.idAlias = Idalias; + return result; + } + return null; + } + + var roomInf = new RoomInfo(); + roomInf.RoomId = Guid.NewGuid(); + roomInf.RoomKey = new byte[16]; + rng.GetBytes(roomInf.RoomKey); + + roomInf.AddPeer(peerId, out byte alias); + + rooms.TryAdd(roomName, roomInf); + result.RoomInfo = roomInfo; + result.IsCreated = false; + result.idAlias = alias; + + return result; + } } } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerRoomState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerRoomState.cs new file mode 100644 index 0000000..fe37db0 --- /dev/null +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerRoomState.cs @@ -0,0 +1,43 @@ +using NetworkLibrary.DistributedP2P.Components; +using System; + +namespace NetworkLibrary.DistributedP2P.Server.StateManagement +{ + internal class ServerRoomState : ConversationStateBase + { + private readonly IServerConnection connection; + + public ServerRoomState(Guid stateId, int timeout, IServerConnection connection, ILogger logger) : base(stateId, timeout, logger) + { + this.connection = connection; + } + + public override void HandleMessage(MessageEnvelope message) + { + switch (message.Header) + { + case InternalConstants.RequestCreateOrJointRoom: + HandleRoomCreation(message); + break; + case InternalConstants.JoinedRoom: + HandleSuccess(message); + break; + } + } + + private async void HandleRoomCreation(MessageEnvelope message) + { + var roomName = message.KeyValuePairs["RoomName"]; + var password = message.KeyValuePairs["Pass"]; + + // room names are unique, end of story! + + //await connection.CreateOrJoinRoom(roomName, password); + } + + private void HandleSuccess(MessageEnvelope message) + { + throw new NotImplementedException(); + } + } +} From 30e5fe8161c1da8a79b39d5d43d7392c50049a90 Mon Sep 17 00:00:00 2001 From: dogancan ozturk Date: Thu, 17 Apr 2025 23:44:36 +0200 Subject: [PATCH 26/27] fixes --- .../Client/DistributedLobbyClient.cs | 13 +- .../ClientSequentialTcpHolepunchState.cs | 36 ++- .../ClientSimultaneousTcpHolepunchState.cs | 70 +++--- .../ClientUdpHolepunchState.cs | 218 +++++++++++------- .../Components/EndpointDiscoveryClient.cs | 2 +- .../Server/DistributedLobbyServer.cs | 4 +- .../DistributedP2P/Server/SessionManager.cs | 2 +- .../SimpleRelay/RelayService.cs | 27 ++- NetworkLibrary/TCP/Base/AsyncTcpServer.cs | 6 +- NetworkLibrary/TCP/Base/TcpSession.cs | 16 +- NetworkLibrary/TCP/SSL/SslClient.cs | 2 +- 11 files changed, 250 insertions(+), 146 deletions(-) diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index db9cb54..0f6ea5d 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -79,7 +79,7 @@ public async Task ConnectAsync(string ip, int port) IClientAuthenticationToken authToken = clientAuthProvider.Authenticate(); - bool res = await sslClient.ConnectAsync(ip, port); + bool res = await sslClient.ConnectAsync(ip, port).ConfigureAwait(false); if (res) { serverEndpoint = new EndpointData(ip, port); @@ -90,7 +90,7 @@ public async Task ConnectAsync(string ip, int port) stateManager.RegisterState(conState); conState.Start(); - await conState.WaitCompletion(); + await conState.WaitCompletion().ConfigureAwait(false); if (conState.IsSuccesful) { @@ -133,7 +133,6 @@ public Task SendMessageAndWaitResponse(Guid a, MessageEnvelope private void HandleServerMsg(MessageEnvelope envelope) { - if (envelope.IsInternal) { if (stateManager.HandleMessage(envelope)) @@ -222,7 +221,7 @@ public async Task OpenRelayChannel(Guid destinationPeer, ChannelInfo I stateManager.RegisterState(pipeState); pipeState.Start(destinationPeer); - await pipeState.WaitCompletion(); + await pipeState.WaitCompletion().ConfigureAwait(false); if (pipeState.IsSuccesful) { @@ -256,7 +255,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp stateManager.RegisterState(state); state.Start(); - await state.WaitCompletion(); + await state.WaitCompletion().ConfigureAwait(false); if (state.IsSuccesful) { @@ -272,7 +271,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp stateManager.RegisterState(state); state.Start(); - await state.WaitCompletion(); + await state.WaitCompletion().ConfigureAwait(false); if (state.IsSuccesful) { @@ -286,7 +285,7 @@ public async Task TryHolePunch(Guid destination, ChannelInfo info, Tcp stateManager.RegisterState(state); state.Start(); - await state.WaitCompletion(); + await state.WaitCompletion().ConfigureAwait(false); if (state.IsSuccesful) { diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs index 514e6d5..5c46f6f 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs @@ -141,24 +141,36 @@ private async void HandleRemoteHpRequest(MessageEnvelope message) private void StartHolepunchRoutine(MessageEnvelope message) { - if (IsCompleted()) return; - int offs = message.PayloadOffset; + try + { - var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + if (IsCompleted()) return; + int offs = message.PayloadOffset; - var epMsg = hpData.Endpoints; - othersPublicKey = hpData.DHPublic; - localEndpoints = epMsg.LocalEndpoints; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); - bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); - publicEndpointToConnect = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; + var epMsg = hpData.Endpoints; + othersPublicKey = hpData.DHPublic; + localEndpoints = epMsg.LocalEndpoints; - SignalCompletionCondition(); - if (IsCompleted()) return; + bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); + publicEndpointToConnect = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; + + SignalCompletionCondition(); + if (IsCompleted()) return; - if (!isListening) + if (!isListening) + { + TryPunch(); + } + } + catch (Exception e) { - TryPunch(); + if (!IsCompleted()) + { + Log(LogType.Exception, e.Message + "\n" + e.StackTrace); + Cancel(); + } } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs index d6f90ec..be65831 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs @@ -159,47 +159,59 @@ private async Task BindPort() private void StartHolepunchRoutine(MessageEnvelope message) { + try + { - if (IsCompleted()) return; - int offs = message.PayloadOffset; - var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + if (IsCompleted()) return; + int offs = message.PayloadOffset; - var epMsg = hpData.Endpoints; - otherPublicKey = hpData.DHPublic; - localEndpoints = epMsg.LocalEndpoints; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); - bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); - publicEndpointToConnect = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; - var time = double.Parse(message.KeyValuePairs["Time"]); + var epMsg = hpData.Endpoints; + otherPublicKey = hpData.DHPublic; + localEndpoints = epMsg.LocalEndpoints; - - // if there are local endpoints to test - if (epMsg.LocalEndpoints.Count > 0) - { - foreach (EndpointData localEp in epMsg.LocalEndpoints) + bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); + publicEndpointToConnect = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; + var time = double.Parse(message.KeyValuePairs["Time"]); + + + // if there are local endpoints to test + if (epMsg.LocalEndpoints.Count > 0) { - if (TryConnect(localEp, 500)) - return; - if (IsEstablished) return; + foreach (EndpointData localEp in epMsg.LocalEndpoints) + { + if (TryConnect(localEp, 500)) + return; + if (IsEstablished) return; + } } - } - if (IsEstablished) return; + if (IsEstablished) return; - var now = connection.GetTime(); - var delay = time - now; - - Log(LogType.Debug, "Delay: " + delay.ToString() + "ms"); + var now = connection.GetTime(); + var delay = time - now; - PreciseTimeAwaiter.Wait(delay); - if (IsEstablished) return; + Log(LogType.Debug, "Delay: " + delay.ToString() + "ms"); - for (int i = 0; i < 4; i++) - { - if (TryConnect(publicEndpointToConnect, (2000))) - return; + PreciseTimeAwaiter.Wait(delay); if (IsEstablished) return; + + for (int i = 0; i < 4; i++) + { + if (TryConnect(publicEndpointToConnect, (2000))) + return; + if (IsEstablished) return; + } + } + catch (Exception e) + { + if (!IsCompleted()) + { + Log(LogType.Exception, e.Message + "\n" + e.StackTrace); + TimedOut(); + } } } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 43b1352..39b0955 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -33,7 +33,13 @@ internal class ClientUdpHolepunchState : ConversationStateBase private int conditionCount = 0; - public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection connection, EndpointData serverEndpoint, EndpointData discoveryServerendPoint, ChannelInfo info, ILogger logger) : base(stateId, 20000, logger) + public ClientUdpHolepunchState(Guid stateId, + Guid destId, + IDistributedConnection connection, + EndpointData serverEndpoint, + EndpointData discoveryServerendPoint, + ChannelInfo info, + ILogger logger) : base(stateId, 20000, logger) { this.destId = destId; this.connection = connection; @@ -45,22 +51,29 @@ public ClientUdpHolepunchState(Guid stateId, Guid destId, IDistributedConnection //the initiator public async void Start() { - isInitiator = true; - Log(LogType.Debug, StateId.ToString()); + try + { + isInitiator = true; + Log(LogType.Debug, StateId.ToString()); - await StartUdpSocket(); + await StartUdpSocket().ConfigureAwait(false); - var msg = CreateEnvelope(); - msg.Header = InternalConstants.RequestHolepunchUdp; - msg.To = destId; + var msg = CreateEnvelope(); + msg.Header = InternalConstants.RequestHolepunchUdp; + msg.To = destId; - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - KnownTypeSerializer.SerializeHolepunchData(stream, GetHpData()); + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, GetHpData()); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); - connection.SendAsyncMessage(msg); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + connection.SendAsyncMessage(msg); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + catch(Exception ex) + { + OnError(ex.Message + "\n" + ex.StackTrace); + } } @@ -98,84 +111,136 @@ public override void HandleMessage(MessageEnvelope message) // the destination peer of hp private async void HandleRemoteHpRequest(MessageEnvelope message) { - Log(LogType.Debug, StateId.ToString()); + try + { + Log(LogType.Debug, StateId.ToString()); - int offs = message.PayloadOffset; - var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); - ChannelInfo = hpData.ChannelInfo; + ChannelInfo = hpData.ChannelInfo; - await StartUdpSocket(); + await StartUdpSocket().ConfigureAwait(false); - var msg = CreateEnvelope(); - msg.Header = InternalConstants.AckRequestHolepunchUdp; - msg.To = destId; + var msg = CreateEnvelope(); + msg.Header = InternalConstants.AckRequestHolepunchUdp; + msg.To = destId; - var hpd = GetHpData(); - hpd.ChannelInfo = null; + var hpd = GetHpData(); + hpd.ChannelInfo = null; - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - KnownTypeSerializer.SerializeHolepunchData(stream, hpd); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, hpd); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); - connection.SendAsyncMessage(msg); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); + connection.SendAsyncMessage(msg); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + catch (Exception ex) + { + OnError(ex.Message + "\n" + ex.StackTrace); + } } - private void StartHolepunchRoutine(MessageEnvelope message) + private async Task StartUdpSocket() { - int offs = message.PayloadOffset; - var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + Socket.SendBufferSize = 12800000; + Socket.ReceiveBufferSize = 12800000; + Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true); - var epMsg = hpData.Endpoints; - otherPublicKey = hpData.DHPublic; + Socket.Bind(new IPEndPoint(IPAddress.Any, 0)); + + selfLocalEp = (IPEndPoint)Socket.LocalEndPoint; + + var remoteEp = new EndpointData("0.0.0.0", selfLocalEp.Port); + + int retryCount = 0; + while (true) + { + remoteEp = await EndpointDiscoveryClient.GetUdpPublicEndpoint(Socket, discoveryServerendPoint.ToIpEndpoint(), 2000).ConfigureAwait(false); + if (remoteEp == null) + { + if (retryCount++ < 2) + continue; + Log(LogType.Warning, "Failed to get public endpoint"); + remoteEp = new EndpointData("0.0.0.0", selfLocalEp.Port); + } + break; + } + + selfRemoteEp = remoteEp.ToIpEndpoint(); + + Receive(); - var time = double.Parse(message.KeyValuePairs["Time"]); - SignalCompletionCondition(); - if (epMsg.LocalEndpoints.Count > 0) + } + + private void StartHolepunchRoutine(MessageEnvelope message) + { + try { - //var now0 = connection.GetTime(); - //var delay0 = (time - now0) / 4; - Thread.Sleep(50); + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + + var epMsg = hpData.Endpoints; + otherPublicKey = hpData.DHPublic; - foreach (EndpointData localEp in epMsg.LocalEndpoints) + var time = double.Parse(message.KeyValuePairs["Time"]); + SignalCompletionCondition(); + + if (epMsg.LocalEndpoints.Count > 0) { - for (int i = 0; i < 2; i++) + //var now0 = connection.GetTime(); + //var delay0 = (time - now0) / 4; + Thread.Sleep(50); + + foreach (EndpointData localEp in epMsg.LocalEndpoints) { - TryPunch(localEp, MessageFlags.HP); - Thread.Sleep(100); - if (IsCompleted()) return; + for (int i = 0; i < 2; i++) + { + TryPunch(localEp, MessageFlags.HP); + Thread.Sleep(100); + if (IsCompleted()) return; + } } } - } - if (IsCompleted()) return; + if (IsCompleted()) return; - // use server ip, peer is on same network as server - bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); - EndpointData publicEp = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; + // use server ip, peer is on same network as server + bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); + EndpointData publicEp = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; - var now = connection.GetTime(); - var delay = time - now; - if (delay > 500) - delay = 0; + var now = connection.GetTime(); + var delay = time - now; + if (delay > 500) + delay = 0; - Log(LogType.Debug, "Delay: " + delay.ToString() + "ms"); - PreciseTimeAwaiter.Wait(delay); - if (IsCompleted()) return; + Log(LogType.Debug, "Delay: " + delay.ToString() + "ms"); + PreciseTimeAwaiter.Wait(delay); + if (IsCompleted()) return; - for (int i = 0; i < 8; i++) + for (int i = 0; i < 8; i++) + { + TryPunch(publicEp, MessageFlags.HP); + PreciseTimeAwaiter.Wait(20 + (20 * i * i)); + if (IsCompleted()) return; + } + } + catch (Exception ex) { - TryPunch(publicEp, MessageFlags.HP); - PreciseTimeAwaiter.Wait(20 + (20 * i * i)); - if (IsCompleted()) return; + if (!IsCompleted()) + { + OnError(ex.Message + "\n" + ex.StackTrace); + } } + } private void TryPunch(EndpointData ep, MessageFlags flag) @@ -203,30 +268,6 @@ private void TryPunch(IPEndPoint ep, MessageFlags flag) } - private async Task StartUdpSocket() - { - - Socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - Socket.SendBufferSize = 12800000; - Socket.ReceiveBufferSize = 12800000; - Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, true); - - Socket.Bind(new IPEndPoint(IPAddress.Any, 0)); - - selfLocalEp = (IPEndPoint)Socket.LocalEndPoint; - - var remoteEp = await EndpointDiscoveryClient.GetUdpPublicEndpoint(Socket, discoveryServerendPoint.ToIpEndpoint(), 3000); - if (remoteEp == null) - { - Log(LogType.Warning, "Failed to get public endpoint"); - remoteEp = new EndpointData("0.0.0.0", selfLocalEp.Port); - } - selfRemoteEp = remoteEp.ToIpEndpoint(); - - Receive(); - - - } private async void Receive() { var buffer = BufferPool.RentBuffer(64000); @@ -239,7 +280,7 @@ private async void Receive() var remoteEP = (EndPoint)new IPEndPoint(IPAddress.Any, 0); var receiveTask = Socket.ReceiveFromAsync(new ArraySegment(buffer), SocketFlags.None, remoteEP); - var completedTask = await Task.WhenAny(receiveTask, Task.Delay(5000)); + var completedTask = await Task.WhenAny(receiveTask, Task.Delay(base.timeout)); if (completedTask == receiveTask) { SocketReceiveFromResult received = await receiveTask; @@ -252,6 +293,13 @@ private async void Receive() var ipep = (IPEndPoint)received.RemoteEndPoint; Log(LogType.Debug, "[-]Received 0xFF from " + ipep.ToString()); TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); + await Task.Delay(500); + if (IsCompleted()) return; + TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); + await Task.Delay(500); + if (IsCompleted()) return; + TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); + } } @@ -322,7 +370,7 @@ private void TimedOut() private void OnError(string error) { - Log(LogType.Debug, "Exception :" + error); + Log(LogType.Error, "Exception :" + error); var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchFail; connection.SendAsyncMessage(msg); diff --git a/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs b/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs index 6d6be33..4f782e7 100644 --- a/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs +++ b/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs @@ -33,7 +33,7 @@ public static async Task GetUdpPublicEndpoint(Socket socket, IPEnd if (result == retrieveTask) { if (!retrieveTask.IsFaulted) - return await retrieveTask; + return retrieveTask.Result; return null; } else diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 80a14bf..5be499a 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -135,8 +135,8 @@ private void ConnectionStateComplete(IConversationState state_) { var sessionEp = sslServer.GetSessionEndpoint(state.EphemeralClientId); var statusList = sessionManager.CreateSession(state.clientDbInfo, state.EphemeralClientId, sessionEp, state.clientLocalIps); - if (statusList != null) - PublishPeerList(new List() { statusList }); + //if (statusList != null) + // PublishPeerList(new List() { statusList }); } } diff --git a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs index 9491923..f6b822b 100644 --- a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs +++ b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs @@ -70,7 +70,7 @@ public PeerStatusList CreateSession(IClientDbInfo clientInfo, Guid ephemeralClie } // for instant sync of new peer var pubInfo = newSession.GetPublishInfo(); - newSession.ResetPublishInfo(); + //newSession.ResetPublishInfo(); publishTrigger.TrySetResult(true); return pubInfo; diff --git a/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs index 94d575f..017e209 100644 --- a/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs @@ -7,7 +7,10 @@ using System; using System.Collections.Concurrent; using System.Net; +using System.Runtime.InteropServices; using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; namespace NetworkLibrary.DistributedP2P.SimpleRelay { @@ -96,9 +99,19 @@ public RelayService(int TcpPort, int UdpPort) UdpServer.StartServer(); TcpServer.StartServer(); + //print(); } + //private async Task print() + //{ + // while (true) + // { + // await Task.Delay(1000); + // long s = Interlocked.Exchange(ref sent, 0); + // Console.WriteLine(s.ToString("N1")); + // } + //} private void TcpClientAccepted(Guid guid) { @@ -141,13 +154,14 @@ private void HandleUdpBytes(IPEndPoint endpoint, byte[] bytes, int offset, int c } } - + long sent; private bool RouteTcp(Guid guid, byte[] bytes, int offset, int count) { // we should look for room stuff here if (pipeMapTcp.TryGetValue(guid, out var to)) { - TcpServer.SendBytesToClient(to, bytes, offset, count); + // Interlocked.Add(ref sent, count); + TcpServer.SendBytesToClientDirect(to, bytes, offset, count); return true; } else if (roomMapTcp.TryGetValue(guid, out var room)) @@ -210,7 +224,7 @@ private void ManagePipeToken(Guid ephemeralId, byte[] bytes, int offset, int cou TcpPipeCreated(pipeState.Clients[0], pipeState.Clients[1]); } - TcpServer.SendBytesToClient(ephemeralId, new byte[1] { 0x01 }); + TcpServer.SendBytesToClientDirect(ephemeralId, new byte[1] { 0x01 },0,1); } else { @@ -227,7 +241,7 @@ private void ManagePipeToken(Guid ephemeralId, byte[] bytes, int offset, int cou if (activeRooms.TryGetValue(roomState.RoomId, out Room room)) { room.Add(token.Token, roomState); - TcpServer.SendBytesToClient(ephemeralId, new byte[1] { 0x01 }); + TcpServer.SendBytesToClientDirect(ephemeralId, new byte[1] { 0x01 }, 0, 1); } } } @@ -317,12 +331,13 @@ private bool VerifyToken(byte[] Token, DateTime expiration) var calculatedSignature = signer.Sign(Token, 0, 24); if (SignatureMatch(calculatedSignature, Token)) { - if (DateTime.UtcNow < expiration) + if (DateTime.UtcNow <= expiration) return true; return false; } else { + Console.WriteLine("Signature did not match"); return false; } } @@ -494,7 +509,7 @@ private void RouteRoomMessage(PeerRoomState to, byte[] b, int o, int c) { if (to.isTcp) { - TcpServer.SendBytesToClient(to.EphemeralId, b, o, c); + TcpServer.SendBytesToClientDirect(to.EphemeralId, b, o, c); } else { diff --git a/NetworkLibrary/TCP/Base/AsyncTcpServer.cs b/NetworkLibrary/TCP/Base/AsyncTcpServer.cs index 17ea95b..5592223 100644 --- a/NetworkLibrary/TCP/Base/AsyncTcpServer.cs +++ b/NetworkLibrary/TCP/Base/AsyncTcpServer.cs @@ -188,7 +188,11 @@ public void SendBytesToClient(Guid id, byte[] bytes, int offset, int count) if (Sessions.TryGetValue(id, out var session)) session.SendAsync(bytes, offset, count); } - + internal void SendBytesToClientDirect(Guid id, byte[] bytes, int offset, int count) + { + if (Sessions.TryGetValue(id, out var session)) + ((TcpSession)session).SendDirect(bytes, offset, count); + } protected virtual void HandleBytesReceived(Guid guid, byte[] bytes, int offset, int count) { OnBytesReceived?.Invoke(guid, bytes, offset, count); diff --git a/NetworkLibrary/TCP/Base/TcpSession.cs b/NetworkLibrary/TCP/Base/TcpSession.cs index 7f47140..2e86f29 100644 --- a/NetworkLibrary/TCP/Base/TcpSession.cs +++ b/NetworkLibrary/TCP/Base/TcpSession.cs @@ -204,7 +204,21 @@ protected virtual void HandleReceived(byte[] buffer, int offset, int count) #endregion Recieve #region Send - + internal void SendDirect(byte[] b, int o, int c) + { + if (IsSessionClosing()) + return; + try + { + sessionSocket.Send(b, o, c, SocketFlags.None); + } + catch (Exception e) + { + if (!IsSessionClosing()) + MiniLogger.Log(MiniLogger.LogLevel.Error, + "Unexpected error while sending async with tcp session" + e.Message + "Trace " + e.StackTrace); + } + } public void SendAsync(List> batch) { if (IsSessionClosing()) diff --git a/NetworkLibrary/TCP/SSL/SslClient.cs b/NetworkLibrary/TCP/SSL/SslClient.cs index 054b162..765ba84 100644 --- a/NetworkLibrary/TCP/SSL/SslClient.cs +++ b/NetworkLibrary/TCP/SSL/SslClient.cs @@ -102,7 +102,7 @@ public override Task ConnectAsyncAwaitable(string ip, int port) } catch { } - tcs.SetResult(false); + tcs.TrySetResult(false); } }, TaskScheduler.Default); From b118a8d0161c24f9c7dd72cd2b2ffffb71da29da Mon Sep 17 00:00:00 2001 From: dogancan ozturk Date: Wed, 23 Apr 2025 06:52:18 +0200 Subject: [PATCH 27/27] stabbilization --- .../Client/DistributedLobbyClient.cs | 58 ++++- .../StateManagement/ClientConnectionState.cs | 3 +- .../Client/StateManagement/ClientPipeState.cs | 6 +- .../ClientSequentialTcpHolepunchState.cs | 104 +++++---- .../ClientSimultaneousTcpHolepunchState.cs | 88 +++++--- .../ClientUdpHolepunchState.cs | 31 ++- .../Components/EndpointDiscoveryClient.cs | 27 +-- .../Components/InternalConstants.cs | 1 + .../DistributedP2P/Components/TimeSync.cs | 2 +- .../Server/DistributedLobbyServer.cs | 34 +-- .../Server/IServerConnection.cs | 1 + .../DistributedP2P/Server/ServerSession.cs | 18 +- .../DistributedP2P/Server/SessionManager.cs | 77 +++++-- .../StateManagement/ServerConnectionState.cs | 2 +- .../SimpleRelay/RelayService.cs | 205 ++++++++++-------- 15 files changed, 399 insertions(+), 258 deletions(-) diff --git a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs index 0f6ea5d..7644a14 100644 --- a/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs +++ b/NetworkLibrary/DistributedP2P/Client/DistributedLobbyClient.cs @@ -51,6 +51,7 @@ public bool IsConnected bool isDisposed = false; ILogger logger; + DateTime lastKeepAlive = DateTime.Now; public DistributedLobbyClient(IClientDbConnection clientDbConnector, IClientAuthenticationProvider clientAuthProvider, @@ -88,24 +89,55 @@ public async Task ConnectAsync(string ip, int port) Guid conversationId = Guid.NewGuid(); var conState = new ClientConnectionState(conversationId, this, clientDbConnector, authToken, logger); stateManager.RegisterState(conState); + + conState.OnComplete += (_) => + { + if (conState.IsSuccesful) + { + SessionId = conState.SessionId; + DiscoveryServerEndpoint = new EndpointData(ip, conState.EDSPort); + Console.WriteLine($"Connected to server {ip}:{port} with session {SessionId} and discovery port {conState.EDSPort}"); + IsConnected = true; + timeSync.StartAutoTimeSync(); + StartKeepAlive(); + } + }; + conState.Start(); await conState.WaitCompletion().ConfigureAwait(false); + return conState.IsSuccesful; - if (conState.IsSuccesful) + } + else return false; + } + + private async void StartKeepAlive() + { + try + { + lastKeepAlive = DateTime.Now; + while (IsConnected) { - SessionId = conState.SessionId; - DiscoveryServerEndpoint = new EndpointData(ip, conState.EDSPort); - Console.WriteLine($"Connected to server {ip}:{port} with session {SessionId} and discovery port {conState.EDSPort}"); - IsConnected = true; - timeSync.StartAutoTimeSync(); - return true; - } + await Task.Delay(4000).ConfigureAwait(false); - return false; + MessageEnvelope envelope = new MessageEnvelope(); + envelope.Header = InternalConstants.KeepAlive; + envelope.IsInternal = true; + SendAsyncMessage(envelope); + if((DateTime.Now- lastKeepAlive).TotalMilliseconds > 10000) + { + Disconnect(); + return; + } + } } - else return false; + catch + { + Disconnect(); + } + } #region Send @@ -170,7 +202,10 @@ private void HandleServerMsg(MessageEnvelope envelope) case InternalConstants.RequestSimultaneousHolepunchTcp: ManageSimyltaneousTcpHolepunchReq(envelope); - + break; + + case InternalConstants.KeepAlive: + lastKeepAlive= DateTime.Now; break; } @@ -373,6 +408,7 @@ public Task SyncTime() public void Disconnect() { + IsConnected= false; sslClient.Disconnect(); } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs index 908dfd3..6ed3641 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientConnectionState.cs @@ -71,6 +71,7 @@ private void SyncTime() connection.SendAsyncMessage(msg); }, TaskScheduler.Default); + task.ConfigureAwait(false); } @@ -87,7 +88,7 @@ private void SendClientPublicData(MessageEnvelope message) private void HandleConnectionSucces(MessageEnvelope message) { SessionId = message.To; - EDSPort = int.Parse(message.KeyValuePairs["EDSPort"]); + EDSPort = int.Parse(message.KeyValuePairs["EDPort"]); Completed(succes: true); } diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs index 8c86d71..05a2ba2 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientPipeState.cs @@ -315,7 +315,7 @@ private async void HandlePipeTokenUdp(MessageEnvelope message) } - private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndPoint remoteEndPoint, int timeoutMs = 3000) + private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndPoint remoteEndPoint, int timeoutMs = 5000) { try { @@ -324,7 +324,7 @@ private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndP var sendTask = udpSocket.SendAsync(new ArraySegment(token), SocketFlags.None); var sendTimeout = Task.Delay(timeoutMs); - if (await Task.WhenAny(sendTask, sendTimeout) == sendTimeout || + if (await Task.WhenAny(sendTask, sendTimeout).ConfigureAwait(false) == sendTimeout || sendTask.Result != token.Length) { Log(LogType.Warning, "Udp Token Send Timeout"); @@ -335,7 +335,7 @@ private async Task UdpTokenExchange(Socket udpSocket, byte[] token, IPEndP var receiveTask = udpSocket.ReceiveAsync(new ArraySegment(responseBuffer), SocketFlags.None); var receiveTimeout = Task.Delay(timeoutMs); - if (await Task.WhenAny(receiveTask, receiveTimeout) == receiveTimeout) + if (await Task.WhenAny(receiveTask, receiveTimeout).ConfigureAwait(false) == receiveTimeout) { Log(LogType.Exception,"Udp Token Receive Timeout"); return false; diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs index 5c46f6f..e2b9f02 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSequentialTcpHolepunchState.cs @@ -64,23 +64,33 @@ public ClientSequentialTcpHolepunchState(Guid stateId, Guid destId, IDistributed //the initiator public async void Start() { - isInitiator = true; - Log(LogType.Debug, StateId.ToString()); + try + { - await BindPort(); - Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); - var msg = CreateEnvelope(); - msg.To = destId; - msg.Header = InternalConstants.RequestSequentialHolepunchTcp; + isInitiator = true; + Log(LogType.Debug, StateId.ToString()); - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - KnownTypeSerializer.SerializeHolepunchData(stream, GetHpData()); + await BindPort().ConfigureAwait(false); + Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); - connection.SendAsyncMessage(msg); + var msg = CreateEnvelope(); + msg.To = destId; + msg.Header = InternalConstants.RequestSequentialHolepunchTcp; - SharerdMemoryStreamPool.ReturnStreamStatic(stream); + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, GetHpData()); + + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + connection.SendAsyncMessage(msg); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + catch (Exception e) + { + Log(LogType.Exception, e.Message + "\n" + e.StackTrace); + Cancel(); + } } @@ -94,8 +104,7 @@ public override void HandleMessage(MessageEnvelope message) break; case InternalConstants.StartHP: - message.LockBytes(); - ThreadPool.UnsafeQueueUserWorkItem((s) => StartHolepunchRoutine(message), null); + StartHolepunchRoutine(message); break; case InternalConstants.PunchSuccesAck: @@ -113,30 +122,39 @@ public override void HandleMessage(MessageEnvelope message) // the destination peer of hp private async void HandleRemoteHpRequest(MessageEnvelope message) { - Log(LogType.Debug,StateId.ToString()); - int offs = message.PayloadOffset; - var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + try + { + Log(LogType.Debug, StateId.ToString()); + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); - ChannelInfo = hpData.ChannelInfo; - await BindPort(); - Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); + ChannelInfo = hpData.ChannelInfo; + await BindPort().ConfigureAwait(false); + Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); - StartTcpListener(); - Log(LogType.Debug, "listening"); + StartTcpListener(); + Log(LogType.Debug, "listening"); - var msg = CreateEnvelope(); - msg.To = destId; - msg.Header = InternalConstants.AckRequestHolepunchTcp; + var msg = CreateEnvelope(); + msg.To = destId; + msg.Header = InternalConstants.AckRequestHolepunchTcp; - var hpd = GetHpData(); - hpd.ChannelInfo = null; + var hpd = GetHpData(); + hpd.ChannelInfo = null; - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - KnownTypeSerializer.SerializeHolepunchData(stream, hpd); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, hpd); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + + connection.SendAsyncMessage(msg); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + catch (Exception e) + { + Log(LogType.Exception, e.Message + "\n" + e.StackTrace); + Cancel(); + } - connection.SendAsyncMessage(msg); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); } private void StartHolepunchRoutine(MessageEnvelope message) @@ -156,7 +174,7 @@ private void StartHolepunchRoutine(MessageEnvelope message) bool useServerIp = IPHelper.IsZero(epMsg.IpRemote); publicEndpointToConnect = new EndpointData() { Ip = useServerIp ? serverEndpoint.Ip : epMsg.IpRemote, Port = epMsg.PortRemote }; - SignalCompletionCondition(); + AddompletionCondition(); if (IsCompleted()) return; if (!isListening) @@ -176,7 +194,7 @@ private void StartHolepunchRoutine(MessageEnvelope message) } - private void TryPunch() + private async void TryPunch() { try { @@ -184,7 +202,7 @@ private void TryPunch() { foreach (EndpointData localEp in localEndpoints) { - if (TryConnect(localEp, 600)) + if (await TryConnect(localEp, 600).ConfigureAwait(false)) return; if (IsCompleted()) return; @@ -194,7 +212,7 @@ private void TryPunch() for (int i = 0; i < 1; i++) { - if (TryConnect(publicEndpointToConnect, (2000))) + if (await TryConnect(publicEndpointToConnect, (2000)).ConfigureAwait(false)) return; if (IsCompleted()) return; @@ -265,7 +283,7 @@ private void Swap() } } - private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) + private async Task TryConnect(EndpointData endpoint, int timeoutMs = 600) { Socket connectSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); @@ -277,7 +295,7 @@ private bool TryConnect(EndpointData endpoint, int timeoutMs = 600) var connectTask = connectSocket.ConnectAsync(endpoint.ToIpEndpoint()); var timeoutTask = Task.Delay(timeoutMs); - if (Task.WhenAny(connectTask, timeoutTask).GetAwaiter().GetResult() == connectTask) + if (await Task.WhenAny(connectTask, timeoutTask).ConfigureAwait(false) == connectTask) { if (connectTask.IsFaulted) { @@ -310,7 +328,7 @@ private async Task BindPort() clientSocket.Bind(selfLocalEp); selfLocalEp = (IPEndPoint)clientSocket.LocalEndPoint; - var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket, discoveryServerEp.ToIpEndpoint(), 5000); + var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket, discoveryServerEp.ToIpEndpoint(), 5000).ConfigureAwait(false); if (remoteEp == null) { Log(LogType.Warning, "Failed to get public endpoint"); @@ -336,10 +354,8 @@ private int StartTcpListener() listeningSocket.ReceiveBufferSize = 12800000; listeningSocket.Bind(selfLocalEp); - selfLocalEp = (IPEndPoint)listeningSocket.LocalEndPoint; - Listen(); isListening = true; return ((IPEndPoint)listeningSocket.LocalEndPoint).Port; @@ -421,15 +437,15 @@ private void HandleAcceptedSocket(Socket socket) private void HandleFailure() { - Log(LogType.Debug, "Failed Punch"); + Log(LogType.Warning, "Failed Punch"); Completed(false); } private void HandleRemoteSucces(MessageEnvelope message) { - SignalCompletionCondition(); + AddompletionCondition(); } - private void SignalCompletionCondition() + private void AddompletionCondition() { if (Interlocked.Increment(ref conditionCount) == 2) { diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs index be65831..e912e32 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientSimultaneousTcpHolepunchState.cs @@ -53,28 +53,36 @@ public ClientSimultaneousTcpHolepunchState(Guid stateId, Guid destId, IDistribut //the initiator public async void Start() { + try + { - isInitiator = true; - Log(LogType.Debug, StateId.ToString()); + isInitiator = true; + Log(LogType.Debug, StateId.ToString()); - await BindPort(); - Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); + await BindPort().ConfigureAwait(false); + Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); - StartListening(); - Log(LogType.Debug, "listening"); + StartListening(); + Log(LogType.Debug, "listening"); - var msg = CreateEnvelope(); - msg.To = destId; - msg.Header = InternalConstants.RequestSimultaneousHolepunchTcp; + var msg = CreateEnvelope(); + msg.To = destId; + msg.Header = InternalConstants.RequestSimultaneousHolepunchTcp; - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - KnownTypeSerializer.SerializeHolepunchData(stream, GetHpData()); + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, GetHpData()); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); - connection.SendAsyncMessage(msg); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + connection.SendAsyncMessage(msg); + + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + catch (Exception e) + { + Log(LogType.Exception, e.Message + "\n" + e.StackTrace); + Cancel(); + } - SharerdMemoryStreamPool.ReturnStreamStatic(stream); - } @@ -104,32 +112,42 @@ public override void HandleMessage(MessageEnvelope message) // the destination peer of hp private async void HandleRemoteHpRequest(MessageEnvelope message) { - Log(LogType.Debug, StateId.ToString()); - int offs = message.PayloadOffset; - var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); + try + { - ChannelInfo = hpData.ChannelInfo; - await BindPort(); - Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); - StartListening(); - Log(LogType.Debug, "listening"); + Log(LogType.Debug, StateId.ToString()); + int offs = message.PayloadOffset; + var hpData = KnownTypeSerializer.DeserializeHolepunchData(message.Payload, ref offs); - var msg = CreateEnvelope(); - msg.To = destId; - msg.Header = InternalConstants.AckRequestHolepunchTcp; + ChannelInfo = hpData.ChannelInfo; + await BindPort(); + Log(LogType.Debug, $"Local port {selfLocalEp.Port} Remote port {selfRemoteEp.Port}"); - var hpd = GetHpData(); - hpd.ChannelInfo = null; + StartListening(); + Log(LogType.Debug, "listening"); - var stream = SharerdMemoryStreamPool.RentStreamStatic(); - KnownTypeSerializer.SerializeHolepunchData(stream, hpd); - msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + var msg = CreateEnvelope(); + msg.To = destId; + msg.Header = InternalConstants.AckRequestHolepunchTcp; + + var hpd = GetHpData(); + hpd.ChannelInfo = null; + + var stream = SharerdMemoryStreamPool.RentStreamStatic(); + KnownTypeSerializer.SerializeHolepunchData(stream, hpd); + msg.SetPayload(stream.GetBuffer(), 0, stream.Position32); + + connection.SendAsyncMessage(msg); + SharerdMemoryStreamPool.ReturnStreamStatic(stream); + } + catch (Exception e) + { + Log(LogType.Exception, e.Message + "\n" + e.StackTrace); + Cancel(); + } - connection.SendAsyncMessage(msg); - SharerdMemoryStreamPool.ReturnStreamStatic(stream); - } private async Task BindPort() @@ -138,7 +156,7 @@ private async Task BindPort() clientSocket.Bind(selfLocalEp); selfLocalEp = (IPEndPoint)clientSocket.LocalEndPoint; - var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket, discoveryServerEp.ToIpEndpoint(), 5000); + var remoteEp = await EndpointDiscoveryClient.GetTcpPublicEndpoint(clientSocket, discoveryServerEp.ToIpEndpoint(), 5000).ConfigureAwait(false); if (remoteEp == null) { Log(LogType.Warning, "Failed to get public endpoint"); diff --git a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs index 39b0955..e629cfd 100644 --- a/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs +++ b/NetworkLibrary/DistributedP2P/Client/StateManagement/ClientUdpHolepunchState.cs @@ -70,7 +70,7 @@ public async void Start() SharerdMemoryStreamPool.ReturnStreamStatic(stream); } - catch(Exception ex) + catch (Exception ex) { OnError(ex.Message + "\n" + ex.StackTrace); } @@ -89,8 +89,7 @@ public override void HandleMessage(MessageEnvelope message) break; case InternalConstants.StartHP: - message.LockBytes(); - ThreadPool.UnsafeQueueUserWorkItem((s) => StartHolepunchRoutine(message), null); + StartHolepunchRoutine(message); break; case InternalConstants.PunchSuccesAck: @@ -178,7 +177,7 @@ private async Task StartUdpSocket() } - private void StartHolepunchRoutine(MessageEnvelope message) + private async void StartHolepunchRoutine(MessageEnvelope message) { try { @@ -195,14 +194,13 @@ private void StartHolepunchRoutine(MessageEnvelope message) { //var now0 = connection.GetTime(); //var delay0 = (time - now0) / 4; - Thread.Sleep(50); foreach (EndpointData localEp in epMsg.LocalEndpoints) { for (int i = 0; i < 2; i++) { TryPunch(localEp, MessageFlags.HP); - Thread.Sleep(100); + await Task.Delay(100).ConfigureAwait(false); if (IsCompleted()) return; } } @@ -222,13 +220,14 @@ private void StartHolepunchRoutine(MessageEnvelope message) delay = 0; Log(LogType.Debug, "Delay: " + delay.ToString() + "ms"); - PreciseTimeAwaiter.Wait(delay); + if (delay > 0) + await Task.Delay((int)delay).ConfigureAwait(false); if (IsCompleted()) return; for (int i = 0; i < 8; i++) { TryPunch(publicEp, MessageFlags.HP); - PreciseTimeAwaiter.Wait(20 + (20 * i * i)); + await Task.Delay(20 + (20 * i * i)).ConfigureAwait(false); if (IsCompleted()) return; } } @@ -280,7 +279,7 @@ private async void Receive() var remoteEP = (EndPoint)new IPEndPoint(IPAddress.Any, 0); var receiveTask = Socket.ReceiveFromAsync(new ArraySegment(buffer), SocketFlags.None, remoteEP); - var completedTask = await Task.WhenAny(receiveTask, Task.Delay(base.timeout)); + var completedTask = await Task.WhenAny(receiveTask, Task.Delay(base.timeout)).ConfigureAwait(false); if (completedTask == receiveTask) { SocketReceiveFromResult received = await receiveTask; @@ -293,10 +292,10 @@ private async void Receive() var ipep = (IPEndPoint)received.RemoteEndPoint; Log(LogType.Debug, "[-]Received 0xFF from " + ipep.ToString()); TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); - await Task.Delay(500); + await Task.Delay(500).ConfigureAwait(false); if (IsCompleted()) return; TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); - await Task.Delay(500); + await Task.Delay(500).ConfigureAwait(false); if (IsCompleted()) return; TryPunch((IPEndPoint)received.RemoteEndPoint, MessageFlags.HPAck); @@ -316,7 +315,7 @@ private async void Receive() } else { - Log(LogType.Debug, "Cancel1"); + Log(LogType.Warning, "Cancel1"); Cancel(); return; } @@ -329,7 +328,7 @@ private async void Receive() } catch (Exception e) { - Log(LogType.Debug, "ERROR" + e.Message); + Log(LogType.Error, "ERROR" + e.Message); Cancel(); return; } @@ -361,7 +360,7 @@ private void ReceivedBidirectional(EndPoint remoteEndPoint) private void TimedOut() { - Log(LogType.Debug, "Timed out"); + Log(LogType.Warning, "Timed out"); var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchFail; connection.SendAsyncMessage(msg); @@ -370,7 +369,7 @@ private void TimedOut() private void OnError(string error) { - Log(LogType.Error, "Exception :" + error); + Log(LogType.Warning, "Exception :" + error); var msg = CreateEnvelope(); msg.Header = InternalConstants.PunchFail; connection.SendAsyncMessage(msg); @@ -380,7 +379,7 @@ private void OnError(string error) private void HandleFailure() { - Log(LogType.Debug, "Failed Punch"); + Log(LogType.Warning, "Failed Punch"); Completed(false); } diff --git a/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs b/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs index 4f782e7..6aaf47f 100644 --- a/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs +++ b/NetworkLibrary/DistributedP2P/Components/EndpointDiscoveryClient.cs @@ -45,21 +45,21 @@ public static async Task GetUdpPublicEndpoint(Socket socket, IPEnd private static async Task GetPublicEndpointTcp(Socket socket, IPEndPoint whereToAsk) { - - await socket.ConnectAsync(whereToAsk).ConfigureAwait(false); - var buff = BufferPool.RentBuffer(1024); - int received = await socket.ReceiveAsync(new ArraySegment(buff), SocketFlags.None).ConfigureAwait(false); + await socket.ConnectAsync(whereToAsk).ConfigureAwait(false); + var buff = BufferPool.RentBuffer(1024); + int received = await socket.ReceiveAsync(new ArraySegment(buff), SocketFlags.None).ConfigureAwait(false); - if (received == 0) - { - throw new Exception("No data received"); - } - BufferPool.ReturnBuffer(buff); + if (received == 0) + { + return null; + } + + var data = KnownTypeSerializer.DeserializeEndpointData(buff, 0); + BufferPool.ReturnBuffer(buff); + return data; - return KnownTypeSerializer.DeserializeEndpointData(buff, 0); - } private static async Task GetPublicEndpointUdp(Socket socket, IPEndPoint whereToAsk) @@ -68,10 +68,11 @@ private static async Task GetPublicEndpointUdp(Socket socket, IPEn var buff = BufferPool.RentBuffer(1024); await socket.ReceiveFromAsync(new ArraySegment(buff), SocketFlags.None, whereToAsk).ConfigureAwait(false); + + var data = KnownTypeSerializer.DeserializeEndpointData(buff, 0); BufferPool.ReturnBuffer(buff); + return data; - return KnownTypeSerializer.DeserializeEndpointData(buff, 0); - } } } diff --git a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs index b162dff..2b115dd 100644 --- a/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs +++ b/NetworkLibrary/DistributedP2P/Components/InternalConstants.cs @@ -35,6 +35,7 @@ internal class InternalConstants public const string RequestCreateOrJointRoom = "o"; public const string ResponseCreateOrJointRoom = "p"; public const string JoinedRoom = "r"; + public const string KeepAlive = "s"; } } diff --git a/NetworkLibrary/DistributedP2P/Components/TimeSync.cs b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs index 1ac9c0e..6e596f3 100644 --- a/NetworkLibrary/DistributedP2P/Components/TimeSync.cs +++ b/NetworkLibrary/DistributedP2P/Components/TimeSync.cs @@ -50,7 +50,7 @@ public async void StartAutoTimeSync() { try { - await Task.Delay(PeriodLocal); + await Task.Delay(PeriodLocal).ConfigureAwait(false); if (cancel) return; diff --git a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs index 5be499a..4fef739 100644 --- a/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs +++ b/NetworkLibrary/DistributedP2P/Server/DistributedLobbyServer.cs @@ -1,4 +1,5 @@ using NetworkLibrary.Components.Crypto.Certificate; +using NetworkLibrary.DistributedP2P.Channels.Components; using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Server.StateManagement; using NetworkLibrary.DistributedP2P.SimpleRelay; @@ -34,8 +35,8 @@ public class ServerParameters public class DistributedLobbyServerBase : IServerConnection, IDisposable { public readonly int SSlPort; - public readonly int TcpPort; - public readonly int UdpPort; + public readonly int TcpRelayPort; + public readonly int UdpRelayPort; public readonly int DiscoveryServerPort; private X509Certificate2 serverCertificate; @@ -55,8 +56,6 @@ public class DistributedLobbyServerBase : IServerConnection, IDisposable RelayService relayService; RoomManager roomManager = new RoomManager(); NTPServer ntpServer; - private byte[] serverKey = new byte[16]; - EndpointDiscoveryServer discoveryServer; public DistributedLobbyServerBase(Dependencies dependencies, ServerParameters parameters) { @@ -64,8 +63,8 @@ public DistributedLobbyServerBase(Dependencies dependencies, ServerParameters pa dbConnector = dependencies.DbConnector; logger = dependencies.logger; SSlPort = parameters.SSlPort; - TcpPort = parameters.TcpPort; - UdpPort = parameters.UdpPort; + TcpRelayPort = parameters.TcpPort; + UdpRelayPort = parameters.UdpPort; DiscoveryServerPort = parameters.DiscoveryServerPort; stateManager = new StateManager(logger); serverCertificate = parameters.certificate ?? CertificateGenerator.GenerateSelfSignedCertificate(); @@ -81,7 +80,7 @@ public void StartServer() ntpServer = new NTPServer(SSlPort, serverClock); ntpServer.Start(); - relayService = new RelayService(TcpPort, UdpPort); + relayService = new RelayService(TcpRelayPort, UdpRelayPort); sslServer.OnClientRequestedConnection += ValidateSslConnection; sslServer.OnClientAccepted += SslClientAccepted; @@ -168,17 +167,13 @@ private void SslMessageReceived(Guid guid, MessageEnvelope envelope) } else { - sessionManager.HandleMessage(guid, envelope); + sessionManager.RouteMessage(guid, envelope); } } private void HandleInternalMessage(Guid clientId, MessageEnvelope message) { - //server messages. - // time sync - // holepunching - // ping - + message.From = clientId; if (stateManager.HandleMessage(message)) @@ -231,6 +226,10 @@ private void HandleInternalMessage(Guid clientId, MessageEnvelope message) HandleTimeSync(clientId, message); break; + + case InternalConstants.KeepAlive: + sessionManager.HandleMessage(clientId,message); + break; } } int ctr = 0; @@ -325,11 +324,14 @@ public Task SendMessageAndWaitResponse(MessageEnvelope envelope return sslServer.SendMessageAndWaitResponse(envelope.To, envelope); } - private void SslClientDisconnected(Guid guid) + private void SslClientDisconnected(Guid ephemeralId) { - sessionManager.DestroySession(guid); + sessionManager.DestroySession(ephemeralId); + } + public void EndSession (Guid ephemeralId) + { + sslServer.CloseSession(ephemeralId); } - public void ShutDownServer() { sslServer.ShutdownServer(); diff --git a/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs b/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs index 05a7baf..d090fe3 100644 --- a/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs +++ b/NetworkLibrary/DistributedP2P/Server/IServerConnection.cs @@ -13,5 +13,6 @@ internal interface IServerConnection : IDistributedConnection { void GetPipeToken(bool isTcpPipe, Guid fromEphemeral, Guid toEphemeral, Action onReady); RoomResult CreateOrJoinRoom(string roomName, string roomPassword, Guid peerId, RoomProtocol protocol); + void EndSession(Guid ephemeralId); } } diff --git a/NetworkLibrary/DistributedP2P/Server/ServerSession.cs b/NetworkLibrary/DistributedP2P/Server/ServerSession.cs index 5f0d618..7ec9958 100644 --- a/NetworkLibrary/DistributedP2P/Server/ServerSession.cs +++ b/NetworkLibrary/DistributedP2P/Server/ServerSession.cs @@ -2,8 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; -using System.Text; -using static System.Collections.Specialized.BitVector32; namespace NetworkLibrary.DistributedP2P.Server { @@ -36,13 +34,14 @@ internal class ServerSession public List ClientLocalIps { get; } public DateTime OnlineSince { get; internal set; } + public DateTime lastKeepAlive= DateTime.Now; private PeerStatusList statusList = new PeerStatusList(); - private ConcurrentDictionary onlinePeers = new ConcurrentDictionary(); - private PeerStatus status = new PeerStatus(); + private ConcurrentDictionary onlinePeers = new ConcurrentDictionary(); + private PeerStatus status = new PeerStatus(); - public ServerSession(IClientDbInfo clientInfo,Guid ephemeralId, System.Net.IPEndPoint clientPublicIp, List clientLocalIps) + public ServerSession(IClientDbInfo clientInfo, Guid ephemeralId, System.Net.IPEndPoint clientPublicIp, List clientLocalIps) { ClientInfo = clientInfo; EphemeralId = ephemeralId; @@ -63,7 +62,7 @@ internal bool Knows(Guid newPeer) return true; } - internal void AddNewPeer(Guid ephemeralId,PeerStatus status) + internal void AddNewPeer(Guid ephemeralId, PeerStatus status) { if (onlinePeers.TryAdd(ephemeralId, status)) { @@ -92,7 +91,7 @@ internal PeerStatusList GetPublishInfo() return null; // must be hard copy // either hard copy or lock until all published over network. - PeerStatusList list = new PeerStatusList(); + PeerStatusList list = new PeerStatusList(); list.WentOffline = new ConcurrentDictionary(statusList.WentOffline); list.NewOnline = new ConcurrentDictionary(statusList.NewOnline); list.WhoNeedsToKnow = EphemeralId; @@ -105,5 +104,10 @@ internal void ResetPublishInfo() statusList.WentOffline.Clear(); statusList.NewOnline.Clear(); } + + internal void KeepAliveMark() + { + lastKeepAlive = DateTime.Now; + } } } diff --git a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs index f6b822b..9acb017 100644 --- a/NetworkLibrary/DistributedP2P/Server/SessionManager.cs +++ b/NetworkLibrary/DistributedP2P/Server/SessionManager.cs @@ -1,19 +1,8 @@ -using NetworkLibrary.Components; -using NetworkLibrary.Components.Crypto.DigitalSignature; -using NetworkLibrary.DistributedP2P.Components; -using NetworkLibrary.MessageProtocol; -using NetworkLibrary.MessageProtocol.Serialization; -using NetworkLibrary.P2P.Components.HolePunch; -using NetworkLibrary.TCP.Base; -using NetworkLibrary.UDP; -using NetworkLibrary.Utils; +using NetworkLibrary.DistributedP2P.Components; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Net; -using System.Security.Cryptography; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -27,29 +16,69 @@ internal class SessionManager internal ConcurrentDictionary serverSessions = new ConcurrentDictionary(); internal HashSet lastSnapshot = new HashSet(); - private TaskCompletionSource publishTrigger = new TaskCompletionSource(); + private TaskCompletionSource publishTrigger = new TaskCompletionSource(); public event Action> PeerListPublish; - IDistributedConnection serverConnection; + IServerConnection serverConnection; private bool shutdown; private object publishMutex = new object(); - public SessionManager(IDistributedConnection serverConnection) + public SessionManager(IServerConnection serverConnection) { this.serverConnection = serverConnection; PublishRoutine(); + KeepAliveRoutine(); } - internal bool HandleMessage(Guid from, MessageEnvelope envelope) + private async void KeepAliveRoutine() + { + try + { + while (true) + { + await Task.Delay(10000).ConfigureAwait(false); + var now = DateTime.Now; + foreach (var ses in serverSessions) + { + if ((now - ses.Value.lastKeepAlive).TotalMilliseconds > 10000) + { + serverConnection.EndSession(ses.Key); + DestroySession(ses.Key); + } + } + } + + } + catch { } + } + + internal bool RouteMessage(Guid from, MessageEnvelope envelope) { Guid to = envelope.To; envelope.To = Guid.Empty; envelope.From = from; serverConnection.SendAsyncMessage(to, envelope); - return true; + return true; } - + internal bool HandleMessage(Guid from, MessageEnvelope envelope) + { + switch (envelope.Header) + { + case InternalConstants.KeepAlive: + if (serverSessions.TryGetValue(from, out var session)) + { + session.KeepAliveMark(); + var msg = new MessageEnvelope(); + msg.Header = InternalConstants.KeepAlive; + msg.IsInternal = true; + serverConnection.SendAsyncMessage(from,msg); + } + break; + } + return true; + } + public PeerStatusList CreateSession(IClientDbInfo clientInfo, Guid ephemeralClientId, IPEndPoint clientPublicIp, List clientLocalIps) { var newSession = new ServerSession(clientInfo, ephemeralClientId, clientPublicIp, clientLocalIps); @@ -66,16 +95,16 @@ public PeerStatusList CreateSession(IClientDbInfo clientInfo, Guid ephemeralClie } } serverSessions.TryAdd(ephemeralClientId, newSession); - + } // for instant sync of new peer - var pubInfo = newSession.GetPublishInfo(); + var pubInfo = newSession.GetPublishInfo(); //newSession.ResetPublishInfo(); publishTrigger.TrySetResult(true); return pubInfo; } - + } internal void DestroySession(Guid ephemeralClientId) @@ -102,7 +131,7 @@ internal bool IsSessionActive(Guid ephemeralClientId) internal bool GetSessionData(Guid guid, out ServerSession sesData) { return serverSessions.TryGetValue(guid, out sesData); - + } internal async void PublishRoutine() @@ -115,7 +144,7 @@ internal async void PublishRoutine() PubList.Clear(); - lock (publishMutex) + lock (publishMutex) { foreach (var sessionKV in serverSessions) { @@ -128,7 +157,7 @@ internal async void PublishRoutine() } } - + PeerListPublish?.Invoke(PubList); await Task.Delay(1000); } diff --git a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs index c430876..55f87a0 100644 --- a/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs +++ b/NetworkLibrary/DistributedP2P/Server/StateManagement/ServerConnectionState.cs @@ -183,7 +183,7 @@ private void SendGood() msg.Header = InternalConstants.ConnectionAckGood; msg.To = EphemeralClientId; msg.KeyValuePairs = new Dictionary(); - msg.KeyValuePairs["EDSPort"] = endpointDiscoveryServerPort.ToString(); + msg.KeyValuePairs["EDPort"] = endpointDiscoveryServerPort.ToString(); lock (cancellationMutex) { if (IsCompleted()) diff --git a/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs index 017e209..28801f9 100644 --- a/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs +++ b/NetworkLibrary/DistributedP2P/SimpleRelay/RelayService.cs @@ -1,4 +1,6 @@ -using NetworkLibrary.Components.Crypto.DigitalSignature; +//#define PRINT + +using NetworkLibrary.Components.Crypto.DigitalSignature; using NetworkLibrary.DistributedP2P.Components; using NetworkLibrary.DistributedP2P.Server.StateManagement; using NetworkLibrary.TCP.Base; @@ -7,11 +9,9 @@ using System; using System.Collections.Concurrent; using System.Net; -using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; - namespace NetworkLibrary.DistributedP2P.SimpleRelay { internal class PipeToken @@ -30,7 +30,7 @@ internal bool IsExpired() } internal class TcpTokenStorage { - internal byte[] Token = new byte[PipeData.TokenLength]; + internal byte[] TokenBytes = new byte[PipeData.TokenLength]; int tokenOffset = 0; internal int StoreTokenFragment(byte[] tokenFragment, int offset, int count) @@ -41,7 +41,7 @@ internal int StoreTokenFragment(byte[] tokenFragment, int offset, int count) } else { - Buffer.BlockCopy(tokenFragment, offset, Token, tokenOffset, count); + Buffer.BlockCopy(tokenFragment, offset, TokenBytes, tokenOffset, count); tokenOffset += count; if (tokenOffset == PipeData.TokenLength) { @@ -76,7 +76,8 @@ internal class RelayService : IDisposable private readonly ConcurrentDictionary tokenStorage = new ConcurrentDictionary(); byte[] cryptoKey = new byte[32]; - PrivateKeySign signer; + + ThreadLocal signer = new ThreadLocal(); readonly object tokenMtex = new object(); public RelayService(int TcpPort, int UdpPort) @@ -94,30 +95,37 @@ public RelayService(int TcpPort, int UdpPort) TcpServer.OnBytesReceived += HandleTcpBytes; UdpServer.OnBytesRecieved += HandleUdpBytes; - signer = new PrivateKeySign(cryptoKey); - UdpServer.StartServer(); TcpServer.StartServer(); - //print(); +#if PRINT + print(); +#endif } - //private async Task print() - //{ - // while (true) - // { - // await Task.Delay(1000); - // long s = Interlocked.Exchange(ref sent, 0); - // Console.WriteLine(s.ToString("N1")); - // } - //} + private PrivateKeySign GetSigner() + { + if (signer.Value == null) + { + signer.Value = new PrivateKeySign(cryptoKey); + } + return signer.Value; + } + + private async Task print() + { + while (true) + { + await Task.Delay(1000); + long s = Interlocked.Exchange(ref sent, 0); + Console.WriteLine($"Num clint {TcpServer.SessionCount} - Transfer rate:"+s.ToString("N1")); + } + } private void TcpClientAccepted(Guid guid) { - tokenStorage.TryAdd(guid, new TcpTokenStorage()); TimerService.RegisterTimer(guid, tokenLifetimeMs, () => HandleTcpClientTimeout(guid)); - } private void HandleTcpClientTimeout(Guid guid) @@ -160,7 +168,10 @@ private bool RouteTcp(Guid guid, byte[] bytes, int offset, int count) // we should look for room stuff here if (pipeMapTcp.TryGetValue(guid, out var to)) { - // Interlocked.Add(ref sent, count); +#if PRINT + Interlocked.Add(ref sent, count); +#endif + TcpServer.SendBytesToClientDirect(to, bytes, offset, count); return true; } @@ -176,6 +187,9 @@ private bool RouteUdp(IPEndPoint endpoint, byte[] bytes, int offset, int count) { if (pipeMapUdp.TryGetValue(endpoint, out var to)) { +#if PRINT + Interlocked.Add(ref sent, count); +#endif UdpServer.SendBytesToClient(to, bytes, offset, count); return true; } @@ -194,72 +208,86 @@ private void ManagePipeToken(Guid ephemeralId, byte[] bytes, int offset, int cou // state object etc lock (tokenMtex) { - tokenStorage.TryGetValue(ephemeralId, out var storage); - - int result = storage.StoreTokenFragment(bytes, offset, count); - - if (result == 0)//incomplete - return; - - if (result == -1)// nonsense + PipeToken token = null; + byte[] tokenBytes = null; + if (count == PipeData.TokenLength) { - RemoveTcpClient(ephemeralId); - return; + token = DeserializePipeToken(bytes, offset); + tokenBytes = ByteCopy.ToArray(bytes, offset, count); } - - if (result == 1)// tokenComplete + else { - PipeToken token = DeserializePipeToken(storage.Token, 0); - if (activeTcpPipeStates.TryGetValue(token.Token, out var pipeState)) + if (!tokenStorage.TryGetValue(ephemeralId, out var storage)) { - if (VerifyToken(storage.Token, token.Expiration)) - { - Console.WriteLine("TokenVerified"); - pipeState.RegisterClient(ephemeralId); + storage = new TcpTokenStorage(); + tokenStorage.TryAdd(ephemeralId,storage); - if (pipeState.IsComplete()) - { - activeTcpPipeStates.TryRemove(token.Token, out _); - TcpPipeCreated(pipeState.Clients[0], pipeState.Clients[1]); - } + } - TcpServer.SendBytesToClientDirect(ephemeralId, new byte[1] { 0x01 },0,1); - } - else + int result = storage.StoreTokenFragment(bytes, offset, count); + + if (result == 0)//incomplete + return; + + if (result == -1)// nonsense + { + RemoveTcpClient(ephemeralId); + return; + } + if (result == 1)// tokenComplete + { + token = DeserializePipeToken(storage.TokenBytes, 0); + tokenBytes = storage.TokenBytes; + } + } + + if (activeTcpPipeStates.TryGetValue(token.Token, out var pipeState)) + { + if (VerifyToken(tokenBytes, token.Expiration)) + { + Console.WriteLine("TokenVerified"); + pipeState.RegisterClient(ephemeralId); + + if (pipeState.IsComplete()) { - Console.WriteLine("Token Rejected"); - RemoveTcpClient(ephemeralId); + activeTcpPipeStates.TryRemove(token.Token, out _); + TcpPipeCreated(pipeState.Clients[0], pipeState.Clients[1]); } + + TcpServer.SendBytesToClientDirect(ephemeralId, new byte[1] { 0x01 }, 0, 1); + } + else + { + Console.WriteLine("Token Rejected"); + RemoveTcpClient(ephemeralId); } - else if (activeRoomStates.TryRemove(token.Token, out var roomState))//token is peerId + } + else if (activeRoomStates.TryRemove(token.Token, out var roomState))//token is peerId + { + if (VerifyToken(tokenBytes, token.Expiration)) { - if (VerifyToken(storage.Token, token.Expiration)) + if (roomState.Verify(token, ephemeralId)) { - if (roomState.Verify(token, ephemeralId)) + if (activeRooms.TryGetValue(roomState.RoomId, out Room room)) { - if (activeRooms.TryGetValue(roomState.RoomId, out Room room)) - { - room.Add(token.Token, roomState); - TcpServer.SendBytesToClientDirect(ephemeralId, new byte[1] { 0x01 }, 0, 1); - } + room.Add(token.Token, roomState); + TcpServer.SendBytesToClientDirect(ephemeralId, new byte[1] { 0x01 }, 0, 1); } } - else - { - RemoveTcpClient(ephemeralId); - } - } else { RemoveTcpClient(ephemeralId); } + } else { RemoveTcpClient(ephemeralId); } + + } } @@ -282,6 +310,7 @@ private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int { if (VerifyToken(tokenBytes, token.Expiration)) { + Console.WriteLine("Token verified"); pipeState.RegisterClient(clientEp); if (pipeState.IsComplete()) @@ -294,6 +323,7 @@ private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int } else { + Console.WriteLine("Token rejected"); // ddos here. UdpServer.RemoveClient(clientEp); } @@ -328,7 +358,9 @@ private void ManagePipeToken(IPEndPoint clientEp, byte[] bytes, int offset, int private bool VerifyToken(byte[] Token, DateTime expiration) { - var calculatedSignature = signer.Sign(Token, 0, 24); + + var calculatedSignature = GetSigner().Sign(Token, 0, 24); + if (SignatureMatch(calculatedSignature, Token)) { if (DateTime.UtcNow <= expiration) @@ -387,7 +419,7 @@ private void HandleUdpPipeDisconnect(IPEndPoint from) { pipeMapUdp.TryRemove(to, out _); } - else if(roomMapUdp.TryRemove(from, out Room room)) + else if (roomMapUdp.TryRemove(from, out Room room)) { room.HandleDisconnect(from); } @@ -409,17 +441,20 @@ private void HandleTcpPipeDisconnect(Guid from) } - private void RegisterTcpToken(PipeToken pipeData) - { - activeTcpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); - TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeTcpPipeStates.TryRemove(pipeData.Token, out _)); - } - private void RegisterUdpToken(PipeToken pipeData) + // for direct p2p + public byte[] GetPipeToken(bool tcp) { - activeUdpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); - TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeUdpPipeStates.TryRemove(pipeData.Token, out _)); + byte[] data; + PipeToken pipeData; + Createtoken(Guid.NewGuid(), out data, out pipeData); + if (tcp) + RegisterTcpToken(pipeData); + else + RegisterUdpToken(pipeData); + + return data; } private void Createtoken(Guid tokenId, out byte[] data, out PipeToken pipeData) { @@ -433,29 +468,26 @@ private void Createtoken(Guid tokenId, out byte[] data, out PipeToken pipeData) long Time = pipeData.Expiration.ToBinary(); PrimitiveEncoder.WriteFixedInt64(data, ref offset, Time); //8 - - byte[] signature = signer.Sign(data, 0, 24); + var signature = GetSigner().Sign(data, 0, 24); Buffer.BlockCopy(signature, 0, data, offset, 32);//32 } - - - // for direct p2p - public byte[] GetPipeToken(bool tcp) + private void RegisterTcpToken(PipeToken pipeData) { - byte[] data; - PipeToken pipeData; - Createtoken(Guid.NewGuid(), out data, out pipeData); + if (!activeTcpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData))) + Console.WriteLine("GuidDupe"); + TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeTcpPipeStates.TryRemove(pipeData.Token, out _)); + } - if (tcp) - RegisterTcpToken(pipeData); - else - RegisterUdpToken(pipeData); + private void RegisterUdpToken(PipeToken pipeData) + { + activeUdpPipeStates.TryAdd(pipeData.Token, new PipeState(pipeData)); + TimerService.RegisterTimer(pipeData.Token, tokenLifetimeMs, () => activeUdpPipeStates.TryRemove(pipeData.Token, out _)); - return data; } + //for broadcast public bool CreateRoom(Guid roomId, RoomProtocol protocol) { @@ -477,6 +509,7 @@ public bool CreateRoom(Guid roomId, RoomProtocol protocol) } } + // get token for spesific peer, who wants to join a room public byte[] GetRoomToken(Guid roomId, Guid peerId) {