From 0f3102542b786d6f6dd4989558f1da8dc4eda735 Mon Sep 17 00:00:00 2001 From: Trudy Date: Sat, 28 Jan 2023 20:58:59 -0800 Subject: [PATCH 1/7] Add datagram message handler --- VpnHood.Client/VpnHoodClient.cs | 3 +- VpnHood.Server/TcpHost.cs | 4 +- .../DatagramMessaging/CloseDatagramMessage.cs | 5 ++ .../DatagramMessaging/DatagramBaseMessage.cs | 5 ++ .../DatagramMessaging/DatagramMessageCode.cs | 6 ++ .../DatagramMessageHandler.cs | 59 +++++++++++++++++++ VpnHood.Tunneling/PacketUtil.cs | 1 - VpnHood.Tunneling/StreamUtil.cs | 4 +- VpnHood.Tunneling/TcpDatagramChannel.cs | 6 +- VpnHood.Tunneling/Tunnel.cs | 2 +- VpnHood.ZTest/Tests/TunnelTest.cs | 12 ++++ 11 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 VpnHood.Tunneling/DatagramMessaging/CloseDatagramMessage.cs create mode 100644 VpnHood.Tunneling/DatagramMessaging/DatagramBaseMessage.cs create mode 100644 VpnHood.Tunneling/DatagramMessaging/DatagramMessageCode.cs create mode 100644 VpnHood.Tunneling/DatagramMessaging/DatagramMessageHandler.cs diff --git a/VpnHood.Client/VpnHoodClient.cs b/VpnHood.Client/VpnHoodClient.cs index 865c4ec68..c4b5fd9d3 100644 --- a/VpnHood.Client/VpnHoodClient.cs +++ b/VpnHood.Client/VpnHoodClient.cs @@ -390,14 +390,13 @@ private void PacketCapture_OnPacketReceivedFromInbound(object sender, PacketRece } } - private bool IsIcmpControlMessage(IPPacket ipPacket) + private static bool IsIcmpControlMessage(IPPacket ipPacket) { // IPv4 if (ipPacket is { Version: IPVersion.IPv4, Protocol: ProtocolType.Icmp }) { var icmpPacket = ipPacket.Extract(); return icmpPacket.TypeCode != IcmpV4TypeCode.EchoRequest; // drop all other Icmp but echo - } // IPv6 diff --git a/VpnHood.Server/TcpHost.cs b/VpnHood.Server/TcpHost.cs index 46f6221e7..cbda87f2e 100644 --- a/VpnHood.Server/TcpHost.cs +++ b/VpnHood.Server/TcpHost.cs @@ -251,9 +251,7 @@ private async Task ProcessRequest(TcpClientStream tcpClientStream, CancellationT // check request version var version = buffer[0]; if (version != 1) - { throw new NotSupportedException("The request version is not supported!"); - } // read request code var requestCode = (RequestCode)buffer[1]; @@ -280,7 +278,7 @@ private async Task ProcessRequest(TcpClientStream tcpClientStream, CancellationT break; default: - throw new NotSupportedException("Unknown requestCode!"); + throw new NotSupportedException($"Unknown requestCode. requestCode: {requestCode}"); } } diff --git a/VpnHood.Tunneling/DatagramMessaging/CloseDatagramMessage.cs b/VpnHood.Tunneling/DatagramMessaging/CloseDatagramMessage.cs new file mode 100644 index 000000000..fea234c4b --- /dev/null +++ b/VpnHood.Tunneling/DatagramMessaging/CloseDatagramMessage.cs @@ -0,0 +1,5 @@ +namespace VpnHood.Tunneling.DatagramMessaging; + +public class CloseDatagramMessage : DatagramBaseMessage +{ +} \ No newline at end of file diff --git a/VpnHood.Tunneling/DatagramMessaging/DatagramBaseMessage.cs b/VpnHood.Tunneling/DatagramMessaging/DatagramBaseMessage.cs new file mode 100644 index 000000000..0fa80daa3 --- /dev/null +++ b/VpnHood.Tunneling/DatagramMessaging/DatagramBaseMessage.cs @@ -0,0 +1,5 @@ +namespace VpnHood.Tunneling.DatagramMessaging; + +public class DatagramBaseMessage +{ +} \ No newline at end of file diff --git a/VpnHood.Tunneling/DatagramMessaging/DatagramMessageCode.cs b/VpnHood.Tunneling/DatagramMessaging/DatagramMessageCode.cs new file mode 100644 index 000000000..9852d1b6e --- /dev/null +++ b/VpnHood.Tunneling/DatagramMessaging/DatagramMessageCode.cs @@ -0,0 +1,6 @@ +namespace VpnHood.Tunneling.DatagramMessaging; + +public enum DatagramMessageCode : byte +{ + CloseDatagramChannel +} \ No newline at end of file diff --git a/VpnHood.Tunneling/DatagramMessaging/DatagramMessageHandler.cs b/VpnHood.Tunneling/DatagramMessaging/DatagramMessageHandler.cs new file mode 100644 index 000000000..3c8b4a057 --- /dev/null +++ b/VpnHood.Tunneling/DatagramMessaging/DatagramMessageHandler.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using System.Net; +using PacketDotNet; + +namespace VpnHood.Tunneling.DatagramMessaging; + +public static class DatagramMessageHandler +{ + private static DatagramMessageCode GetMessageCode(DatagramBaseMessage requestMessage) + { + if (requestMessage is CloseDatagramMessage) return DatagramMessageCode.CloseDatagramChannel; + throw new ArgumentException("Could not detect version code for this datagram message."); + } + + public static IPPacket CreateMessage(DatagramBaseMessage requestMessage) + { + // building request + using var mem = new MemoryStream(); + mem.WriteByte(1); + mem.WriteByte((byte)GetMessageCode(requestMessage)); + StreamUtil.WriteJson(mem, requestMessage); + var ipPacket = PacketUtil.CreateUdpPacket(new IPEndPoint(0, 0), new IPEndPoint(0, 0), mem.ToArray(), false); + return ipPacket; + } + + public static bool IsDatagramMessage(IPPacket ipPacket) + { + return ipPacket.DestinationAddress.Equals(new IPAddress(0)) && ipPacket.Protocol == ProtocolType.Udp; + } + + public static DatagramBaseMessage ReadMessage(IPPacket ipPacket) + { + if (!IsDatagramMessage(ipPacket)) + throw new ArgumentException("packet is not an Datagram message.", nameof(ipPacket)); + + var udpPacket = PacketUtil.ExtractUdp(ipPacket); + + // read version and messageCode + var buffer = new byte[2]; + var stream = new MemoryStream(udpPacket.PayloadData); + var res = stream.Read(buffer, 0, buffer.Length); + if (res != buffer.Length) + throw new Exception($"Invalid datagram message length. Length: {buffer.Length}"); + + // check version + var version = buffer[0]; + if (version != 1) + throw new NotSupportedException($"The datagram message version is not supported. Version: {version}"); + + // check message code + var messageCode = (DatagramMessageCode)buffer[1]; + return messageCode switch + { + DatagramMessageCode.CloseDatagramChannel => StreamUtil.ReadJson(stream), + _ => throw new NotSupportedException($"Unknown Datagram Message messageCode. MessageCode: {messageCode}") + }; + } +} \ No newline at end of file diff --git a/VpnHood.Tunneling/PacketUtil.cs b/VpnHood.Tunneling/PacketUtil.cs index cd0df1ddb..d737b40a4 100644 --- a/VpnHood.Tunneling/PacketUtil.cs +++ b/VpnHood.Tunneling/PacketUtil.cs @@ -5,7 +5,6 @@ using System.Net.Sockets; using Microsoft.Extensions.Logging; using PacketDotNet; -using PacketDotNet.Ieee80211; using PacketDotNet.Utils; using VpnHood.Common.Logging; using ProtocolType = PacketDotNet.ProtocolType; diff --git a/VpnHood.Tunneling/StreamUtil.cs b/VpnHood.Tunneling/StreamUtil.cs index a54e1a27f..dc55bbed8 100644 --- a/VpnHood.Tunneling/StreamUtil.cs +++ b/VpnHood.Tunneling/StreamUtil.cs @@ -12,9 +12,7 @@ public static class StreamUtil public static byte[]? ReadWaitForFill(Stream stream, int count) { var buffer = new byte[count]; - if (!ReadWaitForFill(stream, buffer, 0, buffer.Length)) - return null; - return buffer; + return ReadWaitForFill(stream, buffer, 0, buffer.Length) ? buffer : null; } public static async Task ReadWaitForFillAsync(Stream stream, int count, diff --git a/VpnHood.Tunneling/TcpDatagramChannel.cs b/VpnHood.Tunneling/TcpDatagramChannel.cs index f021393b4..4029375a2 100644 --- a/VpnHood.Tunneling/TcpDatagramChannel.cs +++ b/VpnHood.Tunneling/TcpDatagramChannel.cs @@ -31,10 +31,10 @@ public TcpDatagramChannel(TcpClientStream tcpClientStream) public Task Start() { if (Connected) - throw new Exception("Start has already been called!"); + throw new Exception("TcpDatagram has been already started."); if (_disposed) - throw new ObjectDisposedException(nameof(TcpDatagramChannel)); + throw new ObjectDisposedException(VhLogger.FormatTypeName(this)); Connected = true; return ReadTask(); @@ -43,7 +43,7 @@ public Task Start() public async Task SendPacketAsync(IPPacket[] ipPackets) { if (_disposed) - throw new ObjectDisposedException(nameof(TcpDatagramChannel)); + throw new ObjectDisposedException(VhLogger.FormatTypeName(this)); var maxDataLen = Mtu; var dataLen = ipPackets.Sum(x => x.TotalPacketLength); diff --git a/VpnHood.Tunneling/Tunnel.cs b/VpnHood.Tunneling/Tunnel.cs index 382ef7ea3..35bc7c45d 100644 --- a/VpnHood.Tunneling/Tunnel.cs +++ b/VpnHood.Tunneling/Tunnel.cs @@ -353,7 +353,7 @@ private async Task SendPacketTask(IDatagramChannel channel) } catch (Exception ex) { - VhLogger.Instance.LogWarning($"Could not send {packets.Count} packets via a channel! Message: {ex.Message}"); + VhLogger.Instance.LogWarning(ex, "Could not send some packets via a channel. PacketCount: {PacketCount}", packets.Count); } // make sure to remove the channel diff --git a/VpnHood.ZTest/Tests/TunnelTest.cs b/VpnHood.ZTest/Tests/TunnelTest.cs index 1094c4522..f92657492 100644 --- a/VpnHood.ZTest/Tests/TunnelTest.cs +++ b/VpnHood.ZTest/Tests/TunnelTest.cs @@ -10,6 +10,7 @@ using PacketDotNet; using PacketDotNet.Utils; using VpnHood.Tunneling; +using VpnHood.Tunneling.DatagramMessaging; using ProtocolType = PacketDotNet.ProtocolType; namespace VpnHood.Test.Tests; @@ -186,4 +187,15 @@ public async Task UdpChannel_via_Tunnel() Assert.AreEqual(packets.Count, serverReceivedPackets.Length); Assert.AreEqual(packets.Count, clientReceivedPackets.Length); } + + [TestMethod] + public void DatagramMessages() + { + var ipPacket = DatagramMessageHandler.CreateMessage(new CloseDatagramMessage()); + Assert.IsTrue(DatagramMessageHandler.IsDatagramMessage(ipPacket)); + + var message = DatagramMessageHandler.ReadMessage(ipPacket); + Assert.IsTrue(message is CloseDatagramMessage); + } + } \ No newline at end of file From aa4cad55d460826a9c2782a72bfa2e5bea87acb9 Mon Sep 17 00:00:00 2001 From: Trudy Date: Tue, 31 Jan 2023 02:29:15 -0800 Subject: [PATCH 2/7] add life time to TcpDatagramChannel --- VpnHood.Client.App.UI/VpnHoodAppUI.cs | 2 +- VpnHood.Client.App/VpnHoodApp.cs | 2 +- VpnHood.Client/TcpProxyHost.cs | 8 +- VpnHood.Client/VpnHoodClient.cs | 5 +- VpnHood.Common/JobController/JobLock.cs | 8 +- VpnHood.Common/JobController/JobRunner.cs | 25 ++--- VpnHood.Common/Logging/VhLogger.cs | 13 ++- VpnHood.Common/Utils/Util.cs | 2 +- VpnHood.Server.App.Net/NLog.config | 2 +- VpnHood.Server/Session.cs | 6 +- VpnHood.Server/TcpHost.cs | 6 +- VpnHood.Tunneling/GeneralEventId.cs | 2 +- VpnHood.Tunneling/IChannel.cs | 1 + VpnHood.Tunneling/TcpDatagramChannel.cs | 117 +++++++++++++++++----- VpnHood.Tunneling/TcpProxyChannel.cs | 18 ++-- VpnHood.Tunneling/Tunnel.cs | 101 +++++++++++-------- VpnHood.Tunneling/UdpChannel.cs | 4 +- VpnHood.ZTest/TestEmbedIoAccessServer.cs | 2 +- VpnHood.ZTest/TestHelper.cs | 28 +++++- VpnHood.ZTest/Tests/ClientAppTest.cs | 2 +- VpnHood.ZTest/Tests/ClientServerTest.cs | 6 +- VpnHood.ZTest/Tests/ServerTest.cs | 2 +- VpnHood.ZTest/Tests/TcpDatagramTest.cs | 72 +++++++++++++ VpnHood.ZTest/Tests/TunnelTest.cs | 16 +-- 24 files changed, 316 insertions(+), 134 deletions(-) create mode 100644 VpnHood.ZTest/Tests/TcpDatagramTest.cs diff --git a/VpnHood.Client.App.UI/VpnHoodAppUI.cs b/VpnHood.Client.App.UI/VpnHoodAppUI.cs index e4dede377..61ccff4fb 100644 --- a/VpnHood.Client.App.UI/VpnHoodAppUI.cs +++ b/VpnHood.Client.App.UI/VpnHoodAppUI.cs @@ -59,7 +59,7 @@ public static VpnHoodAppUi Init(Stream zipStream, int defaultPort = 9090) private void Start() { - _url = $"http://{Util.GetFreeEndPoint(IPAddress.Loopback, DefaultPort)}"; + _url = $"http://{Util.GetFreeTcpEndPoint(IPAddress.Loopback, DefaultPort)}"; _server = CreateWebServer(Url, GetSpaPath()); try { diff --git a/VpnHood.Client.App/VpnHoodApp.cs b/VpnHood.Client.App/VpnHoodApp.cs index 8f88d225a..d54642703 100644 --- a/VpnHood.Client.App/VpnHoodApp.cs +++ b/VpnHood.Client.App/VpnHoodApp.cs @@ -300,7 +300,7 @@ public async Task Connect(Guid clientProfileId, bool diagnose = false, string? u if (eventId == GeneralEventId.Nat) return VhLogger.IsDiagnoseMode; if (eventId == GeneralEventId.Dns) return VhLogger.IsDiagnoseMode; if (eventId == GeneralEventId.Udp) return VhLogger.IsDiagnoseMode; - if (eventId == GeneralEventId.StreamChannel) return VhLogger.IsDiagnoseMode; + if (eventId == GeneralEventId.TcpProxyChannel) return VhLogger.IsDiagnoseMode; if (eventId == GeneralEventId.DatagramChannel) return true; return true; }); diff --git a/VpnHood.Client/TcpProxyHost.cs b/VpnHood.Client/TcpProxyHost.cs index 5cc26625d..88d08ea4a 100644 --- a/VpnHood.Client/TcpProxyHost.cs +++ b/VpnHood.Client/TcpProxyHost.cs @@ -224,7 +224,7 @@ private async Task ProcessClient(TcpClient orgTcpClient, CancellationToken cance // create a scope for the logger using var scope = VhLogger.Instance.BeginScope( $"LocalPort: {natItem.SourcePort}, RemoteEp: {VhLogger.Format(natItem.DestinationAddress)}:{natItem.DestinationPort}"); - VhLogger.Instance.LogTrace(GeneralEventId.StreamChannel, "New TcpProxy Request."); + VhLogger.Instance.LogTrace(GeneralEventId.TcpProxyChannel, "New TcpProxy Request."); // check invalid income var loopbackAddress = ipVersion == IPVersion.IPv4 ? LoopbackAddressIpV4 : LoopbackAddressIpV6; @@ -249,7 +249,7 @@ await Client.AddPassthruTcpStream( Util.GenerateSessionKey(), natItem.DestinationPort == 443 ? TunnelUtil.TlsHandshakeLength : -1); - tcpProxyClientStream = await Client.GetTlsConnectionToServer(GeneralEventId.StreamChannel, cancellationToken); + tcpProxyClientStream = await Client.GetTlsConnectionToServer(GeneralEventId.TcpProxyChannel, cancellationToken); tcpProxyClientStream.TcpClient.ReceiveBufferSize = orgTcpClient.ReceiveBufferSize; tcpProxyClientStream.TcpClient.SendBufferSize = orgTcpClient.SendBufferSize; tcpProxyClientStream.TcpClient.SendTimeout = orgTcpClient.SendTimeout; @@ -260,7 +260,7 @@ await Client.SendRequest(tcpProxyClientStream.Stream, RequestCode.TcpProxyChannel, request, cancellationToken); // create a TcpProxyChannel - VhLogger.Instance.LogTrace(GeneralEventId.StreamChannel, + VhLogger.Instance.LogTrace(GeneralEventId.TcpProxyChannel, $"Adding a channel to session {VhLogger.FormatId(request.SessionId)}..."); var orgTcpClientStream = new TcpClientStream(orgTcpClient, orgTcpClient.GetStream()); @@ -277,7 +277,7 @@ await Client.SendRequest(tcpProxyClientStream.Stream, { tcpProxyClientStream?.Dispose(); orgTcpClient.Dispose(); - VhLogger.Instance.LogError(GeneralEventId.StreamChannel, $"{ex.Message}"); + VhLogger.Instance.LogError(GeneralEventId.TcpProxyChannel, $"{ex.Message}"); } } } \ No newline at end of file diff --git a/VpnHood.Client/VpnHoodClient.cs b/VpnHood.Client/VpnHoodClient.cs index c4b5fd9d3..5bc03e8d8 100644 --- a/VpnHood.Client/VpnHoodClient.cs +++ b/VpnHood.Client/VpnHoodClient.cs @@ -749,7 +749,8 @@ await SendRequest(tcpClientStream.Stream, RequestCode.TcpDa request, cancellationToken); // add the new channel - var channel = new TcpDatagramChannel(tcpClientStream); + var lifespan = TimeSpan.FromSeconds(new Random().Next(120, 360)); + var channel = new TcpDatagramChannel(tcpClientStream, lifespan); try { Tunnel.AddChannel(channel); } catch { channel.Dispose(); throw; } @@ -766,7 +767,7 @@ internal async Task SendRequest(Stream stream, RequestCode requestCode, ob { RequestCode.Hello => GeneralEventId.Session, RequestCode.TcpDatagramChannel => GeneralEventId.DatagramChannel, - RequestCode.TcpProxyChannel => GeneralEventId.StreamChannel, + RequestCode.TcpProxyChannel => GeneralEventId.TcpProxyChannel, _ => GeneralEventId.Tcp }; VhLogger.Instance.LogTrace(eventId, $"Sending a request... RequestCode: {requestCode}."); diff --git a/VpnHood.Common/JobController/JobLock.cs b/VpnHood.Common/JobController/JobLock.cs index 20ad74fde..4bfba8be3 100644 --- a/VpnHood.Common/JobController/JobLock.cs +++ b/VpnHood.Common/JobController/JobLock.cs @@ -5,17 +5,17 @@ namespace VpnHood.Common.JobController; public class JobLock : IDisposable { private readonly JobSection _jobSection; - public bool ShouldEnter { get; } + public bool IsEntered { get; } - internal JobLock(JobSection jobSection, bool shouldEnter) + internal JobLock(JobSection jobSection, bool isEntered) { _jobSection = jobSection; - ShouldEnter = shouldEnter; + IsEntered = isEntered; } public void Dispose() { - if (ShouldEnter) + if (IsEntered) _jobSection.Leave(); } } \ No newline at end of file diff --git a/VpnHood.Common/JobController/JobRunner.cs b/VpnHood.Common/JobController/JobRunner.cs index 80f51b3cf..c31d05de8 100644 --- a/VpnHood.Common/JobController/JobRunner.cs +++ b/VpnHood.Common/JobController/JobRunner.cs @@ -3,18 +3,20 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using VpnHood.Common.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace VpnHood.Common.JobController; public class JobRunner { + public ILogger Logger { get; set; } = NullLogger.Instance; private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly LinkedList> _jobRefs = new(); - private readonly List> _deadWatchDogRefs = new(); + private readonly List> _deadJobs = new(); private Timer? _timer; - private TimeSpan _interval = TimeSpan.FromSeconds(5); + public bool IsStarted => _timer != null; + public TimeSpan Interval { get => _interval; @@ -30,9 +32,10 @@ public TimeSpan Interval public static JobRunner Default => DefaultLazy.Value; private static readonly Lazy DefaultLazy = new(() => new JobRunner()); - public JobRunner() + public JobRunner(bool start = true) { - Start(); + if (start) + Start(); } public void TimerProc(object? _) @@ -48,7 +51,7 @@ public void TimerProc(object? _) // the WatchDog object is dead if (!jobRef.TryGetTarget(out var job)) { - _deadWatchDogRefs.Add(jobRef); + _deadJobs.Add(jobRef); continue; } @@ -67,19 +70,19 @@ public void TimerProc(object? _) } catch (ObjectDisposedException) { - _deadWatchDogRefs.Add(jobRef); + _deadJobs.Add(jobRef); } catch (Exception ex) { - VhLogger.Instance.LogError(ex, "Could not run a WatchDog."); + Logger.LogError(ex, "Could not run a job."); } } // clear dead watch dogs - foreach (var item in _deadWatchDogRefs) + foreach (var item in _deadJobs) _jobRefs.Remove(item); - _deadWatchDogRefs.Clear(); + _deadJobs.Clear(); } finally { @@ -105,8 +108,6 @@ private async Task AddInternal(IJob job) } } - public bool IsStarted => _timer != null; - public void Start() { _timer?.Dispose(); diff --git a/VpnHood.Common/Logging/VhLogger.cs b/VpnHood.Common/Logging/VhLogger.cs index ced5ae516..842b9417a 100644 --- a/VpnHood.Common/Logging/VhLogger.cs +++ b/VpnHood.Common/Logging/VhLogger.cs @@ -11,7 +11,18 @@ namespace VpnHood.Common.Logging; public static class VhLogger { private static bool _isDiagnoseMode; - public static ILogger Instance { get; set; } = NullLogger.Instance; + private static ILogger _instance = NullLogger.Instance; + + public static ILogger Instance + { + get => _instance; + set + { + _instance = value; + JobController.JobRunner.Default.Logger = value; + } + } + public static bool IsAnonymousMode { get; set; } = true; public static bool IsDiagnoseMode { diff --git a/VpnHood.Common/Utils/Util.cs b/VpnHood.Common/Utils/Util.cs index 3b2ffe4c0..73fa44c45 100644 --- a/VpnHood.Common/Utils/Util.cs +++ b/VpnHood.Common/Utils/Util.cs @@ -27,7 +27,7 @@ public static bool IsSocketClosedException(Exception ex) return ex is ObjectDisposedException or IOException or SocketException; } - public static IPEndPoint GetFreeEndPoint(IPAddress ipAddress, int defaultPort = 0) + public static IPEndPoint GetFreeTcpEndPoint(IPAddress ipAddress, int defaultPort = 0) { try { diff --git a/VpnHood.Server.App.Net/NLog.config b/VpnHood.Server.App.Net/NLog.config index f0bb194a5..e5709debd 100644 --- a/VpnHood.Server.App.Net/NLog.config +++ b/VpnHood.Server.App.Net/NLog.config @@ -57,7 +57,7 @@ - + diff --git a/VpnHood.Server/Session.cs b/VpnHood.Server/Session.cs index eb5c7636c..ce676ebf2 100644 --- a/VpnHood.Server/Session.cs +++ b/VpnHood.Server/Session.cs @@ -53,7 +53,7 @@ public class Session : IAsyncDisposable, IJob public JobSection JobSection { get; } public HelloRequest? HelloRequest { get; } public int TcpConnectWaitCount => _tcpConnectWaitCount; - public int TcpChannelCount => Tunnel.StreamChannelCount + (UseUdpChannel ? 0 : Tunnel.DatagramChannels.Length); + public int TcpChannelCount => Tunnel.TcpProxyChannelCount + (UseUdpChannel ? 0 : Tunnel.DatagramChannels.Length); public int UdpConnectionCount => _proxyManager.UdpClientCount + (UseUdpChannel ? 1 : 0); public DateTime LastActivityTime => Tunnel.LastActivityTime; @@ -261,7 +261,7 @@ public async Task ProcessTcpChannelRequest(TcpClientStream tcpClientStream, TcpP try { // connect to requested site - VhLogger.Instance.LogTrace(GeneralEventId.StreamChannel, + VhLogger.Instance.LogTrace(GeneralEventId.TcpProxyChannel, $"Connecting to the requested endpoint. RequestedEP: {VhLogger.Format(request.DestinationEndPoint)}"); // Apply limitation @@ -294,7 +294,7 @@ await Util.RunTask( request.CipherKey, null, request.CipherLength); // add the connection - VhLogger.Instance.LogTrace(GeneralEventId.StreamChannel, + VhLogger.Instance.LogTrace(GeneralEventId.TcpProxyChannel, $"Adding a {nameof(TcpProxyChannel)}. SessionId: {VhLogger.FormatSessionId(SessionId)}, CipherLength: {request.CipherLength}"); tcpClientStream2 = new TcpClientStream(tcpClient2, tcpClient2.GetStream()); diff --git a/VpnHood.Server/TcpHost.cs b/VpnHood.Server/TcpHost.cs index cbda87f2e..954d26755 100644 --- a/VpnHood.Server/TcpHost.cs +++ b/VpnHood.Server/TcpHost.cs @@ -367,7 +367,7 @@ private async Task ProcessUdpChannel(TcpClientStream tcpClientStream, Cancellati private async Task ProcessBye(TcpClientStream tcpClientStream, CancellationToken cancellationToken) { - VhLogger.Instance.LogTrace(GeneralEventId.StreamChannel, $"Reading the {RequestCode.Bye} request ..."); + VhLogger.Instance.LogTrace(GeneralEventId.TcpProxyChannel, $"Reading the {RequestCode.Bye} request ..."); var request = await StreamUtil.ReadJsonAsync(tcpClientStream.Stream, cancellationToken); // finding session @@ -381,7 +381,7 @@ private async Task ProcessBye(TcpClientStream tcpClientStream, CancellationToken private async Task ProcessTcpDatagramChannel(TcpClientStream tcpClientStream, CancellationToken cancellationToken) { - VhLogger.Instance.LogTrace(GeneralEventId.StreamChannel, $"Reading the {nameof(TcpDatagramChannelRequest)} ..."); + VhLogger.Instance.LogTrace(GeneralEventId.TcpProxyChannel, $"Reading the {nameof(TcpDatagramChannelRequest)} ..."); var request = await StreamUtil.ReadJsonAsync(tcpClientStream.Stream, cancellationToken); // finding session @@ -403,7 +403,7 @@ private async Task ProcessTcpDatagramChannel(TcpClientStream tcpClientStream, Ca private async Task ProcessTcpProxyChannel(TcpClientStream tcpClientStream, CancellationToken cancellationToken) { - VhLogger.Instance.LogInformation(GeneralEventId.StreamChannel, $"Reading the {nameof(TcpProxyChannelRequest)} ..."); + VhLogger.Instance.LogInformation(GeneralEventId.TcpProxyChannel, $"Reading the {nameof(TcpProxyChannelRequest)} ..."); var request = await StreamUtil.ReadJsonAsync(tcpClientStream.Stream, cancellationToken); // find session diff --git a/VpnHood.Tunneling/GeneralEventId.cs b/VpnHood.Tunneling/GeneralEventId.cs index c739a6b5a..42365fcc0 100644 --- a/VpnHood.Tunneling/GeneralEventId.cs +++ b/VpnHood.Tunneling/GeneralEventId.cs @@ -13,7 +13,7 @@ public static class GeneralEventId public static EventId Tls = new((int)EventCode.Tls, nameof(Tls)); public static EventId Udp = new((int)EventCode.Udp, nameof(Udp)); public static EventId Track = new((int)EventCode.Track, nameof(Track)); - public static EventId StreamChannel = new((int)EventCode.StreamChannel, nameof(StreamChannel)); + public static EventId TcpProxyChannel = new((int)EventCode.StreamChannel, nameof(TcpProxyChannel)); public static EventId DatagramChannel = new((int)EventCode.DatagramChannel, EventCode.DatagramChannel.ToString()); public static EventId AccessServer = new((int)EventCode.AccessServer, nameof(AccessServer)); public static EventId NetProtect = new((int)EventCode.NetProtect, nameof(NetProtect)); diff --git a/VpnHood.Tunneling/IChannel.cs b/VpnHood.Tunneling/IChannel.cs index b9beb986c..e930750d2 100644 --- a/VpnHood.Tunneling/IChannel.cs +++ b/VpnHood.Tunneling/IChannel.cs @@ -5,6 +5,7 @@ namespace VpnHood.Tunneling; public interface IChannel : IDisposable { + bool IsClosePending { get; } bool Connected { get; } DateTime LastActivityTime { get; } long SentByteCount { get; } diff --git a/VpnHood.Tunneling/TcpDatagramChannel.cs b/VpnHood.Tunneling/TcpDatagramChannel.cs index 4029375a2..f462561c3 100644 --- a/VpnHood.Tunneling/TcpDatagramChannel.cs +++ b/VpnHood.Tunneling/TcpDatagramChannel.cs @@ -1,33 +1,50 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using PacketDotNet; +using VpnHood.Common.JobController; using VpnHood.Common.Logging; using VpnHood.Common.Utils; +using VpnHood.Tunneling.DatagramMessaging; namespace VpnHood.Tunneling; -public class TcpDatagramChannel : IDatagramChannel +public class TcpDatagramChannel : IDatagramChannel, IJob { private readonly byte[] _buffer = new byte[0xFFFF]; private const int Mtu = 0xFFFF; private readonly TcpClientStream _tcpClientStream; private bool _disposed; - - public TcpDatagramChannel(TcpClientStream tcpClientStream) - { - _tcpClientStream = tcpClientStream ?? throw new ArgumentNullException(nameof(tcpClientStream)); - tcpClientStream.TcpClient.NoDelay = true; - } - + private readonly DateTime _lifeTime = DateTime.MaxValue; + private readonly SemaphoreSlim _sendSemaphore = new(1, 1); + public event EventHandler? OnFinished; public event EventHandler? OnPacketReceived; + public JobSection? JobSection => null; + public bool IsClosePending { get; private set; } public bool Connected { get; private set; } public long SentByteCount { get; private set; } public long ReceivedByteCount { get; private set; } public DateTime LastActivityTime { get; private set; } = FastDateTime.Now; + public TcpDatagramChannel(TcpClientStream tcpClientStream) + : this(tcpClientStream, Timeout.InfiniteTimeSpan) + { + } + + public TcpDatagramChannel(TcpClientStream tcpClientStream, TimeSpan lifespan) + { + _tcpClientStream = tcpClientStream ?? throw new ArgumentNullException(nameof(tcpClientStream)); + tcpClientStream.TcpClient.NoDelay = true; + if (lifespan != Timeout.InfiniteTimeSpan) + { + _lifeTime = FastDateTime.Now + lifespan; + JobRunner.Default.Add(this); + } + } + public Task Start() { if (Connected) @@ -45,25 +62,33 @@ public async Task SendPacketAsync(IPPacket[] ipPackets) if (_disposed) throw new ObjectDisposedException(VhLogger.FormatTypeName(this)); - var maxDataLen = Mtu; - var dataLen = ipPackets.Sum(x => x.TotalPacketLength); - if (dataLen > maxDataLen) - throw new InvalidOperationException( - $"Total packets length is too big for {VhLogger.FormatTypeName(this)}. MaxSize: {maxDataLen}, Packets Size: {dataLen} !"); + try + { + await _sendSemaphore.WaitAsync(); + + var dataLen = ipPackets.Sum(x => x.TotalPacketLength); + if (dataLen > Mtu) + throw new InvalidOperationException( + $"Total packets length is too big for {VhLogger.FormatTypeName(this)}. MaxSize: {Mtu}, Packets Size: {dataLen}"); + + // copy packets to buffer + var buffer = _buffer; + var bufferIndex = 0; - // copy packets to buffer - var buffer = _buffer; - var bufferIndex = 0; + foreach (var ipPacket in ipPackets) + { + Buffer.BlockCopy(ipPacket.Bytes, 0, buffer, bufferIndex, ipPacket.TotalPacketLength); + bufferIndex += ipPacket.TotalPacketLength; + } - foreach (var ipPacket in ipPackets) + await _tcpClientStream.Stream.WriteAsync(buffer, 0, bufferIndex); + LastActivityTime = FastDateTime.Now; + SentByteCount += bufferIndex; + } + finally { - Buffer.BlockCopy(ipPacket.Bytes, 0, buffer, bufferIndex, ipPacket.TotalPacketLength); - bufferIndex += ipPacket.TotalPacketLength; + _sendSemaphore.Release(); } - - await _tcpClientStream.Stream.WriteAsync(buffer, 0, bufferIndex); - LastActivityTime = FastDateTime.Now; - SentByteCount += bufferIndex; } private async Task ReadTask() @@ -83,11 +108,17 @@ private async Task ReadTask() LastActivityTime = FastDateTime.Now; ReceivedByteCount += ipPackets.Sum(x => x.TotalPacketLength); FireReceivedPackets(ipPackets); + + // check datagram message + foreach (var ipPacket in ipPackets) + if (DatagramMessageHandler.IsDatagramMessage(ipPacket)) + ProcessMessage(DatagramMessageHandler.ReadMessage(ipPacket)); } } - catch + catch (Exception ex) { - // ignored + if (VhLogger.IsDiagnoseMode) + VhLogger.Instance.LogError(GeneralEventId.Udp, ex, "Error in reading UDP."); } finally { @@ -95,6 +126,34 @@ private async Task ReadTask() } } + private void ProcessMessage(DatagramBaseMessage message) + { + if (message is not CloseDatagramMessage) return; + + VhLogger.Instance.Log(LogLevel.Information, GeneralEventId.DatagramChannel, + "Receiving the close message from the peer. Lifetime: {Lifetime}, CurrentClosePending: {IsClosePending}", _lifeTime, IsClosePending); + + // dispose if this channel is already sent its request and get the answer from peer + if (IsClosePending) + Dispose(); + else + _ = SendCloseMessageAsync(); + } + + private Task SendCloseMessageAsync() + { + // already send + if (IsClosePending) + return Task.CompletedTask; + + VhLogger.Instance.Log(LogLevel.Information, GeneralEventId.DatagramChannel, + "Sending the close message to the peer... Lifetime: {Lifetime}", _lifeTime); + + var ipPacket = DatagramMessageHandler.CreateMessage(new CloseDatagramMessage()); + IsClosePending = true; + return SendPacketAsync(new[] { ipPacket }); + } + private void FireReceivedPackets(IPPacket[] ipPackets) { if (_disposed) @@ -111,6 +170,13 @@ private void FireReceivedPackets(IPPacket[] ipPackets) } } + public Task RunJob() + { + if (!IsClosePending && FastDateTime.Now > _lifeTime) + _ = SendCloseMessageAsync(); + + return Task.CompletedTask; + } public void Dispose() { if (_disposed) return; @@ -120,5 +186,4 @@ public void Dispose() Connected = false; OnFinished?.Invoke(this, new ChannelEventArgs(this)); } - } \ No newline at end of file diff --git a/VpnHood.Tunneling/TcpProxyChannel.cs b/VpnHood.Tunneling/TcpProxyChannel.cs index 82962e405..4d1c3d32b 100644 --- a/VpnHood.Tunneling/TcpProxyChannel.cs +++ b/VpnHood.Tunneling/TcpProxyChannel.cs @@ -21,6 +21,14 @@ public class TcpProxyChannel : IChannel, IJob private const int BufferSizeMin = 0x1000; private bool _disposed; + public JobSection JobSection { get; } + public event EventHandler? OnFinished; + public bool IsClosePending => false; + public bool Connected { get; private set; } + public long SentByteCount { get; private set; } + public long ReceivedByteCount { get; private set; } + public DateTime LastActivityTime { get; private set; } = FastDateTime.Now; + public TcpProxyChannel(TcpClientStream orgTcpClientStream, TcpClientStream tunnelTcpClientStream, TimeSpan tcpTimeout, int? orgStreamReadBufferSize = BufferSizeMin, int? tunnelStreamReadBufferSize = BufferSizeMin) { @@ -49,14 +57,6 @@ public TcpProxyChannel(TcpClientStream orgTcpClientStream, TcpClientStream tunne JobRunner.Default.Add(this); } - public JobSection JobSection { get; } - - public event EventHandler? OnFinished; - public bool Connected { get; private set; } - public long SentByteCount { get; private set; } - public long ReceivedByteCount { get; private set; } - public DateTime LastActivityTime { get; private set; } = FastDateTime.Now; - public async Task Start() { Connected = true; @@ -100,7 +100,7 @@ private void CheckTcpStates() IsConnectionValid(_tunnelTcpClientStream.TcpClient.Client)) return; - VhLogger.Instance.LogInformation(GeneralEventId.StreamChannel, + VhLogger.Instance.LogInformation(GeneralEventId.TcpProxyChannel, $"Disposing a {VhLogger.FormatTypeName(this)} due to its error state."); Dispose(); diff --git a/VpnHood.Tunneling/Tunnel.cs b/VpnHood.Tunneling/Tunnel.cs index 35bc7c45d..4b2c0ae9c 100644 --- a/VpnHood.Tunneling/Tunnel.cs +++ b/VpnHood.Tunneling/Tunnel.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using PacketDotNet; +using VpnHood.Common.Collections; using VpnHood.Common.Logging; using VpnHood.Common.Utils; @@ -19,7 +20,7 @@ public class Tunnel : IDisposable private readonly Queue _packetQueue = new(); private readonly SemaphoreSlim _packetSentEvent = new(0); private readonly SemaphoreSlim _packetSenderSemaphore = new(0); - private readonly HashSet _streamChannels = new(); + private readonly HashSet _tcpProxyChannels = new(); private readonly Timer _speedMonitorTimer; private bool _disposed; private long _lastReceivedByteCount; @@ -30,10 +31,15 @@ public class Tunnel : IDisposable private readonly TimeSpan _datagramPacketTimeout = TimeSpan.FromSeconds(100); private DateTime _lastSpeedUpdateTime = FastDateTime.Now; private readonly TimeSpan _speedTestThreshold = TimeSpan.FromSeconds(2); + private readonly TimeoutDictionary> _closePendingChannels = new(TimeSpan.FromSeconds(30)); + public event EventHandler? OnPacketReceived; + public event EventHandler? OnChannelAdded; + public event EventHandler? OnChannelRemoved; public long SendSpeed { get; private set; } public long ReceiveSpeed { get; private set; } public DateTime LastActivityTime { get; private set; } = FastDateTime.Now; + public IDatagramChannel[] DatagramChannels { get; private set; } = Array.Empty(); public Tunnel(TunnelOptions? options = null) { @@ -41,9 +47,14 @@ public Tunnel(TunnelOptions? options = null) _maxDatagramChannelCount = options.MaxDatagramChannelCount; _speedMonitorTimer = new Timer(_ => UpdateSpeed(), null, TimeSpan.Zero, _speedTestThreshold); } - - public int StreamChannelCount => _streamChannels.Count; - public IDatagramChannel[] DatagramChannels { get; private set; } = Array.Empty(); + public int TcpProxyChannelCount + { + get + { + lock (_closePendingChannels) + return _tcpProxyChannels.Count; + } + } public long ReceivedByteCount { @@ -51,7 +62,7 @@ public long ReceivedByteCount { lock (_channelListLock) { - return _receivedByteCount + _streamChannels.Sum(x => x.ReceivedByteCount) + + return _receivedByteCount + _tcpProxyChannels.Sum(x => x.ReceivedByteCount) + DatagramChannels.Sum(x => x.ReceivedByteCount); } } @@ -63,7 +74,7 @@ public long SentByteCount { lock (_channelListLock) { - return _sentByteCount + _streamChannels.Sum(x => x.SentByteCount) + + return _sentByteCount + _tcpProxyChannels.Sum(x => x.SentByteCount) + DatagramChannels.Sum(x => x.SentByteCount); } } @@ -80,15 +91,11 @@ public int MaxDatagramChannelCount } } - public event EventHandler? OnPacketReceived; - public event EventHandler? OnChannelAdded; - public event EventHandler? OnChannelRemoved; - private void UpdateSpeed() { if (_disposed) return; - + if (FastDateTime.Now - _lastSpeedUpdateTime < _speedTestThreshold) return; @@ -99,7 +106,7 @@ private void UpdateSpeed() SendSpeed = (int)((sentByteCount - _lastSentByteCount) / duration); ReceiveSpeed = (int)((receivedByteCount - _lastReceivedByteCount) / duration); - + _lastSpeedUpdateTime = FastDateTime.Now; _lastSentByteCount = sentByteCount; _lastReceivedByteCount = receivedByteCount; @@ -113,7 +120,7 @@ private bool IsChannelExists(IChannel channel) { return channel is IDatagramChannel ? DatagramChannels.Contains(channel) - : _streamChannels.Contains(channel); + : _tcpProxyChannels.Contains(channel); } } @@ -162,12 +169,13 @@ public void AddChannel(TcpProxyChannel channel) // add channel lock (_channelListLock) { - if (_streamChannels.Contains(channel)) + if (_tcpProxyChannels.Contains(channel)) throw new Exception($"{nameof(channel)} already exists in the collection."); - _streamChannels.Add(channel); + _tcpProxyChannels.Add(channel); } - VhLogger.Instance.LogInformation(GeneralEventId.StreamChannel, - $"A {VhLogger.FormatTypeName(channel)} has been added. ChannelCount: {_streamChannels.Count}"); + + VhLogger.Instance.LogInformation(GeneralEventId.TcpProxyChannel, + "A TcpProxyChannel has been added. ChannelCount: {ChannelCount}", TcpProxyChannelCount); // register finish channel.OnFinished += Channel_OnFinished; @@ -181,34 +189,39 @@ public void AddChannel(TcpProxyChannel channel) public void RemoveChannel(IChannel channel) { + if (!IsChannelExists(channel)) + return; // channel already removed or does not exist + lock (_channelListLock) { - if (!IsChannelExists(channel)) - return; // channel already removed or does not exist - - if (channel is IDatagramChannel datagramChannel) + if (channel is IDatagramChannel) { - datagramChannel.OnPacketReceived -= OnPacketReceived; DatagramChannels = DatagramChannels.Where(x => x != channel).ToArray(); VhLogger.Instance.LogInformation(GeneralEventId.DatagramChannel, - $"A {VhLogger.FormatTypeName(channel)} has been removed. ChannelCount: {DatagramChannels.Length}"); + "A DatagramChannel has been removed. Channel: {Channel}, ChannelCount: {ChannelCount}, Connected: {Connected}, ClosePending: {ClosePending}", + VhLogger.FormatTypeName(channel), DatagramChannels.Length, channel.Connected, channel.IsClosePending); } - else + else if (channel is TcpProxyChannel tcpProxyChannel) { - _streamChannels.Remove(channel); - VhLogger.Instance.LogInformation(GeneralEventId.StreamChannel, - $"A {VhLogger.FormatTypeName(channel)} has been removed. ChannelCount: {_streamChannels.Count}"); + _tcpProxyChannels.Remove(tcpProxyChannel); + VhLogger.Instance.LogInformation(GeneralEventId.TcpProxyChannel, + "A TcpProxyChannel has been removed. Channel: {Channel}, ChannelCount: {ChannelCount}, Connected: {Connected}, ClosePending: {ClosePending}", + VhLogger.FormatTypeName(channel), _tcpProxyChannels.Count, channel.Connected, channel.IsClosePending); } - - // add stats of dead channel - _sentByteCount += channel.SentByteCount; - _receivedByteCount += channel.ReceivedByteCount; - channel.OnFinished -= Channel_OnFinished; + else + throw new ArgumentOutOfRangeException(nameof(channel), "Unknown Channel."); } - // dispose before invoking the event - // channel may be disposed by itself so let call the invoke always with a disposed channel - channel.Dispose(); + // dispose or close-pending + if (channel.Connected && channel.IsClosePending) + _closePendingChannels.TryAdd(channel, new TimeoutItem(channel, true)); + else + channel.Dispose(); + + // clean up tunnel + _sentByteCount += channel.SentByteCount; + _receivedByteCount += channel.ReceivedByteCount; + channel.OnFinished -= Channel_OnFinished; // notify channel has been removed OnChannelRemoved?.Invoke(this, new ChannelEventArgs(channel)); @@ -288,8 +301,11 @@ private async Task SendPacketTask(IDatagramChannel channel) // ** Warning: This is one of the most busy loop in the app. Performance is critical! try { - while (channel.Connected && !_disposed) + while (channel.Connected && !channel.IsClosePending && !_disposed) { + if (_disposed) + return; + //only one thread can dequeue packets to let send buffer with sequential packets // dequeue available packets and add them to list in favor of buffer size lock (_packetQueue) @@ -298,7 +314,7 @@ private async Task SendPacketTask(IDatagramChannel channel) packets.Clear(); while (_packetQueue.TryPeek(out var ipPacket)) { - if (ipPacket == null) throw new Exception("Null packet should not be in the queue!"); + if (ipPacket == null) throw new Exception("Null packet should not be in the queue."); var packetSize = ipPacket.TotalPacketLength; // drop packet if it is larger than _mtuWithFragment @@ -366,11 +382,9 @@ private async Task SendPacketTask(IDatagramChannel channel) VhLogger.Instance.LogError(ex, "Could not remove a datagram channel."); } - // lets the other do the rest of the job (if any) - // should not throw error if object has been disposed - try { _packetSenderSemaphore.Release(); } catch (ObjectDisposedException) { } - - try { _packetSentEvent.Release(); } catch (ObjectDisposedException) { } + // lets the others do the rest of the job (if any) + _packetSenderSemaphore.Release(); + _packetSentEvent.Release(); } public void Dispose() @@ -381,7 +395,7 @@ public void Dispose() // make sure to call RemoveChannel to perform proper clean up such as setting _sentByteCount and _receivedByteCount lock (_channelListLock) { - foreach (var channel in _streamChannels.ToArray()) + foreach (var channel in _tcpProxyChannels.ToArray()) RemoveChannel(channel); foreach (var channel in DatagramChannels.ToArray()) @@ -398,5 +412,6 @@ public void Dispose() // release worker threads _packetSenderSemaphore.Release(MaxDatagramChannelCount * 10); //make sure to release all semaphores _packetSentEvent.Release(); + _closePendingChannels.Dispose(); } } \ No newline at end of file diff --git a/VpnHood.Tunneling/UdpChannel.cs b/VpnHood.Tunneling/UdpChannel.cs index c5149f64f..b31eedf97 100644 --- a/VpnHood.Tunneling/UdpChannel.cs +++ b/VpnHood.Tunneling/UdpChannel.cs @@ -21,11 +21,13 @@ public class UdpChannel : IDatagramChannel private readonly bool _isClient; private readonly object _lockCleanup = new(); private readonly int _mtuWithFragmentation = TunnelUtil.MtuWithFragmentation; - private readonly uint _sessionId; private readonly UdpClient _udpClient; private bool _disposed; private IPEndPoint? _lastRemoteEp; + + public bool IsClosePending => false; + public UdpChannel(bool isClient, UdpClient udpClient, uint sessionId, byte[] key) { VhLogger.Instance.LogInformation(GeneralEventId.Udp, $"Creating a {nameof(UdpChannel)}. SessionId: {VhLogger.FormatId(_sessionId)} ..."); diff --git a/VpnHood.ZTest/TestEmbedIoAccessServer.cs b/VpnHood.ZTest/TestEmbedIoAccessServer.cs index 6bcdfafd1..804f1c7b1 100644 --- a/VpnHood.ZTest/TestEmbedIoAccessServer.cs +++ b/VpnHood.ZTest/TestEmbedIoAccessServer.cs @@ -29,7 +29,7 @@ public TestEmbedIoAccessServer(IAccessServer fileFileAccessServer, bool autoStar try { Logger.UnregisterLogger(); } catch { /* ignored */} FileAccessServer = fileFileAccessServer; - BaseUri = new Uri($"http://{Util.GetFreeEndPoint(IPAddress.Loopback)}"); + BaseUri = new Uri($"http://{Util.GetFreeTcpEndPoint(IPAddress.Loopback)}"); _webServer = CreateServer(BaseUri); if (autoStart) _webServer.Start(); diff --git a/VpnHood.ZTest/TestHelper.cs b/VpnHood.ZTest/TestHelper.cs index 8fbb01f6a..ce054aee9 100644 --- a/VpnHood.ZTest/TestHelper.cs +++ b/VpnHood.ZTest/TestHelper.cs @@ -10,8 +10,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json.Linq; -using PacketDotNet; using VpnHood.Client; using VpnHood.Client.App; using VpnHood.Client.Device; @@ -213,7 +211,7 @@ public static FileAccessServerOptions CreateFileAccessServerOptions() { var options = new FileAccessServerOptions { - TcpEndPoints = new[] { Util.GetFreeEndPoint(IPAddress.Loopback) }, + TcpEndPoints = new[] { Util.GetFreeTcpEndPoint(IPAddress.Loopback) }, TrackingOptions = new TrackingOptions { TrackClientIp = true, @@ -439,6 +437,30 @@ public static async Task AssertEqualsWait(T obj, TValue? expectedValu Assert.AreEqual(expectedValue, valueFactory(obj)); } + public static async Task WaitForValue(object? expectedValue, Func valueFactory, int timeout = 5000) + { + const int waitTime = 100; + for (var elapsed = 0; elapsed < timeout; elapsed += waitTime) + { + if (Equals(valueFactory(), expectedValue)) + return true; + await Task.Delay(waitTime); + } + + return false; + } + + public static async Task AssertEqualsWait(TValue? expectedValue, Func valueFactory, string? message = null, int timeout = 5000) + { + await WaitForValue(expectedValue, valueFactory, timeout); + + if (message != null) + Assert.AreEqual(expectedValue, valueFactory(), message); + else + Assert.AreEqual(expectedValue, valueFactory()); + } + + private static bool _isInit; internal static void Init() { diff --git a/VpnHood.ZTest/Tests/ClientAppTest.cs b/VpnHood.ZTest/Tests/ClientAppTest.cs index 8f9713b66..fb3f98ec2 100644 --- a/VpnHood.ZTest/Tests/ClientAppTest.cs +++ b/VpnHood.ZTest/Tests/ClientAppTest.cs @@ -474,7 +474,7 @@ public async Task Get_token_from_tokenLink() var token2 = TestHelper.CreateAccessToken(server); //create web server and set token url to it - var endPoint = Util.GetFreeEndPoint(IPAddress.Loopback); + var endPoint = Util.GetFreeTcpEndPoint(IPAddress.Loopback); using var webServer = new WebServer(endPoint.Port); token1.Url = $"http://{endPoint}/accesskey"; diff --git a/VpnHood.ZTest/Tests/ClientServerTest.cs b/VpnHood.ZTest/Tests/ClientServerTest.cs index c4372ce66..4754687e9 100644 --- a/VpnHood.ZTest/Tests/ClientServerTest.cs +++ b/VpnHood.ZTest/Tests/ClientServerTest.cs @@ -30,14 +30,14 @@ public void Initialize() [TestMethod] public void Redirect_Server() { - var serverEndPoint1 = Util.GetFreeEndPoint(IPAddress.Loopback); + var serverEndPoint1 = Util.GetFreeTcpEndPoint(IPAddress.Loopback); var fileAccessServerOptions1 = new FileAccessServerOptions { TcpEndPoints = new[] { serverEndPoint1 } }; using var fileAccessServer1 = TestHelper.CreateFileAccessServer(fileAccessServerOptions1); using var testAccessServer1 = new TestAccessServer(fileAccessServer1); using var server1 = TestHelper.CreateServer(testAccessServer1); // Create Server 2 - var serverEndPoint2 = Util.GetFreeEndPoint(IPAddress.Loopback); + var serverEndPoint2 = Util.GetFreeTcpEndPoint(IPAddress.Loopback); var fileAccessServerOptions2 = new FileAccessServerOptions { TcpEndPoints = new[] { serverEndPoint2 } }; using var fileAccessServer2 = TestHelper.CreateFileAccessServer(fileAccessServerOptions2, fileAccessServer1.StoragePath); using var testAccessServer2 = new TestAccessServer(fileAccessServer2); @@ -58,7 +58,7 @@ public void Redirect_Server() public async Task TcpChannel() { // Create Server - var serverEp = Util.GetFreeEndPoint(IPAddress.IPv6Loopback); + var serverEp = Util.GetFreeTcpEndPoint(IPAddress.IPv6Loopback); var fileAccessServerOptions = TestHelper.CreateFileAccessServerOptions(); fileAccessServerOptions.TcpEndPoints= new[] { serverEp }; using var fileAccessServer = TestHelper.CreateFileAccessServer(fileAccessServerOptions); diff --git a/VpnHood.ZTest/Tests/ServerTest.cs b/VpnHood.ZTest/Tests/ServerTest.cs index eb39482bf..7bae37091 100644 --- a/VpnHood.ZTest/Tests/ServerTest.cs +++ b/VpnHood.ZTest/Tests/ServerTest.cs @@ -51,7 +51,7 @@ public async Task Auto_sync_sessions_by_interval() [TestMethod] public async Task Reconfigure() { - var serverEndPoint = Util.GetFreeEndPoint(IPAddress.Loopback); + var serverEndPoint = Util.GetFreeTcpEndPoint(IPAddress.Loopback); var fileAccessServerOptions = new FileAccessServerOptions { TcpEndPoints = new[] { serverEndPoint } }; using var fileAccessServer = TestHelper.CreateFileAccessServer(fileAccessServerOptions); var serverConfig = fileAccessServer.ServerConfig; diff --git a/VpnHood.ZTest/Tests/TcpDatagramTest.cs b/VpnHood.ZTest/Tests/TcpDatagramTest.cs new file mode 100644 index 000000000..d256cd6ea --- /dev/null +++ b/VpnHood.ZTest/Tests/TcpDatagramTest.cs @@ -0,0 +1,72 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using PacketDotNet; +using System; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using VpnHood.Common.Utils; +using VpnHood.Tunneling; +using VpnHood.Tunneling.DatagramMessaging; + +namespace VpnHood.Test.Tests; + +[TestClass] +public class TcpDatagramChannelTest +{ + [TestMethod] + public void DatagramMessages() + { + var ipPacket = DatagramMessageHandler.CreateMessage(new CloseDatagramMessage()); + Assert.IsTrue(DatagramMessageHandler.IsDatagramMessage(ipPacket)); + + var message = DatagramMessageHandler.ReadMessage(ipPacket); + Assert.IsTrue(message is CloseDatagramMessage); + } + + [TestMethod] + public async Task AutoCloseChannel() + { + // create server tcp listener + var tcpEndPoint = Util.GetFreeTcpEndPoint(IPAddress.Loopback); + var tcpListener = new TcpListener(tcpEndPoint); + tcpListener.Start(); + var listenerTask = tcpListener.AcceptTcpClientAsync(); + + // create tcp client and connect + using var tcpClient = new TcpClient(); + var connectTask = tcpClient.ConnectAsync(tcpEndPoint); + await Task.WhenAll(listenerTask, connectTask); + + // create server channel + var serverTcpClient = await listenerTask; + var serverStream = new TcpClientStream(serverTcpClient, serverTcpClient.GetStream()); + using var serverChannel = new TcpDatagramChannel(serverStream); + using var serverTunnel = new Tunnel(new TunnelOptions()); + serverTunnel.AddChannel(serverChannel); + IPPacket? lastServerReceivedPacket = null; + serverTunnel.OnPacketReceived += (_, args) => + { + lastServerReceivedPacket = args.IpPackets.Last(); + }; + + // create client channel + var clientStream = new TcpClientStream(tcpClient, tcpClient.GetStream()); + using var clientChannel = new TcpDatagramChannel(clientStream, TimeSpan.FromMilliseconds(1000)); + using var clientTunnel = new Tunnel(new TunnelOptions()); + clientTunnel.AddChannel(clientChannel); + + // ------- + // Check sending packet to server + // ------ + var testPacket = PacketUtil.CreateUdpPacket(IPEndPoint.Parse("1.1.1.1:1"), IPEndPoint.Parse("1.1.1.1:2"), new byte[] { 1, 2, 3 }); + await clientTunnel.SendPacket(testPacket); + await TestHelper.AssertEqualsWait(testPacket.ToString(), () => lastServerReceivedPacket?.ToString()); + await TestHelper.AssertEqualsWait(0, () => clientTunnel.DatagramChannels.Length); + await TestHelper.AssertEqualsWait(0, () => serverTunnel.DatagramChannels.Length); + Assert.AreEqual(typeof(CloseDatagramMessage), DatagramMessageHandler.ReadMessage(lastServerReceivedPacket!).GetType()); + } + + +} \ No newline at end of file diff --git a/VpnHood.ZTest/Tests/TunnelTest.cs b/VpnHood.ZTest/Tests/TunnelTest.cs index f92657492..ae4efdb34 100644 --- a/VpnHood.ZTest/Tests/TunnelTest.cs +++ b/VpnHood.ZTest/Tests/TunnelTest.cs @@ -10,7 +10,6 @@ using PacketDotNet; using PacketDotNet.Utils; using VpnHood.Tunneling; -using VpnHood.Tunneling.DatagramMessaging; using ProtocolType = PacketDotNet.ProtocolType; namespace VpnHood.Test.Tests; @@ -102,7 +101,7 @@ public void UdpChannel_Direct() aes.GenerateKey(); var serverUdpClient = new UdpClient(new IPEndPoint(IPAddress.Loopback, 0)); - UdpChannel serverUdpChannel = new(false, serverUdpClient, 200, aes.Key); + var serverUdpChannel = new UdpChannel(false, serverUdpClient, 200, aes.Key); serverUdpChannel.Start(); var serverReceivedPackets = Array.Empty(); @@ -116,8 +115,9 @@ public void UdpChannel_Direct() var clientUdpClient = new UdpClient(new IPEndPoint(IPAddress.Loopback, 0)); if (serverUdpClient.Client.LocalEndPoint == null) throw new Exception("Client connection has not been established!"); + clientUdpClient.Connect((IPEndPoint)serverUdpClient.Client.LocalEndPoint); - UdpChannel clientUdpChannel = new(true, clientUdpClient, 200, aes.Key); + var clientUdpChannel = new UdpChannel(true, clientUdpClient, 200, aes.Key); clientUdpChannel.Start(); var clientReceivedPackets = Array.Empty(); @@ -188,14 +188,6 @@ public async Task UdpChannel_via_Tunnel() Assert.AreEqual(packets.Count, clientReceivedPackets.Length); } - [TestMethod] - public void DatagramMessages() - { - var ipPacket = DatagramMessageHandler.CreateMessage(new CloseDatagramMessage()); - Assert.IsTrue(DatagramMessageHandler.IsDatagramMessage(ipPacket)); - - var message = DatagramMessageHandler.ReadMessage(ipPacket); - Assert.IsTrue(message is CloseDatagramMessage); - } + } \ No newline at end of file From 8140daa9528b55b04d5d7c7171ab28310a2bd13e Mon Sep 17 00:00:00 2001 From: Trudy Date: Wed, 1 Feb 2023 18:58:02 -0800 Subject: [PATCH 3/7] TcpDatagramOptimization --- CHANGELOG.md | 8 ++ VpnHood.Client/ClientOptions.cs | 6 +- VpnHood.Client/VpnHoodClient.cs | 107 ++++++++++-------- VpnHood.Common/Logging/ISelfLog.cs | 2 +- VpnHood.Common/Net/IPAddressUtil.cs | 35 +++++- VpnHood.Common/Utils/Util.cs | 6 + VpnHood.Server.Access/ServerConfig.cs | 1 + VpnHood.Server/VpnHoodServer.cs | 6 +- .../DatagramMessageHandler.cs | 2 +- .../Exceptions/UdpClientQuotaException.cs | 2 +- VpnHood.Tunneling/Factory/ISocketFactory.cs | 3 +- VpnHood.Tunneling/Factory/SocketFactory.cs | 6 +- .../Messaging/HelloSessionResponse.cs | 1 + VpnHood.Tunneling/ProxyManager.cs | 1 - VpnHood.Tunneling/ProxyManagerOptions.cs | 1 - VpnHood.Tunneling/TcpDatagramChannel.cs | 48 ++++---- VpnHood.Tunneling/Tunnel.cs | 24 ++-- VpnHood.ZTest/TestHelper.cs | 6 + VpnHood.ZTest/TestWebServer.cs | 2 - VpnHood.ZTest/Tests/AccessTest.cs | 51 ++++----- VpnHood.ZTest/Tests/TcpDatagramTest.cs | 2 - 21 files changed, 195 insertions(+), 125 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a166c960a..997ec594e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# Upcoming +### Client +Update: Improve stability when using no UDP mode + +### Server +Feature: Improve stability by adding lifetime to TcpDatagarmChannel +Fix: IpV6 detection + # v2.6.342 ### Client * Fix: UDP port memory leak diff --git a/VpnHood.Client/ClientOptions.cs b/VpnHood.Client/ClientOptions.cs index 8470e207d..7426a0cbd 100644 --- a/VpnHood.Client/ClientOptions.cs +++ b/VpnHood.Client/ClientOptions.cs @@ -29,8 +29,10 @@ public class ClientOptions public SocketFactory SocketFactory { get; set; } = new(); public int MaxDatagramChannelCount { get; set; } = 4; public string UserAgent { get; set; } = Environment.OSVersion.ToString(); - -#if DEBUG + public TimeSpan MinTcpDatagramTimespan { get; set; } = TimeSpan.FromMinutes(5); + public TimeSpan MaxTcpDatagramTimespan { get; set; } = TimeSpan.FromMinutes(10); + +#if DEBUG public int ProtocolVersion { get; set; } #endif } \ No newline at end of file diff --git a/VpnHood.Client/VpnHoodClient.cs b/VpnHood.Client/VpnHoodClient.cs index 5bc03e8d8..3a8d9b065 100644 --- a/VpnHood.Client/VpnHoodClient.cs +++ b/VpnHood.Client/VpnHoodClient.cs @@ -55,18 +55,26 @@ public void Clear() private readonly SendingPackets _sendingPacket = new(); private readonly TcpProxyHost _tcpProxyHost; private bool _disposed; - private Timer? _intervalCheckTimer; - private bool _isManagingDatagramChannels; + private readonly SemaphoreSlim _datagramChannelsSemaphore = new(1, 1); private DateTime? _lastConnectionErrorTime; private byte[]? _sessionKey; private ClientState _state = ClientState.None; private readonly IPAddress? _dnsServerIpV4; private readonly IPAddress? _dnsServerIpV6; private readonly IIpFilter? _ipFilter; + private readonly TimeSpan _minTcpDatagramLifespan; + private readonly TimeSpan _maxTcpDatagramLifespan; + private bool _udpChannelAdded; + private DateTime _lastReceivedPacketTime = DateTime.MinValue; + private int ProtocolVersion { get; } + public Version? ServerVersion { get; private set; } + private bool IsTcpDatagramLifespanSupported => ServerVersion?.Build >= 345; //will be deprecated + internal Nat Nat { get; } internal Tunnel Tunnel { get; } internal SocketFactory SocketFactory { get; } + public event EventHandler? StateChanged; public IPAddress? PublicAddress { get; private set; } public bool IsIpV6Supported { get; private set; } public TimeSpan SessionTimeout { get; set; } @@ -88,16 +96,24 @@ public void Clear() public string UserAgent { get; } public IPEndPoint? HostEndPoint { get; private set; } public int DatagramChannelsCount => Tunnel.DatagramChannels.Length; - public event EventHandler? StateChanged; public VpnHoodClient(IPacketCapture packetCapture, Guid clientId, Token token, ClientOptions options) { - if (options.TcpProxyLoopbackAddressIpV4 == null) throw new ArgumentNullException(nameof(options.TcpProxyLoopbackAddressIpV4)); - if (options.TcpProxyLoopbackAddressIpV6 == null) throw new ArgumentNullException(nameof(options.TcpProxyLoopbackAddressIpV6)); + if (options.TcpProxyLoopbackAddressIpV4 == null) + throw new ArgumentNullException(nameof(options.TcpProxyLoopbackAddressIpV4)); + + if (options.TcpProxyLoopbackAddressIpV6 == null) + throw new ArgumentNullException(nameof(options.TcpProxyLoopbackAddressIpV6)); + + if (!Util.IsInfinite(_maxTcpDatagramLifespan) && _maxTcpDatagramLifespan < _minTcpDatagramLifespan) + throw new ArgumentNullException(nameof(options.MaxTcpDatagramTimespan), $"{nameof(options.MaxTcpDatagramTimespan)} must be bigger or equal than {nameof(options.MinTcpDatagramTimespan)}."); + SocketFactory = options.SocketFactory ?? throw new ArgumentNullException(nameof(options.SocketFactory)); DnsServers = options.DnsServers ?? throw new ArgumentNullException(nameof(options.DnsServers)); _dnsServerIpV4 = DnsServers.FirstOrDefault(x => x.AddressFamily == AddressFamily.InterNetwork); _dnsServerIpV6 = DnsServers.FirstOrDefault(x => x.AddressFamily == AddressFamily.InterNetworkV6); + _minTcpDatagramLifespan = options.MinTcpDatagramTimespan; + _maxTcpDatagramLifespan = options.MaxTcpDatagramTimespan; Token = token ?? throw new ArgumentNullException(nameof(token)); Version = options.Version ?? throw new ArgumentNullException(nameof(Version)); @@ -209,9 +225,6 @@ public async Task Connect() // Establish first connection and create a session await ConnectInternal(_cancellationToken); - // run interval checker - _intervalCheckTimer = new Timer(state => _ = DoWatch(), null, 0, 5000); - // create Tcp Proxy Host VhLogger.Instance.LogTrace($"Starting {nameof(TcpProxyHost)}..."); _tcpProxyHost.Start(); @@ -296,13 +309,12 @@ private void ConfigPacketFilter(IPEndPoint hostEndPoint) private void Tunnel_OnChannelRemoved(object sender, ChannelEventArgs e) { - if (e.Channel is IDatagramChannel) - _ = DoWatch(); - } + // device is sleep. Don't wake it up + if (!Util.IsInfinite(_maxTcpDatagramLifespan) && FastDateTime.Now - _lastReceivedPacketTime > _maxTcpDatagramLifespan) + return; - private async Task DoWatch() - { - await ManageDatagramChannels(_cancellationToken); + if (e.Channel is IDatagramChannel) + _ = ManageDatagramChannels(_cancellationToken); } // WARNING: Performance Critical! @@ -314,6 +326,7 @@ private void Tunnel_OnPacketReceived(object sender, ChannelPacketReceivedEventAr UpdateDnsRequest(ipPacket, false); _packetCapture.SendPacketToInbound(e.IpPackets); + _lastReceivedPacketTime = FastDateTime.Now; } // WARNING: Performance Critical! @@ -378,15 +391,16 @@ private void PacketCapture_OnPacketReceivedFromInbound(object sender, PacketRece } // send packets + if (tunnelPackets.Count > 0) _ = ManageDatagramChannels(_cancellationToken); + if (tunnelPackets.Count > 0) Tunnel.SendPacket(tunnelPackets.ToArray()).Wait(_cancellationToken); if (passthruPackets.Count > 0) _packetCapture.SendPacketToOutbound(passthruPackets.ToArray()); if (proxyPackets.Count > 0) _proxyManager.SendPacket(proxyPackets.ToArray()); - if (tunnelPackets.Count > 0) Tunnel.SendPacket(tunnelPackets.ToArray()).Wait(_cancellationToken); if (tcpHostPackets.Count > 0) _packetCapture.SendPacketToInbound(_tcpProxyHost.ProcessOutgoingPacket(tcpHostPackets.ToArray())); } } catch (Exception ex) { - VhLogger.Instance.LogError($"{VhLogger.FormatTypeName(this)}: Error in processing packet! Error: {ex}"); + VhLogger.Instance.LogError(ex, "Could not process packet the capture packets."); } } @@ -502,13 +516,8 @@ private bool UpdateDnsRequest(IPPacket ipPacket, bool outgoing) /// true if managing is in progress private async Task ManageDatagramChannels(CancellationToken cancellationToken) { - // check is in progress - lock (this) - { - if (_isManagingDatagramChannels) - return; - _isManagingDatagramChannels = true; - } + if (!await _datagramChannelsSemaphore.WaitAsync(0, cancellationToken)) + return; try { @@ -538,8 +547,10 @@ private async Task ManageDatagramChannels(CancellationToken cancellationToken) if (!UseUdpChannel) { // remove UDP datagram channels - foreach (var channel in Tunnel.DatagramChannels.Where(x => x is UdpChannel)) - Tunnel.RemoveChannel(channel); + if (_udpChannelAdded) + foreach (var channel in Tunnel.DatagramChannels.Where(x => x is UdpChannel)) + Tunnel.RemoveChannel(channel); + _udpChannelAdded = false; // make sure there is enough DatagramChannel var curDatagramChannelCount = Tunnel.DatagramChannels.Length; @@ -547,7 +558,7 @@ private async Task ManageDatagramChannels(CancellationToken cancellationToken) return; // creating DatagramChannels - List tasks = new(); + var tasks = new List(); for (var i = curDatagramChannelCount; i < Tunnel.MaxDatagramChannelCount; i++) tasks.Add(AddTcpDatagramChannel(cancellationToken)); @@ -555,19 +566,18 @@ await Task.WhenAll(tasks) .ContinueWith(x => { if (x.IsFaulted) - VhLogger.Instance.LogError($"Couldn't add a {VhLogger.FormatTypeName()}!", x.Exception); - _isManagingDatagramChannels = false; + VhLogger.Instance.LogError(GeneralEventId.DatagramChannel, x.Exception, "Could not add a TcpDatagramChannel."); }, cancellationToken); } } catch (Exception ex) { if (_disposed) return; - VhLogger.Instance.LogError(ex.Message); + VhLogger.Instance.LogError(GeneralEventId.DatagramChannel, ex, "Could not Manage DatagramChannels."); } finally { - _isManagingDatagramChannels = false; + _datagramChannelsSemaphore.Release(); } } @@ -575,12 +585,13 @@ private void AddUdpChannel(int udpPort, byte[] udpKey) { if (HostEndPoint == null) throw new InvalidOperationException($"{nameof(HostEndPoint)} is not initialized!"); + if (udpPort == 0) throw new ArgumentException(nameof(udpPort)); if (udpKey == null || udpKey.Length == 0) throw new ArgumentNullException(nameof(udpKey)); var udpEndPoint = new IPEndPoint(HostEndPoint.Address, udpPort); VhLogger.Instance.LogInformation(GeneralEventId.DatagramChannel, - $"Creating {nameof(UdpChannel)}... ServerEp: {VhLogger.Format(udpEndPoint)}"); + "Creating a UdpChannel... ServerEp: {ServerEp}", VhLogger.Format(udpEndPoint)); var udpClient = SocketFactory.CreateUdpClient(HostEndPoint.AddressFamily); if (_packetCapture.CanProtectSocket) @@ -589,8 +600,15 @@ private void AddUdpChannel(int udpPort, byte[] udpKey) // add channel var udpChannel = new UdpChannel(true, udpClient, SessionId, udpKey); - try { Tunnel.AddChannel(udpChannel); } - catch { udpChannel.Dispose(); throw; } + try + { + _udpChannelAdded = true; // let have it before add channel to make sure it will be removed if any exception occur + Tunnel.AddChannel(udpChannel); + } + catch + { + udpChannel.Dispose(); throw; + } } internal async Task GetTlsConnectionToServer(EventId eventId, CancellationToken cancellationToken) @@ -702,6 +720,7 @@ private async Task ConnectInternal(CancellationToken cancellationToken, bool red SessionStatus.SuppressedTo = sessionResponse.SuppressedTo; PublicAddress = sessionResponse.ClientPublicAddress; IsIpV6Supported = sessionResponse.IsIpV6Supported; + ServerVersion = Version.Parse(sessionResponse.ServerVersion); // Get IncludeIpRange for clientIp if (_ipFilter != null) @@ -722,7 +741,7 @@ private async Task ConnectInternal(CancellationToken cancellationToken, bool red if (UseUdpChannel && sessionResponse.UdpPort != 0 && sessionResponse.UdpKey != null) AddUdpChannel(sessionResponse.UdpPort, sessionResponse.UdpKey); - await ManageDatagramChannels(cancellationToken); + _ = ManageDatagramChannels(cancellationToken); // done VhLogger.Instance.LogInformation(GeneralEventId.Session, @@ -735,12 +754,7 @@ private async Task ConnectInternal(CancellationToken cancellationToken, bool red private async Task AddTcpDatagramChannel(CancellationToken cancellationToken) { var tcpClientStream = await GetTlsConnectionToServer(GeneralEventId.DatagramChannel, cancellationToken); - return await AddTcpDatagramChannel(tcpClientStream, cancellationToken); - } - private async Task AddTcpDatagramChannel(TcpClientStream tcpClientStream, - CancellationToken cancellationToken) - { // Create the Request Message var request = new TcpDatagramChannelRequest(SessionId, SessionKey); @@ -748,8 +762,12 @@ private async Task AddTcpDatagramChannel(TcpClientStream tcp await SendRequest(tcpClientStream.Stream, RequestCode.TcpDatagramChannel, request, cancellationToken); + // find timespan + var lifespan = !Util.IsInfinite(_maxTcpDatagramLifespan) && IsTcpDatagramLifespanSupported + ? TimeSpan.FromSeconds(new Random().Next((int)_minTcpDatagramLifespan.TotalSeconds, (int)_maxTcpDatagramLifespan.TotalSeconds)) + : Timeout.InfiniteTimeSpan; + // add the new channel - var lifespan = TimeSpan.FromSeconds(new Random().Next(120, 360)); var channel = new TcpDatagramChannel(tcpClientStream, lifespan); try { Tunnel.AddChannel(channel); } catch { channel.Dispose(); throw; } @@ -824,9 +842,8 @@ private async Task SendByeRequest(TimeSpan timeout) try { // create cancellation token - var cts = new CancellationTokenSource(); - await using var timer = new Timer(_ => cts.Cancel(), null, timeout, Timeout.InfiniteTimeSpan); - var cancellationToken = cts.Token; + using var cancellationTokenSource = new CancellationTokenSource(timeout); + var cancellationToken = cancellationTokenSource.Token; using var tcpClientStream = await GetTlsConnectionToServer(GeneralEventId.Session, cancellationToken); @@ -909,14 +926,12 @@ public async ValueTask DisposeAsync() // log suppressedBy if (SessionStatus.SuppressedBy == SessionSuppressType.YourSelf) VhLogger.Instance.LogWarning("You suppressed by a session of yourself!"); + else if (SessionStatus.SuppressedBy == SessionSuppressType.Other) VhLogger.Instance.LogWarning("You suppressed a session of another client!"); // shutdown VhLogger.Instance.LogTrace("Shutting down..."); - if (_intervalCheckTimer != null) - await _intervalCheckTimer.DisposeAsync(); - VhLogger.Instance.LogTrace($"Disposing {VhLogger.FormatTypeName(_tcpProxyHost)}..."); _tcpProxyHost.Dispose(); diff --git a/VpnHood.Common/Logging/ISelfLog.cs b/VpnHood.Common/Logging/ISelfLog.cs index abf504416..71be2ce20 100644 --- a/VpnHood.Common/Logging/ISelfLog.cs +++ b/VpnHood.Common/Logging/ISelfLog.cs @@ -1,4 +1,4 @@ -namespace VpnHood.Server.Exceptions; +namespace VpnHood.Common.Logging; public interface ISelfLog { diff --git a/VpnHood.Common/Net/IPAddressUtil.cs b/VpnHood.Common/Net/IPAddressUtil.cs index 15a548b6f..f10a2e637 100644 --- a/VpnHood.Common/Net/IPAddressUtil.cs +++ b/VpnHood.Common/Net/IPAddressUtil.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; +using System.Net.NetworkInformation; using System.Net.Sockets; using System.Numerics; using System.Text.Json; @@ -26,6 +27,34 @@ public static async Task GetPrivateIpAddresses() return ret.ToArray(); } + public static async Task IsIpv6Supported() + { + var ping = new Ping(); + var ping1 = ping.SendPingAsync("2001:4860:4860::8888"); + var ping2 = ping.SendPingAsync("2001:4860:4860::8844"); + try + { + if ((await ping1).Status == IPStatus.Success) + return true; + } + catch + { + //ignore + } + + try + { + if ((await ping2).Status == IPStatus.Success) + return true; + } + catch + { + // ignore + } + + return false; + } + public static async Task GetPublicIpAddresses() { var ret = new List(); @@ -69,8 +98,8 @@ public static async Task GetPublicIpAddresses() // : "https://api6.ipify.org?format=json"; var url = addressFamily == AddressFamily.InterNetwork - ? "https://ip4.seeip.org/json" - : "https://ip6.seeip.org/json"; + ? "https://api4.my-ip.io/ip.json" + : "https://api6.my-ip.io/ip.json"; using var httpClient = new HttpClient(); httpClient.Timeout = timeout ?? TimeSpan.FromSeconds(5); @@ -252,7 +281,7 @@ public static IPAddress Anonymize(IPAddress ipAddress) else { var bytes = ipAddress.GetAddressBytes(); - for (var i=6; i< bytes.Length; i++) + for (var i = 6; i < bytes.Length; i++) bytes[i] = 0; return new IPAddress(bytes); } diff --git a/VpnHood.Common/Utils/Util.cs b/VpnHood.Common/Utils/Util.cs index 73fa44c45..e2cd217ee 100644 --- a/VpnHood.Common/Utils/Util.cs +++ b/VpnHood.Common/Utils/Util.cs @@ -241,4 +241,10 @@ public static string FormatBits(long bytes) // Byte return bytes.ToString("0"); } + + public static bool IsInfinite(TimeSpan timeSpan) + { + return timeSpan == TimeSpan.MaxValue || timeSpan == Timeout.InfiniteTimeSpan; + } + } diff --git a/VpnHood.Server.Access/ServerConfig.cs b/VpnHood.Server.Access/ServerConfig.cs index 516c0b60d..9ef504c94 100644 --- a/VpnHood.Server.Access/ServerConfig.cs +++ b/VpnHood.Server.Access/ServerConfig.cs @@ -22,4 +22,5 @@ public class ServerConfig public int? MinCompletionPortThreads { get; set; } public int? MaxCompletionPortThreads { get; set; } public bool LogAnonymizer { get; set; } = false; + public bool AllowIpV6 { get; set; } = true; } \ No newline at end of file diff --git a/VpnHood.Server/VpnHoodServer.cs b/VpnHood.Server/VpnHoodServer.cs index 2e185e6e6..e9dee29c7 100644 --- a/VpnHood.Server/VpnHoodServer.cs +++ b/VpnHood.Server/VpnHoodServer.cs @@ -146,7 +146,9 @@ private async Task Configure() var publicIpV4 = serverInfo.PublicIpAddresses.SingleOrDefault(x => x.AddressFamily == AddressFamily.InterNetwork); var publicIpV6 = serverInfo.PublicIpAddresses.SingleOrDefault(x => x.AddressFamily == AddressFamily.InterNetworkV6); - VhLogger.Instance.LogInformation($"Public IPv4: {VhLogger.Format(publicIpV4)}, Public IPv6: {VhLogger.Format(publicIpV6)}"); + var isIpV6Supported = publicIpV6 != null || await IPAddressUtil.IsIpv6Supported(); + VhLogger.Instance.LogInformation("Public IPv4: {IPv4}, Public IPv6: {IpV6}, IsV6Supported: {IsV6Supported}", + VhLogger.Format(publicIpV4), VhLogger.Format(publicIpV6), isIpV6Supported); // get configuration from access server VhLogger.Instance.LogTrace("Sending config request to the Access Server..."); @@ -165,7 +167,7 @@ private async Task Configure() var verb = _tcpHost.IsStarted ? "Restarting" : "Starting"; VhLogger.Instance.LogInformation($"{verb} {VhLogger.FormatTypeName(_tcpHost)}..."); if (_tcpHost.IsStarted) await _tcpHost.Stop(); - _tcpHost.Start(serverConfig.TcpEndPoints, publicIpV6 != null); + _tcpHost.Start(serverConfig.TcpEndPoints, isIpV6Supported && serverConfig.AllowIpV6); // set config status State = ServerState.Ready; diff --git a/VpnHood.Tunneling/DatagramMessaging/DatagramMessageHandler.cs b/VpnHood.Tunneling/DatagramMessaging/DatagramMessageHandler.cs index 3c8b4a057..5b12be868 100644 --- a/VpnHood.Tunneling/DatagramMessaging/DatagramMessageHandler.cs +++ b/VpnHood.Tunneling/DatagramMessaging/DatagramMessageHandler.cs @@ -32,7 +32,7 @@ public static bool IsDatagramMessage(IPPacket ipPacket) public static DatagramBaseMessage ReadMessage(IPPacket ipPacket) { if (!IsDatagramMessage(ipPacket)) - throw new ArgumentException("packet is not an Datagram message.", nameof(ipPacket)); + throw new ArgumentException("packet is not a Datagram message.", nameof(ipPacket)); var udpPacket = PacketUtil.ExtractUdp(ipPacket); diff --git a/VpnHood.Tunneling/Exceptions/UdpClientQuotaException.cs b/VpnHood.Tunneling/Exceptions/UdpClientQuotaException.cs index 89491c892..fa1b88411 100644 --- a/VpnHood.Tunneling/Exceptions/UdpClientQuotaException.cs +++ b/VpnHood.Tunneling/Exceptions/UdpClientQuotaException.cs @@ -1,5 +1,5 @@ using System; -using VpnHood.Server.Exceptions; +using VpnHood.Common.Logging; namespace VpnHood.Tunneling.Exceptions; diff --git a/VpnHood.Tunneling/Factory/ISocketFactory.cs b/VpnHood.Tunneling/Factory/ISocketFactory.cs index 59f0b9875..1ee70cc6b 100644 --- a/VpnHood.Tunneling/Factory/ISocketFactory.cs +++ b/VpnHood.Tunneling/Factory/ISocketFactory.cs @@ -1,5 +1,4 @@ -using System; -using System.Net.Sockets; +using System.Net.Sockets; namespace VpnHood.Tunneling.Factory; diff --git a/VpnHood.Tunneling/Factory/SocketFactory.cs b/VpnHood.Tunneling/Factory/SocketFactory.cs index 861317e3c..50ad9d740 100644 --- a/VpnHood.Tunneling/Factory/SocketFactory.cs +++ b/VpnHood.Tunneling/Factory/SocketFactory.cs @@ -9,7 +9,7 @@ namespace VpnHood.Tunneling.Factory; public class SocketFactory : ISocketFactory { - private bool _isKeepAliveHasError = false; + private bool _hasKeepAliveError; public virtual TcpClient CreateTcpClient(AddressFamily addressFamily) { @@ -24,7 +24,7 @@ public virtual UdpClient CreateUdpClient(AddressFamily addressFamily) public virtual void SetKeepAlive(Socket socket, bool enable) { - if (_isKeepAliveHasError) + if (_hasKeepAliveError) return; try @@ -39,7 +39,7 @@ public virtual void SetKeepAlive(Socket socket, bool enable) } catch (Exception ex) { - _isKeepAliveHasError = true; + _hasKeepAliveError = true; VhLogger.Instance.LogWarning(ex, "KeepAlive is not supported! Consider upgrading your OS."); } } diff --git a/VpnHood.Tunneling/Messaging/HelloSessionResponse.cs b/VpnHood.Tunneling/Messaging/HelloSessionResponse.cs index baa550368..19dadc065 100644 --- a/VpnHood.Tunneling/Messaging/HelloSessionResponse.cs +++ b/VpnHood.Tunneling/Messaging/HelloSessionResponse.cs @@ -27,4 +27,5 @@ public HelloSessionResponse(SessionResponse obj) [JsonConverter(typeof(IPAddressConverter))] public IPAddress ClientPublicAddress { get; set; } = null!; + } \ No newline at end of file diff --git a/VpnHood.Tunneling/ProxyManager.cs b/VpnHood.Tunneling/ProxyManager.cs index cf9b12ecb..84d400d14 100644 --- a/VpnHood.Tunneling/ProxyManager.cs +++ b/VpnHood.Tunneling/ProxyManager.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.Logging; using PacketDotNet; using VpnHood.Common.Logging; -using VpnHood.Server.Exceptions; using VpnHood.Tunneling.Factory; using ProtocolType = PacketDotNet.ProtocolType; diff --git a/VpnHood.Tunneling/ProxyManagerOptions.cs b/VpnHood.Tunneling/ProxyManagerOptions.cs index b197e60ce..7467dc53f 100644 --- a/VpnHood.Tunneling/ProxyManagerOptions.cs +++ b/VpnHood.Tunneling/ProxyManagerOptions.cs @@ -1,6 +1,5 @@ using System; using VpnHood.Common.Logging; -using VpnHood.Tunneling.Factory; namespace VpnHood.Tunneling; diff --git a/VpnHood.Tunneling/TcpDatagramChannel.cs b/VpnHood.Tunneling/TcpDatagramChannel.cs index f462561c3..279e5694d 100644 --- a/VpnHood.Tunneling/TcpDatagramChannel.cs +++ b/VpnHood.Tunneling/TcpDatagramChannel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -38,7 +39,7 @@ public TcpDatagramChannel(TcpClientStream tcpClientStream, TimeSpan lifespan) { _tcpClientStream = tcpClientStream ?? throw new ArgumentNullException(nameof(tcpClientStream)); tcpClientStream.TcpClient.NoDelay = true; - if (lifespan != Timeout.InfiniteTimeSpan) + if (!Util.IsInfinite(lifespan)) { _lifeTime = FastDateTime.Now + lifespan; JobRunner.Default.Add(this); @@ -107,12 +108,23 @@ private async Task ReadTask() LastActivityTime = FastDateTime.Now; ReceivedByteCount += ipPackets.Sum(x => x.TotalPacketLength); - FireReceivedPackets(ipPackets); // check datagram message + List? processedPackets = null; foreach (var ipPacket in ipPackets) - if (DatagramMessageHandler.IsDatagramMessage(ipPacket)) - ProcessMessage(DatagramMessageHandler.ReadMessage(ipPacket)); + if (ProcessMessage(ipPacket)) + { + processedPackets ??= new List(); + processedPackets.Add(ipPacket); + } + + // remove all processed packets + if (processedPackets != null) + ipPackets = ipPackets.Except(processedPackets).ToArray(); + + // fire new packets + if (ipPackets.Length > 0) + OnPacketReceived?.Invoke(this, new ChannelPacketReceivedEventArgs(ipPackets, this)); } } catch (Exception ex) @@ -126,9 +138,13 @@ private async Task ReadTask() } } - private void ProcessMessage(DatagramBaseMessage message) + private bool ProcessMessage(IPPacket ipPacket) { - if (message is not CloseDatagramMessage) return; + if (!DatagramMessageHandler.IsDatagramMessage(ipPacket)) + return false; + + var message = DatagramMessageHandler.ReadMessage(ipPacket); + if (message is not CloseDatagramMessage) return false; VhLogger.Instance.Log(LogLevel.Information, GeneralEventId.DatagramChannel, "Receiving the close message from the peer. Lifetime: {Lifetime}, CurrentClosePending: {IsClosePending}", _lifeTime, IsClosePending); @@ -138,6 +154,8 @@ private void ProcessMessage(DatagramBaseMessage message) Dispose(); else _ = SendCloseMessageAsync(); + + return true; } private Task SendCloseMessageAsync() @@ -154,25 +172,9 @@ private Task SendCloseMessageAsync() return SendPacketAsync(new[] { ipPacket }); } - private void FireReceivedPackets(IPPacket[] ipPackets) - { - if (_disposed) - return; - - try - { - OnPacketReceived?.Invoke(this, new ChannelPacketReceivedEventArgs(ipPackets, this)); - } - catch (Exception ex) - { - VhLogger.Instance.Log(LogLevel.Warning, GeneralEventId.Udp, - $"Error in processing received packets! Error: {ex.Message}"); - } - } - public Task RunJob() { - if (!IsClosePending && FastDateTime.Now > _lifeTime) + if (!IsClosePending && FastDateTime.Now > _lifeTime ) _ = SendCloseMessageAsync(); return Task.CompletedTask; diff --git a/VpnHood.Tunneling/Tunnel.cs b/VpnHood.Tunneling/Tunnel.cs index 4b2c0ae9c..091db9f5f 100644 --- a/VpnHood.Tunneling/Tunnel.cs +++ b/VpnHood.Tunneling/Tunnel.cs @@ -8,6 +8,7 @@ using VpnHood.Common.Collections; using VpnHood.Common.Logging; using VpnHood.Common.Utils; +using VpnHood.Tunneling.DatagramMessaging; namespace VpnHood.Tunneling; @@ -47,6 +48,7 @@ public Tunnel(TunnelOptions? options = null) _maxDatagramChannelCount = options.MaxDatagramChannelCount; _speedMonitorTimer = new Timer(_ => UpdateSpeed(), null, TimeSpan.Zero, _speedTestThreshold); } + public int TcpProxyChannelCount { get @@ -133,17 +135,19 @@ public void AddChannel(IDatagramChannel datagramChannel) lock (_channelListLock) { if (DatagramChannels.Contains(datagramChannel)) - throw new Exception($"{VhLogger.FormatTypeName(datagramChannel)} already exists in the collection!"); + throw new Exception("the DatagramChannel already exists in the collection."); datagramChannel.OnPacketReceived += Channel_OnPacketReceived; DatagramChannels = DatagramChannels.Concat(new[] { datagramChannel }).ToArray(); VhLogger.Instance.LogInformation(GeneralEventId.DatagramChannel, - $"A {VhLogger.FormatTypeName(datagramChannel)} has been added. ChannelCount: {DatagramChannels.Length}"); + "A DatagramChannel has been added. ChannelCount: {ChannelCount}", DatagramChannels.Length); // remove additional Datagram channels while (DatagramChannels.Length > MaxDatagramChannelCount) { - VhLogger.Instance.LogInformation(GeneralEventId.DatagramChannel, $"Removing an exceeded DatagramChannel! ChannelCount: {DatagramChannels.Length}"); + VhLogger.Instance.LogInformation(GeneralEventId.DatagramChannel, + "Removing an exceeded DatagramChannel. ChannelCount: {ChannelCount}", DatagramChannels.Length); + RemoveChannel(DatagramChannels[0]); } } @@ -213,6 +217,7 @@ public void RemoveChannel(IChannel channel) } // dispose or close-pending + // ReSharper disable once MergeIntoPattern if (channel.Connected && channel.IsClosePending) _closePendingChannels.TryAdd(channel, new TimeoutItem(channel, true)); else @@ -243,14 +248,19 @@ private void Channel_OnPacketReceived(object sender, ChannelPacketReceivedEventA if (VhLogger.IsDiagnoseMode) PacketUtil.LogPackets(e.IpPackets, $"Packets received from {nameof(Tunnel)}."); + // check datagram message + // performance critical; don't create another array by linq + if (e.IpPackets.Any(DatagramMessageHandler.IsDatagramMessage)) + e = new ChannelPacketReceivedEventArgs( + e.IpPackets.Where(x => !DatagramMessageHandler.IsDatagramMessage(x)).ToArray(), e.Channel); + try { OnPacketReceived?.Invoke(sender, e); } catch (Exception ex) { - VhLogger.Instance.Log(LogLevel.Error, - $"Packets dropped! Error in processing channel received packets. Message: {ex}"); + VhLogger.Instance.LogError(GeneralEventId.DatagramChannel, ex, "Packets dropped! Error in processing channel received packets."); } } @@ -276,7 +286,7 @@ public async Task SendPacket(IPPacket[] ipPackets) // check timeout if (FastDateTime.Now - dateTime > _datagramPacketTimeout) - throw new TimeoutException("Could not send the datagram packets."); + throw new TimeoutException("Could not send datagram packets."); } // add all packets to the queue @@ -291,7 +301,7 @@ public async Task SendPacket(IPPacket[] ipPackets) } if (VhLogger.IsDiagnoseMode) - PacketUtil.LogPackets(ipPackets, $"Packet sent to {nameof(Tunnel)} queue."); + PacketUtil.LogPackets(ipPackets, "Packet sent to tunnel queue."); } private async Task SendPacketTask(IDatagramChannel channel) diff --git a/VpnHood.ZTest/TestHelper.cs b/VpnHood.ZTest/TestHelper.cs index ce054aee9..87e3e6c47 100644 --- a/VpnHood.ZTest/TestHelper.cs +++ b/VpnHood.ZTest/TestHelper.cs @@ -93,6 +93,12 @@ public static void WaitForClientState(VpnHoodClient client, ClientState clientSt Assert.AreEqual(clientState, client.State); } + public static Task WaitForClientStateAsync(VpnHoodClient client, ClientState clientState, int timeout = 6000) + { + return AssertEqualsWait(clientState, () => client.State, "Client state didn't reach to expected value.", timeout); + } + + private static PingReply SendPing(Ping? ping = null, IPAddress? ipAddress = null, int timeout = 3000) { using var pingT = new Ping(); diff --git a/VpnHood.ZTest/TestWebServer.cs b/VpnHood.ZTest/TestWebServer.cs index f79f779f9..f94f0ef2f 100644 --- a/VpnHood.ZTest/TestWebServer.cs +++ b/VpnHood.ZTest/TestWebServer.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using System.Net; -using System.Net.Http; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -10,7 +9,6 @@ using EmbedIO; using EmbedIO.Routing; using EmbedIO.WebApi; -using SharpPcap; namespace VpnHood.Test; diff --git a/VpnHood.ZTest/Tests/AccessTest.cs b/VpnHood.ZTest/Tests/AccessTest.cs index c20f2148e..cc2915141 100644 --- a/VpnHood.ZTest/Tests/AccessTest.cs +++ b/VpnHood.ZTest/Tests/AccessTest.cs @@ -1,15 +1,10 @@ using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using VpnHood.Client; -using VpnHood.Common.JobController; using VpnHood.Common.Logging; using VpnHood.Common.Messaging; -using VpnHood.Common.Utils; -using VpnHood.Tunneling; namespace VpnHood.Test.Tests; @@ -18,10 +13,10 @@ public class AccessTest { [TestMethod] - public void Foo() + public async Task Foo() { - //var a = UserAgentParser.GetOperatingSystem("Mozilla/5.0 (Linux; Android 9; Redmi Note 8 Build/PKQ1.190616.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/108.0.5359.128 Mobile Safari/537.36"); - //Console.WriteLine(a); + Console.WriteLine(DateTime.Now + Timeout.InfiniteTimeSpan); + await Task.Delay(0); } [TestInitialize] @@ -90,26 +85,26 @@ public async Task Server_reject_expired_access_hello() } [TestMethod] - public void Server_reject_expired_access_at_runtime() + public async Task Server_reject_expired_access_at_runtime() { var fileAccessServerOptions = TestHelper.CreateFileAccessServerOptions(); fileAccessServerOptions.SessionOptions.SyncInterval = TimeSpan.FromMilliseconds(200); - using var server = TestHelper.CreateServer(fileAccessServerOptions); + await using var server = TestHelper.CreateServer(fileAccessServerOptions); // create an short expiring token - var accessToken = TestHelper.CreateAccessToken(server, expirationTime: DateTime.Now.AddSeconds(1)); + var accessToken = TestHelper.CreateAccessToken(server, expirationTime: DateTime.Now.AddSeconds(2)); // connect and download - using var client1 = TestHelper.CreateClient(accessToken); + await using var client1 = TestHelper.CreateClient(accessToken); try { - Thread.Sleep(2000); - TestHelper.Test_Https(timeout: 1000); + await Task.Delay(2000); + await TestHelper.Test_HttpsAsync(timeout: 1000); } catch { /* ignored */ } - TestHelper.WaitForClientState(client1, ClientState.Disposed); + await TestHelper.WaitForClientStateAsync(client1, ClientState.Disposed); Assert.AreEqual(SessionErrorCode.AccessExpired, client1.SessionStatus.ErrorCode); } @@ -169,20 +164,20 @@ public void Server_reject_trafficOverflow_access() } [TestMethod] - public void Server_maxClient_suppress_other_sessions() + public async Task Server_maxClient_suppress_other_sessions() { using var packetCapture = TestHelper.CreatePacketCapture(); // Create Server - using var server = TestHelper.CreateServer(); + await using var server = TestHelper.CreateServer(); var token = TestHelper.CreateAccessToken(server, 2); // create default token with 2 client count - using var client1 = TestHelper.CreateClient(packetCapture: packetCapture, token: token, + await using var client1 = TestHelper.CreateClient(packetCapture: packetCapture, token: token, clientId: Guid.NewGuid(), options: new ClientOptions { AutoDisposePacketCapture = false }); // suppress by yourself - using var client2 = TestHelper.CreateClient(packetCapture: packetCapture, token: token, + await using var client2 = TestHelper.CreateClient(packetCapture: packetCapture, token: token, clientId: client1.ClientId, options: new ClientOptions { AutoDisposePacketCapture = false }); Assert.AreEqual(SessionSuppressType.YourSelf, client2.SessionStatus.SuppressedTo); Assert.AreEqual(SessionSuppressType.None, client2.SessionStatus.SuppressedBy); @@ -190,7 +185,7 @@ public void Server_maxClient_suppress_other_sessions() // new connection attempt will result to disconnect of client1 try { - TestHelper.Test_Https(); + await TestHelper.Test_HttpsAsync(); } catch { @@ -198,22 +193,22 @@ public void Server_maxClient_suppress_other_sessions() } // wait for finishing client1 - TestHelper.WaitForClientState(client1, ClientState.Disposed); + await TestHelper.WaitForClientStateAsync(client1, ClientState.Disposed); Assert.AreEqual(ClientState.Disposed, client1.State, "Client1 has not been stopped yet!"); Assert.AreEqual(SessionSuppressType.None, client1.SessionStatus.SuppressedTo); Assert.AreEqual(SessionSuppressType.YourSelf, client1.SessionStatus.SuppressedBy); // suppress by other (MaxTokenClient is 2) - using var client3 = TestHelper.CreateClient(packetCapture: packetCapture, token: token, + await using var client3 = TestHelper.CreateClient(packetCapture: packetCapture, token: token, clientId: Guid.NewGuid(), options: new ClientOptions { AutoDisposePacketCapture = false }); - using var client4 = TestHelper.CreateClient(packetCapture: packetCapture, token: token, + await using var client4 = TestHelper.CreateClient(packetCapture: packetCapture, token: token, clientId: Guid.NewGuid(), options: new ClientOptions { AutoDisposePacketCapture = false }); // send a request to check first open client try { - TestHelper.Test_Https(); + await TestHelper.Test_HttpsAsync(); } catch { @@ -222,13 +217,13 @@ public void Server_maxClient_suppress_other_sessions() // create a client with another token var accessTokenX = TestHelper.CreateAccessToken(server); - using var clientX = TestHelper.CreateClient(packetCapture: packetCapture, clientId: Guid.NewGuid(), + await using var clientX = TestHelper.CreateClient(packetCapture: packetCapture, clientId: Guid.NewGuid(), token: accessTokenX, options: new ClientOptions { AutoDisposePacketCapture = false }); // send a request to check first open client try { - TestHelper.Test_Https(); + await TestHelper.Test_HttpsAsync(); } catch { @@ -237,7 +232,7 @@ public void Server_maxClient_suppress_other_sessions() try { - TestHelper.Test_Https(); + await TestHelper.Test_HttpsAsync(); } catch { @@ -245,7 +240,7 @@ public void Server_maxClient_suppress_other_sessions() } // wait for finishing client2 - TestHelper.WaitForClientState(client2, ClientState.Disposed); + await TestHelper.WaitForClientStateAsync(client2, ClientState.Disposed); Assert.AreEqual(SessionSuppressType.YourSelf, client2.SessionStatus.SuppressedTo); Assert.AreEqual(SessionSuppressType.Other, client2.SessionStatus.SuppressedBy); Assert.AreEqual(SessionSuppressType.None, client3.SessionStatus.SuppressedBy); diff --git a/VpnHood.ZTest/Tests/TcpDatagramTest.cs b/VpnHood.ZTest/Tests/TcpDatagramTest.cs index d256cd6ea..b3600b228 100644 --- a/VpnHood.ZTest/Tests/TcpDatagramTest.cs +++ b/VpnHood.ZTest/Tests/TcpDatagramTest.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net; using System.Net.Sockets; -using System.Threading; using System.Threading.Tasks; using VpnHood.Common.Utils; using VpnHood.Tunneling; @@ -65,7 +64,6 @@ public async Task AutoCloseChannel() await TestHelper.AssertEqualsWait(testPacket.ToString(), () => lastServerReceivedPacket?.ToString()); await TestHelper.AssertEqualsWait(0, () => clientTunnel.DatagramChannels.Length); await TestHelper.AssertEqualsWait(0, () => serverTunnel.DatagramChannels.Length); - Assert.AreEqual(typeof(CloseDatagramMessage), DatagramMessageHandler.ReadMessage(lastServerReceivedPacket!).GetType()); } From 129f1180d86488631a91224566053c5c9b5c0c11 Mon Sep 17 00:00:00 2001 From: Trudy Date: Wed, 1 Feb 2023 19:04:41 -0800 Subject: [PATCH 4/7] Publish v2.6.345 --- CHANGELOG.md | 2 +- Pub/Version.json | 6 +++--- VpnHood.Client.App.Android/Properties/AndroidManifest.xml | 2 +- VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj | 4 ++-- VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj | 4 ++-- VpnHood.Client.App/VpnHood.Client.App.csproj | 4 ++-- .../VpnHood.Client.Device.WinDivert.csproj | 4 ++-- VpnHood.Client.Device/VpnHood.Client.Device.csproj | 4 ++-- VpnHood.Client/VpnHood.Client.csproj | 4 ++-- VpnHood.Common/VpnHood.Common.csproj | 4 ++-- VpnHood.Server.Access/VpnHood.Server.Access.csproj | 4 ++-- VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj | 4 ++-- VpnHood.Server/VpnHood.Server.csproj | 4 ++-- VpnHood.Tunneling/VpnHood.Tunneling.csproj | 4 ++-- 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 997ec594e..410740f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Upcoming +# v2.6.345 ### Client Update: Improve stability when using no UDP mode diff --git a/Pub/Version.json b/Pub/Version.json index ead162a10..88b17fb2c 100644 --- a/Pub/Version.json +++ b/Pub/Version.json @@ -1,8 +1,8 @@ { "Major": 2, "Minor": 6, - "Build": 344, - "BumpTime": "2023-01-28T00:07:43.7571315Z", - "Prerelease": false, + "Build": 345, + "BumpTime": "2023-02-02T02:59:20.0744636Z", + "Prerelease": true, "DeprecatedVersion": "2.0.0" } diff --git a/VpnHood.Client.App.Android/Properties/AndroidManifest.xml b/VpnHood.Client.App.Android/Properties/AndroidManifest.xml index f8ed6eda0..c46adb3d4 100644 --- a/VpnHood.Client.App.Android/Properties/AndroidManifest.xml +++ b/VpnHood.Client.App.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + diff --git a/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj b/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj index ffb3a7c9f..ac3264a85 100644 --- a/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj +++ b/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj @@ -12,8 +12,8 @@ VpnHood.png Tiny internal webserver to server your single-page application (SPA). You need this only if you want to create a UI for your VpnHood client by single-page application (SPA). VpnHood.Client.App.UI - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj b/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj index 510e99f7d..b107e5706 100644 --- a/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj +++ b/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj @@ -19,8 +19,8 @@ VpnHood.png VpnHood.Client.App.Win - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable 11 diff --git a/VpnHood.Client.App/VpnHood.Client.App.csproj b/VpnHood.Client.App/VpnHood.Client.App.csproj index 54471b088..4b38b9e02 100644 --- a/VpnHood.Client.App/VpnHood.Client.App.csproj +++ b/VpnHood.Client.App/VpnHood.Client.App.csproj @@ -11,8 +11,8 @@ https://github.com/vpnhood/vpnhood Readymade Vpn App skeleton for VpnHood clients. You just need to create a UI on it. VpnHood.Client.App - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj b/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj index 894fc7849..fb1ab1c48 100644 --- a/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj +++ b/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj @@ -9,10 +9,10 @@ https://github.com/vpnhood/vpnhood VpnHood.png VpnHood client device provider for Windows using WinDivert. - 2.6.344 + 2.6.345 VpnHood.Client.Device.WinDivert 1.1.226 - 2.6.344 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Client.Device/VpnHood.Client.Device.csproj b/VpnHood.Client.Device/VpnHood.Client.Device.csproj index a6a9353cd..9d02be984 100644 --- a/VpnHood.Client.Device/VpnHood.Client.Device.csproj +++ b/VpnHood.Client.Device/VpnHood.Client.Device.csproj @@ -14,8 +14,8 @@ VpnHood.Client.Device VpnHood.Client.Device - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Client/VpnHood.Client.csproj b/VpnHood.Client/VpnHood.Client.csproj index 750dfb590..0825b8cc0 100644 --- a/VpnHood.Client/VpnHood.Client.csproj +++ b/VpnHood.Client/VpnHood.Client.csproj @@ -13,8 +13,8 @@ 2022 VpnHood VpnHood.Client VPN VpnClient Proxy - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Common/VpnHood.Common.csproj b/VpnHood.Common/VpnHood.Common.csproj index 229804836..4e9013391 100644 --- a/VpnHood.Common/VpnHood.Common.csproj +++ b/VpnHood.Common/VpnHood.Common.csproj @@ -12,8 +12,8 @@ VpnHood.Common VpnHood.png VpnHood Common Library is shared among all other VpnHood modules. - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Server.Access/VpnHood.Server.Access.csproj b/VpnHood.Server.Access/VpnHood.Server.Access.csproj index 896f3542a..5ff13cf67 100644 --- a/VpnHood.Server.Access/VpnHood.Server.Access.csproj +++ b/VpnHood.Server.Access/VpnHood.Server.Access.csproj @@ -12,8 +12,8 @@ VpnHood.Server.Access VpnHood.png Stores, and retrieves end users' access and usage. Provides required interfaces and classes to use or create an access server and accounting. - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj b/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj index 4c61182c5..ec4431142 100644 --- a/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj +++ b/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj @@ -12,8 +12,8 @@ https://github.com/vpnhood/vpnhood LGPL-2.1-only VpnHood.png - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Server/VpnHood.Server.csproj b/VpnHood.Server/VpnHood.Server.csproj index 83c6d495f..e9ea9ba1b 100644 --- a/VpnHood.Server/VpnHood.Server.csproj +++ b/VpnHood.Server/VpnHood.Server.csproj @@ -13,8 +13,8 @@ VpnHood.png The core of VpnHood server. It can listen and accept connections from VpnHood clients. VpnHood.Server - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Tunneling/VpnHood.Tunneling.csproj b/VpnHood.Tunneling/VpnHood.Tunneling.csproj index a7aed1b44..01a51e24a 100644 --- a/VpnHood.Tunneling/VpnHood.Tunneling.csproj +++ b/VpnHood.Tunneling/VpnHood.Tunneling.csproj @@ -15,8 +15,8 @@ VpnHood.png VpnHood.Tunneling Provides tunnelling classes and protocols shared between VpnHood.Client and VpnHood.Server. - 2.6.344 - 2.6.344 + 2.6.345 + 2.6.345 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest From 796c76b3814750403c654bfdce53a08b9a695d4a Mon Sep 17 00:00:00 2001 From: Trudy Date: Thu, 2 Feb 2023 18:31:39 -0800 Subject: [PATCH 5/7] udp tester --- CHANGELOG.md | 6 +- Pub/PublishToGitHub.ps1 | 2 +- .../Properties/launchSettings.json | 2 +- VpnHood.Server/SessionManager.cs | 2 +- VpnHood.Tunneling/TcpDatagramChannel.cs | 1 + VpnHood.ZUdpTrafficTest/Program.cs | 46 +++++++++++ .../Properties/launchSettings.json | 9 +++ VpnHood.ZUdpTrafficTest/Speedometer.cs | 68 ++++++++++++++++ VpnHood.ZUdpTrafficTest/UdpEchoClient.cs | 77 +++++++++++++++++++ VpnHood.ZUdpTrafficTest/UdpEchoServer.cs | 76 ++++++++++++++++++ .../VpnHood.ZUdpTrafficTest.csproj | 15 ++++ VpnHood.sln | 6 ++ VpnHood.sln.DotSettings | 1 + 13 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 VpnHood.ZUdpTrafficTest/Program.cs create mode 100644 VpnHood.ZUdpTrafficTest/Properties/launchSettings.json create mode 100644 VpnHood.ZUdpTrafficTest/Speedometer.cs create mode 100644 VpnHood.ZUdpTrafficTest/UdpEchoClient.cs create mode 100644 VpnHood.ZUdpTrafficTest/UdpEchoServer.cs create mode 100644 VpnHood.ZUdpTrafficTest/VpnHood.ZUdpTrafficTest.csproj diff --git a/CHANGELOG.md b/CHANGELOG.md index 410740f4a..93b3d44d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # v2.6.345 ### Client -Update: Improve stability when using no UDP mode +* Update: Improve stability when using no UDP mode ### Server -Feature: Improve stability by adding lifetime to TcpDatagarmChannel -Fix: IpV6 detection +* Feature: Improve stability by adding lifetime to TcpDatagarmChannel +* Fix: IpV6 detection # v2.6.342 ### Client diff --git a/Pub/PublishToGitHub.ps1 b/Pub/PublishToGitHub.ps1 index 1ed99ff15..67f8025dd 100644 --- a/Pub/PublishToGitHub.ps1 +++ b/Pub/PublishToGitHub.ps1 @@ -8,7 +8,7 @@ $changeLog | Out-File -FilePath "$solutionDir/CHANGELOG.md" -Encoding utf8 -For # create release note $releaseNote = $text -replace "# Upcoming", "$versionTag`n"; -$releaseNote = $releaseNote -replace "# $versionTag", "$versionTag"; # remove version hash +$releaseNote = $releaseNote -replace "# $versionTag`n", ""; # remove version hash $releaseNote = $releaseNote.SubString(0, $releaseNote.IndexOf("`n# ")); # $releaseNote += "To see a list of all changes visit: [Changelog](https://github.com/vpnhood/VpnHood/blob/main/CHANGELOG.md)"; $releaseNote | Out-File -FilePath "$packagesRootDir/ReleaseNote.txt" -Encoding utf8 -Force -NoNewline; diff --git a/VpnHood.Client.App.Win/Properties/launchSettings.json b/VpnHood.Client.App.Win/Properties/launchSettings.json index 04c2d3259..717429745 100644 --- a/VpnHood.Client.App.Win/Properties/launchSettings.json +++ b/VpnHood.Client.App.Win/Properties/launchSettings.json @@ -3,7 +3,7 @@ "VpnHood.Client.App.Net": { "commandName": "Executable", "executablePath": "dotnet", - "commandLineArgs": "VpnHoodClient.dll" + "commandLineArgs": "VpnHoodClient.dll server" } } } \ No newline at end of file diff --git a/VpnHood.Server/SessionManager.cs b/VpnHood.Server/SessionManager.cs index 71d9891bc..6e5e4b546 100644 --- a/VpnHood.Server/SessionManager.cs +++ b/VpnHood.Server/SessionManager.cs @@ -23,6 +23,7 @@ public class SessionManager : IDisposable, IAsyncDisposable, IJob private readonly IAccessServer _accessServer; private readonly SocketFactory _socketFactory; private readonly ITracker? _tracker; + private bool _disposed; public JobSection JobSection { get; } = new(TimeSpan.FromMinutes(10)); public string ServerVersion { get; } @@ -49,7 +50,6 @@ public void Dispose() DisposeAsync().GetAwaiter().GetResult(); } - private bool _disposed; public async ValueTask DisposeAsync() { if (_disposed) return; diff --git a/VpnHood.Tunneling/TcpDatagramChannel.cs b/VpnHood.Tunneling/TcpDatagramChannel.cs index 279e5694d..a2b835de1 100644 --- a/VpnHood.Tunneling/TcpDatagramChannel.cs +++ b/VpnHood.Tunneling/TcpDatagramChannel.cs @@ -179,6 +179,7 @@ public Task RunJob() return Task.CompletedTask; } + public void Dispose() { if (_disposed) return; diff --git a/VpnHood.ZUdpTrafficTest/Program.cs b/VpnHood.ZUdpTrafficTest/Program.cs new file mode 100644 index 000000000..aaff36255 --- /dev/null +++ b/VpnHood.ZUdpTrafficTest/Program.cs @@ -0,0 +1,46 @@ +using System.Net; + +namespace VpnHood.ZUdpTrafficTest +{ + internal class Program + { + public static async Task Main(string[] args) + { + if (args.Length == 0 || args.Any(x => x is "/?" or "-?" or "--help")) + { + Console.WriteLine("Usage:"); + Console.WriteLine("udptester client serverEndPoint dataLength echoCount"); + Console.WriteLine("udptester server "); + return; + } + + if (args.Any(x => x == "self")) + { + var udpEchoServer = new UdpEchoServer(); + _ = udpEchoServer.StartAsync(); + var udpEchoClient = new UdpEchoClient(); + await udpEchoClient.StartAsync(udpEchoServer.LocalEndPoint!, 1000, 1000); + } + else if (args[0] == "client") + { + var serverEp = IPEndPoint.Parse(args[1]); + var dataLen = args.Length > 2 ? int.Parse(args[2]) : 1000; + var echoCount = args.Length > 3 ? int.Parse(args[3]) : 1; + var udpEchoClient = new UdpEchoClient(); + await udpEchoClient.StartAsync(serverEp, echoCount, dataLen); + } + + else if (args[0] == "server") + { + var serverEp = args.Length > 1 ? IPEndPoint.Parse(args[1]) : null; + var udpEchoServer = new UdpEchoServer(serverEp); + await udpEchoServer.StartAsync(); + } + + else + { + Console.WriteLine("first parameter can be client or server."); + } + } + } +} \ No newline at end of file diff --git a/VpnHood.ZUdpTrafficTest/Properties/launchSettings.json b/VpnHood.ZUdpTrafficTest/Properties/launchSettings.json new file mode 100644 index 000000000..7672d20ff --- /dev/null +++ b/VpnHood.ZUdpTrafficTest/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "VpnHood.ZUdpTrafficTest": { + "commandName": "Project", + "commandLineArgs2": "self", + "commandLineArgs": "client 65.21.178.152:5050 500 100" + } + } +} \ No newline at end of file diff --git a/VpnHood.ZUdpTrafficTest/Speedometer.cs b/VpnHood.ZUdpTrafficTest/Speedometer.cs new file mode 100644 index 000000000..d1e099ecd --- /dev/null +++ b/VpnHood.ZUdpTrafficTest/Speedometer.cs @@ -0,0 +1,68 @@ +using System.Diagnostics; +using VpnHood.Common.JobController; +using VpnHood.Common.Utils; + +namespace VpnHood.ZUdpTrafficTest; + +public class Speedometer : IJob +{ + private readonly string _name; + private readonly Stopwatch _stopwatch = new(); + private readonly object _lockObject = new(); + + private long _succeededCount; + private long _failedCount; + private long _transferSize; + private long _lastTransferSize; + private long _lastSucceededCount; + + public JobSection JobSection { get; } + + public Speedometer(string name, TimeSpan? interval = null) + { + _name = name; + _stopwatch.Start(); + JobSection = new JobSection(interval ?? TimeSpan.FromSeconds(2)); + JobRunner.Default.Add(this); + } + + public void AddSucceeded(byte[] buffer) + { + lock (_lockObject) + { + _transferSize += buffer.Length; + _succeededCount++; + } + } + + public void AddFailed() + { + lock (_lockObject) + _failedCount++; + } + + public void Report() + { + lock (_lockObject) + { + if (_stopwatch.ElapsedMilliseconds == 0) return; + + var curTransferSize = _transferSize - _lastTransferSize; + var curSucceededCount = _succeededCount - _lastSucceededCount; + Console.WriteLine(_name + ": " + + $"Transfer: {Util.FormatBits(1000 * curTransferSize / _stopwatch.ElapsedMilliseconds)}, " + + $"Success: {curSucceededCount}, TotalSucceeded: {_succeededCount}, TotalFailed: {_failedCount}, TotalBytes: {Util.FormatBytes(_transferSize)}"); + + _lastTransferSize = _transferSize; + _lastSucceededCount = _succeededCount; + _stopwatch.Restart(); + } + } + + public Task RunJob() + { + Report(); + return Task.CompletedTask; + } + +} \ No newline at end of file diff --git a/VpnHood.ZUdpTrafficTest/UdpEchoClient.cs b/VpnHood.ZUdpTrafficTest/UdpEchoClient.cs new file mode 100644 index 000000000..5f8f9dbd7 --- /dev/null +++ b/VpnHood.ZUdpTrafficTest/UdpEchoClient.cs @@ -0,0 +1,77 @@ +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace VpnHood.ZUdpTrafficTest; + +public class UdpEchoClient +{ + private readonly UdpClient _udpClient = new(AddressFamily.InterNetwork); + private readonly Speedometer _sendSpeedometer = new("Sender"); + private readonly Speedometer _receivedSpeedometer = new("Receiver"); + + public UdpEchoClient() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + _udpClient.Client.IOControl(-1744830452, new byte[] { 0 }, new byte[] { 0 }); + } + + private static bool CompareBuffer(byte[] buffer1, byte[] buffer2, int length) + { + for (var i = 0; i < length; i++) + if (buffer1[i] != buffer2[i]) + return false; + + return false; + } + + public async Task StartAsync(IPEndPoint serverEp, int echoCount, int bufferSize, int timeout = 3000) + { + Console.WriteLine($"Sending udp packets to {serverEp}... EchoCount: {echoCount}, BufferSize: {bufferSize}, Timeout: {timeout}"); + + bufferSize += 8; + var buffer = new byte[bufferSize]; + new Random().NextBytes(buffer); + + for (var i = 0; ; i++) + { + //send buffer + Array.Copy(BitConverter.GetBytes(i), 0, buffer, 0, 4); + Array.Copy(BitConverter.GetBytes(echoCount), 0, buffer, 4, 4); + var res = await _udpClient.SendAsync(buffer, serverEp, CancellationToken.None); + if (res!= buffer.Length) + { + _sendSpeedometer.AddFailed(); + Console.WriteLine("Could not send all data."); + } + _sendSpeedometer.AddSucceeded(buffer); + + // wait for buffer + for (var j = 0; j < echoCount; j++) + { + try + { + using var cancellationTokenSource = new CancellationTokenSource(timeout); + var udpResult = await _udpClient.ReceiveAsync(cancellationTokenSource.Token); + var resBuffer = udpResult.Buffer; + var packetNumber = BitConverter.ToInt32(buffer, 0); + if (packetNumber != i || resBuffer.Length != buffer.Length || CompareBuffer(buffer, resBuffer, 8)) + { + j--; + Console.WriteLine("Invalid data received"); + continue; + } + + _receivedSpeedometer.AddSucceeded(buffer); + } + catch (OperationCanceledException) + { + Console.WriteLine("A packet loss!"); + _receivedSpeedometer.AddFailed(); + break; + } + } + } + } + +} \ No newline at end of file diff --git a/VpnHood.ZUdpTrafficTest/UdpEchoServer.cs b/VpnHood.ZUdpTrafficTest/UdpEchoServer.cs new file mode 100644 index 000000000..439f3728f --- /dev/null +++ b/VpnHood.ZUdpTrafficTest/UdpEchoServer.cs @@ -0,0 +1,76 @@ +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace VpnHood.ZUdpTrafficTest; + +public class UdpEchoServer +{ + private readonly UdpClient _udpClient; + private readonly Speedometer _sendSpeedometer = new("Sender"); + private readonly Speedometer _receivedSpeedometer = new("Receiver"); + + public UdpEchoServer(IPEndPoint? serverEp = null) + { + serverEp ??= new IPEndPoint(IPAddress.Loopback, 59090); + _udpClient = new UdpClient(serverEp); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + _udpClient.Client.IOControl(-1744830452, new byte[] { 0 }, new byte[] { 0 }); + + } + + public IPEndPoint? LocalEndPoint => (IPEndPoint?)_udpClient.Client.LocalEndPoint; + + public async Task StartAsync() + { + Console.WriteLine($"Starting server on {_udpClient.Client.LocalEndPoint}. Waiting for packet..."); + + while (true) + { + // receiving + var udpResult = await _udpClient.ReceiveAsync(); + var buffer = udpResult.Buffer; + if (buffer.Length < 8) + { + _receivedSpeedometer.AddFailed(); + continue; + } + + _receivedSpeedometer.AddSucceeded(buffer); + + // saving + var echoCount = BitConverter.ToInt32(buffer, 4); + for (var i = 0; i < echoCount; i++) + { + await _udpClient.SendAsync(buffer, buffer.Length, udpResult.RemoteEndPoint); + _sendSpeedometer.AddSucceeded(buffer); + } + } + } + + public void Start() + { + while (true) + { + // receiving + IPEndPoint? remoteEp = null; + var buffer = _udpClient.Receive(ref remoteEp); + if (buffer.Length < 8) + { + _receivedSpeedometer.AddFailed(); + continue; + } + + // saving + _receivedSpeedometer.AddSucceeded(buffer); + var echoCount = BitConverter.ToInt32(buffer, 4); + for (var i = 0; i < echoCount; i++) + { + _udpClient.Send(buffer); + _sendSpeedometer.AddSucceeded(buffer); + } + } + } +} \ No newline at end of file diff --git a/VpnHood.ZUdpTrafficTest/VpnHood.ZUdpTrafficTest.csproj b/VpnHood.ZUdpTrafficTest/VpnHood.ZUdpTrafficTest.csproj new file mode 100644 index 000000000..e680e1cce --- /dev/null +++ b/VpnHood.ZUdpTrafficTest/VpnHood.ZUdpTrafficTest.csproj @@ -0,0 +1,15 @@ + + + + Exe + net7.0 + enable + enable + udptester + + + + + + + diff --git a/VpnHood.sln b/VpnHood.sln index 4a97322b1..e97ee754a 100644 --- a/VpnHood.sln +++ b/VpnHood.sln @@ -50,6 +50,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VpnHood.Samples.SimpleClien EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VpnHood.Samples.SimpleClient.Win", "Samples\VpnHood.Samples.SimpleClient.Win\VpnHood.Samples.SimpleClient.Win.csproj", "{1247900C-916A-426B-8733-D43B17FAA694}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VpnHood.ZUdpTrafficTest", "VpnHood.ZUdpTrafficTest\VpnHood.ZUdpTrafficTest.csproj", "{8DF23016-35C1-41AA-94C5-B0D0A5B96F15}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -124,6 +126,10 @@ Global {1247900C-916A-426B-8733-D43B17FAA694}.Debug|Any CPU.Build.0 = Debug|Any CPU {1247900C-916A-426B-8733-D43B17FAA694}.Release|Any CPU.ActiveCfg = Release|Any CPU {1247900C-916A-426B-8733-D43B17FAA694}.Release|Any CPU.Build.0 = Release|Any CPU + {8DF23016-35C1-41AA-94C5-B0D0A5B96F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8DF23016-35C1-41AA-94C5-B0D0A5B96F15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8DF23016-35C1-41AA-94C5-B0D0A5B96F15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8DF23016-35C1-41AA-94C5-B0D0A5B96F15}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/VpnHood.sln.DotSettings b/VpnHood.sln.DotSettings index ff30a3f97..e0df87c8c 100644 --- a/VpnHood.sln.DotSettings +++ b/VpnHood.sln.DotSettings @@ -39,6 +39,7 @@ True True True + True True True From 3f033123d502fc7deba6e3ab78f8199c4c6929c7 Mon Sep 17 00:00:00 2001 From: Trudy Date: Thu, 2 Feb 2023 19:43:12 -0800 Subject: [PATCH 6/7] update udp tester --- VpnHood.Tunneling/Tunnel.cs | 4 +- .../Properties/launchSettings.json | 2 +- VpnHood.ZUdpTrafficTest/UdpEchoClient2.cs | 79 +++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 VpnHood.ZUdpTrafficTest/UdpEchoClient2.cs diff --git a/VpnHood.Tunneling/Tunnel.cs b/VpnHood.Tunneling/Tunnel.cs index 091db9f5f..004708276 100644 --- a/VpnHood.Tunneling/Tunnel.cs +++ b/VpnHood.Tunneling/Tunnel.cs @@ -53,7 +53,7 @@ public int TcpProxyChannelCount { get { - lock (_closePendingChannels) + lock (_channelListLock) return _tcpProxyChannels.Count; } } @@ -273,7 +273,7 @@ public async Task SendPacket(IPPacket[] ipPackets) { var dateTime = FastDateTime.Now; if (_disposed) throw new ObjectDisposedException(nameof(Tunnel)); - + // waiting for a space in the packetQueue; the Inconsistently is not important. synchronization may lead to dead-lock // ReSharper disable once InconsistentlySynchronizedField while (_packetQueue.Count > _maxQueueLength) diff --git a/VpnHood.ZUdpTrafficTest/Properties/launchSettings.json b/VpnHood.ZUdpTrafficTest/Properties/launchSettings.json index 7672d20ff..b763ca3b0 100644 --- a/VpnHood.ZUdpTrafficTest/Properties/launchSettings.json +++ b/VpnHood.ZUdpTrafficTest/Properties/launchSettings.json @@ -3,7 +3,7 @@ "VpnHood.ZUdpTrafficTest": { "commandName": "Project", "commandLineArgs2": "self", - "commandLineArgs": "client 65.21.178.152:5050 500 100" + "commandLineArgs": "client 65.21.178.152:5050 300 1" } } } \ No newline at end of file diff --git a/VpnHood.ZUdpTrafficTest/UdpEchoClient2.cs b/VpnHood.ZUdpTrafficTest/UdpEchoClient2.cs new file mode 100644 index 000000000..2406541f4 --- /dev/null +++ b/VpnHood.ZUdpTrafficTest/UdpEchoClient2.cs @@ -0,0 +1,79 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace VpnHood.ZUdpTrafficTest; + +public class UdpEchoClient2 +{ + private readonly UdpClient _udpClient = new(AddressFamily.InterNetwork); + //private readonly Speedometer _sendSpeedometer = new("Sender"); + //private readonly Speedometer _receivedSpeedometer = new("Receiver"); + + public UdpEchoClient2() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + _udpClient.Client.IOControl(-1744830452, new byte[] { 0 }, new byte[] { 0 }); + } + + private static bool CompareBuffer(byte[] buffer1, byte[] buffer2, int length) + { + for (var i = 0; i < length; i++) + if (buffer1[i] != buffer2[i]) + return false; + + return false; + } + public async Task StartAsync(IPEndPoint serverEp, int echoCount, int bufferSize, int timeout = 3000) + { + var task1 = StartAsync2(serverEp, echoCount, bufferSize, timeout); + var task2 = ReceiveAsync(serverEp, echoCount, bufferSize, timeout); + await Task.WhenAll(task1, task2); + } + + private async Task StartAsync2(IPEndPoint serverEp, int echoCount, int bufferSize, int timeout = 3000) + { + Console.WriteLine($"Sending udp packets to {serverEp}... EchoCount: {echoCount}, BufferSize: {bufferSize}, Timeout: {timeout}"); + + bufferSize += 8; + var buffer = new byte[bufferSize]; + new Random().NextBytes(buffer); + + while (true) + { + for (var i = 0; i<3000 ; i++) + { + //send buffer + Array.Copy(BitConverter.GetBytes(i), 0, buffer, 0, 4); + Array.Copy(BitConverter.GetBytes(1), 0, buffer, 4, 4); + Array.Copy(BitConverter.GetBytes(Environment.TickCount), 0, buffer, 8, 4); + var res = await _udpClient.SendAsync(buffer, serverEp, CancellationToken.None); + if (res != buffer.Length) + { + Console.WriteLine("Could not send all data."); + } + + } + await Task.Delay(1000); + } + } + + public async Task ReceiveAsync(IPEndPoint serverEp, int echoCount, int bufferSize, int timeout = 3000) + { + // wait for buffer + int max_count = 1000; + while (true) + { + int delay_sum = 0; + for( int i=0; i Date: Fri, 3 Feb 2023 13:56:24 -0800 Subject: [PATCH 7/7] Publish v2.6.346 --- CHANGELOG.md | 2 +- Pub/Version.json | 6 +++--- VpnHood.Client.App.Android/Properties/AndroidManifest.xml | 2 +- VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj | 4 ++-- VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj | 4 ++-- VpnHood.Client.App/VpnHood.Client.App.csproj | 4 ++-- .../VpnHood.Client.Device.WinDivert.csproj | 4 ++-- VpnHood.Client.Device/VpnHood.Client.Device.csproj | 4 ++-- VpnHood.Client/VpnHood.Client.csproj | 4 ++-- VpnHood.Common/VpnHood.Common.csproj | 4 ++-- VpnHood.Server.Access/VpnHood.Server.Access.csproj | 4 ++-- VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj | 4 ++-- VpnHood.Server/VpnHood.Server.csproj | 4 ++-- VpnHood.Tunneling/VpnHood.Tunneling.csproj | 4 ++-- VpnHood.ZTest/TestHelper.cs | 3 +-- VpnHood.ZTest/Tests/ServerTest.cs | 2 +- 16 files changed, 29 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b3d44d8..628f638d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v2.6.345 +# v2.6.346 ### Client * Update: Improve stability when using no UDP mode diff --git a/Pub/Version.json b/Pub/Version.json index 88b17fb2c..a947d4dc8 100644 --- a/Pub/Version.json +++ b/Pub/Version.json @@ -1,8 +1,8 @@ { "Major": 2, "Minor": 6, - "Build": 345, - "BumpTime": "2023-02-02T02:59:20.0744636Z", - "Prerelease": true, + "Build": 346, + "BumpTime": "2023-02-03T21:51:41.0629987Z", + "Prerelease": false, "DeprecatedVersion": "2.0.0" } diff --git a/VpnHood.Client.App.Android/Properties/AndroidManifest.xml b/VpnHood.Client.App.Android/Properties/AndroidManifest.xml index c46adb3d4..a054e2deb 100644 --- a/VpnHood.Client.App.Android/Properties/AndroidManifest.xml +++ b/VpnHood.Client.App.Android/Properties/AndroidManifest.xml @@ -1,5 +1,5 @@  - + diff --git a/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj b/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj index ac3264a85..052fadaac 100644 --- a/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj +++ b/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj @@ -12,8 +12,8 @@ VpnHood.png Tiny internal webserver to server your single-page application (SPA). You need this only if you want to create a UI for your VpnHood client by single-page application (SPA). VpnHood.Client.App.UI - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj b/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj index b107e5706..aa858d4c2 100644 --- a/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj +++ b/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj @@ -19,8 +19,8 @@ VpnHood.png VpnHood.Client.App.Win - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable 11 diff --git a/VpnHood.Client.App/VpnHood.Client.App.csproj b/VpnHood.Client.App/VpnHood.Client.App.csproj index 4b38b9e02..efece8e37 100644 --- a/VpnHood.Client.App/VpnHood.Client.App.csproj +++ b/VpnHood.Client.App/VpnHood.Client.App.csproj @@ -11,8 +11,8 @@ https://github.com/vpnhood/vpnhood Readymade Vpn App skeleton for VpnHood clients. You just need to create a UI on it. VpnHood.Client.App - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj b/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj index fb1ab1c48..19b094d9e 100644 --- a/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj +++ b/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj @@ -9,10 +9,10 @@ https://github.com/vpnhood/vpnhood VpnHood.png VpnHood client device provider for Windows using WinDivert. - 2.6.345 + 2.6.346 VpnHood.Client.Device.WinDivert 1.1.226 - 2.6.345 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Client.Device/VpnHood.Client.Device.csproj b/VpnHood.Client.Device/VpnHood.Client.Device.csproj index 9d02be984..54ac842f4 100644 --- a/VpnHood.Client.Device/VpnHood.Client.Device.csproj +++ b/VpnHood.Client.Device/VpnHood.Client.Device.csproj @@ -14,8 +14,8 @@ VpnHood.Client.Device VpnHood.Client.Device - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Client/VpnHood.Client.csproj b/VpnHood.Client/VpnHood.Client.csproj index 0825b8cc0..6333f267e 100644 --- a/VpnHood.Client/VpnHood.Client.csproj +++ b/VpnHood.Client/VpnHood.Client.csproj @@ -13,8 +13,8 @@ 2022 VpnHood VpnHood.Client VPN VpnClient Proxy - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Common/VpnHood.Common.csproj b/VpnHood.Common/VpnHood.Common.csproj index 4e9013391..25922e58a 100644 --- a/VpnHood.Common/VpnHood.Common.csproj +++ b/VpnHood.Common/VpnHood.Common.csproj @@ -12,8 +12,8 @@ VpnHood.Common VpnHood.png VpnHood Common Library is shared among all other VpnHood modules. - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Server.Access/VpnHood.Server.Access.csproj b/VpnHood.Server.Access/VpnHood.Server.Access.csproj index 5ff13cf67..300f6ef86 100644 --- a/VpnHood.Server.Access/VpnHood.Server.Access.csproj +++ b/VpnHood.Server.Access/VpnHood.Server.Access.csproj @@ -12,8 +12,8 @@ VpnHood.Server.Access VpnHood.png Stores, and retrieves end users' access and usage. Provides required interfaces and classes to use or create an access server and accounting. - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj b/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj index ec4431142..cfb5d6c60 100644 --- a/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj +++ b/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj @@ -12,8 +12,8 @@ https://github.com/vpnhood/vpnhood LGPL-2.1-only VpnHood.png - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Server/VpnHood.Server.csproj b/VpnHood.Server/VpnHood.Server.csproj index e9ea9ba1b..a806976ce 100644 --- a/VpnHood.Server/VpnHood.Server.csproj +++ b/VpnHood.Server/VpnHood.Server.csproj @@ -13,8 +13,8 @@ VpnHood.png The core of VpnHood server. It can listen and accept connections from VpnHood clients. VpnHood.Server - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.Tunneling/VpnHood.Tunneling.csproj b/VpnHood.Tunneling/VpnHood.Tunneling.csproj index 01a51e24a..5b37a5e70 100644 --- a/VpnHood.Tunneling/VpnHood.Tunneling.csproj +++ b/VpnHood.Tunneling/VpnHood.Tunneling.csproj @@ -15,8 +15,8 @@ VpnHood.png VpnHood.Tunneling Provides tunnelling classes and protocols shared between VpnHood.Client and VpnHood.Server. - 2.6.345 - 2.6.345 + 2.6.346 + 2.6.346 $([System.DateTime]::Now.ToString("yyyy.M.d.HHmm")) enable latest diff --git a/VpnHood.ZTest/TestHelper.cs b/VpnHood.ZTest/TestHelper.cs index 87e3e6c47..7c81c3dbd 100644 --- a/VpnHood.ZTest/TestHelper.cs +++ b/VpnHood.ZTest/TestHelper.cs @@ -98,7 +98,6 @@ public static Task WaitForClientStateAsync(VpnHoodClient client, ClientState cli return AssertEqualsWait(clientState, () => client.State, "Client state didn't reach to expected value.", timeout); } - private static PingReply SendPing(Ping? ping = null, IPAddress? ipAddress = null, int timeout = 3000) { using var pingT = new Ping(); @@ -437,7 +436,7 @@ public static async Task AssertEqualsWait(T obj, TValue? expectedValu { await WaitForValue(obj, expectedValue, valueFactory, timeout); - if (message!=null) + if (message != null) Assert.AreEqual(expectedValue, valueFactory(obj), message); else Assert.AreEqual(expectedValue, valueFactory(obj)); diff --git a/VpnHood.ZTest/Tests/ServerTest.cs b/VpnHood.ZTest/Tests/ServerTest.cs index 7bae37091..5272fe532 100644 --- a/VpnHood.ZTest/Tests/ServerTest.cs +++ b/VpnHood.ZTest/Tests/ServerTest.cs @@ -174,7 +174,7 @@ public async Task Server_should_close_session_if_it_does_not_exist_in_access_ser try { await TestHelper.Test_HttpsAsync(); - Assert.Fail("Must fail. Session does not exist any more."); + Assert.Fail("Must fail. Session should not exist any more."); } catch { /*ignored*/ }