diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbf78585..9bf37f3b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# v2.6.327 +### Server +* Fix: Error on centos +* Feature: Report CPU usage to access server +* Feature: Add TcpConnectWait control +* Feature: Add TcpChannelCount control + # v2.6.326 ### Client * Feature: Windows: Compile as Win-x64. NET runtime is not required anymore. diff --git a/Pub/PublishApps.ps1 b/Pub/PublishApps.ps1 index b74e8ce46..ce1ca041c 100644 --- a/Pub/PublishApps.ps1 +++ b/Pub/PublishApps.ps1 @@ -7,7 +7,6 @@ param( [Parameter(Mandatory=$true)][object]$distribute ); -$bump = $bump -eq "1"; $nugets = $nugets -eq "1"; $android = $android -eq "1"; $distribute = $distribute -eq "1"; diff --git a/Pub/PublishToGitHub.ps1 b/Pub/PublishToGitHub.ps1 index 69e36dce5..de954396a 100644 --- a/Pub/PublishToGitHub.ps1 +++ b/Pub/PublishToGitHub.ps1 @@ -48,7 +48,7 @@ gh release create "$versionTag" ` -F $releaseRootDir/ReleaseNote.txt ` $releaseClientDir/android/VpnHoodClient-Android.apk ` $releaseClientDir/windows/VpnHoodClient-win-x64.exe ` - $releaseClientDir/windows/VpnHoodClient-win.txt ` + $packagesRootDir/../Latest/Client/windows/VpnHoodClient-win.txt ` $releaseClientDir/windows/VpnHoodClient-win-x64.txt ` $releaseServerDir/linux-x64/VpnHoodServer-linux-x64.json ` $releaseServerDir/linux-x64/VpnHoodServer-linux-x64.sh ` diff --git a/Pub/Version.json b/Pub/Version.json index 97a98f19c..e22be8035 100644 --- a/Pub/Version.json +++ b/Pub/Version.json @@ -1,7 +1,7 @@ { "Major": 2, "Minor": 6, - "Build": 326, - "BumpTime": "2022-12-15T04:41:17.2014844Z", + "Build": 328, + "BumpTime": "2022-12-19T10:20:18.3580504Z", "Prerelease": false } diff --git a/Samples/VpnHood.Samples.SimpleClient.Droid/VpnHood.Samples.SimpleClient.Droid.csproj b/Samples/VpnHood.Samples.SimpleClient.Droid/VpnHood.Samples.SimpleClient.Droid.csproj index 79a700b48..2f3688fd3 100644 --- a/Samples/VpnHood.Samples.SimpleClient.Droid/VpnHood.Samples.SimpleClient.Droid.csproj +++ b/Samples/VpnHood.Samples.SimpleClient.Droid/VpnHood.Samples.SimpleClient.Droid.csproj @@ -108,10 +108,10 @@ - 2.5.324 + 2.6.327 - 2.5.324 + 2.6.327 diff --git a/Samples/VpnHood.Samples.SimpleClient.Win/VpnHood.Samples.SimpleClient.Win.csproj b/Samples/VpnHood.Samples.SimpleClient.Win/VpnHood.Samples.SimpleClient.Win.csproj index bc07e2efe..1009381a1 100644 --- a/Samples/VpnHood.Samples.SimpleClient.Win/VpnHood.Samples.SimpleClient.Win.csproj +++ b/Samples/VpnHood.Samples.SimpleClient.Win/VpnHood.Samples.SimpleClient.Win.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/VpnHood.Client.App.Android/Properties/AndroidManifest.xml b/VpnHood.Client.App.Android/Properties/AndroidManifest.xml index be55375c8..96c071c07 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 9dc487db0..f5de5eed2 100644 --- a/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj +++ b/VpnHood.Client.App.UI/VpnHood.Client.App.UI.csproj @@ -12,9 +12,9 @@ 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.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 enable latest diff --git a/VpnHood.Client.App.Win.Setup/VpnHood.Client.App.Win.Setup.back (1).aip b/VpnHood.Client.App.Win.Setup/VpnHood.Client.App.Win.Setup.back (1).aip new file mode 100644 index 000000000..8f8b54d72 --- /dev/null +++ b/VpnHood.Client.App.Win.Setup/VpnHood.Client.App.Win.Setup.back (1).aip @@ -0,0 +1,1860 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj b/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj index 088a42173..8c33f214e 100644 --- a/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj +++ b/VpnHood.Client.App.Win/VpnHood.Client.App.Win.csproj @@ -19,11 +19,11 @@ VpnHood.png VpnHood.Client.App.Win - 2.6.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 enable - latest + 11 false 7.0 False diff --git a/VpnHood.Client.App.Win/WebViewWindow.cs b/VpnHood.Client.App.Win/WebViewWindow.cs index 6a3fe6ec9..1281fd63a 100644 --- a/VpnHood.Client.App.Win/WebViewWindow.cs +++ b/VpnHood.Client.App.Win/WebViewWindow.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Drawing; +using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; @@ -69,6 +70,11 @@ private void InitWebView(Uri url, string dataFolderPath) ((System.ComponentModel.ISupportInitialize)(_webView)).EndInit(); } + public Task EnsureCoreWebView2Async() + { + return _webView.EnsureCoreWebView2Async(); + } + private void UpdatePosition() { // body @@ -102,7 +108,7 @@ private void WebView_CoreWebView2InitializationCompleted(object? sender, EventAr { if (sender is WebView2 webView) { - webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; + webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true; webView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested; } } diff --git a/VpnHood.Client.App.Win/WinApp.cs b/VpnHood.Client.App.Win/WinApp.cs index 47d00eb60..03b5006f3 100644 --- a/VpnHood.Client.App.Win/WinApp.cs +++ b/VpnHood.Client.App.Win/WinApp.cs @@ -16,9 +16,9 @@ namespace VpnHood.Client.App; public class WinApp : AppBaseNet { private const string FileNameAppCommand = "appcommand"; - private readonly System.Windows.Forms.Timer _uiTimer; + private readonly Timer _uiTimer; private DateTime _lastUpdateTime = DateTime.MinValue; - private System.Windows.Forms.NotifyIcon? _notifyIcon; + private NotifyIcon? _notifyIcon; private readonly CommandListener _commandListener; private WebViewWindow? _webViewWindow; private string AppLocalDataPath { get; } @@ -26,7 +26,7 @@ public class WinApp : AppBaseNet public WinApp() : base("VpnHood") { //init timer - _uiTimer = new System.Windows.Forms.Timer + _uiTimer = new Timer { Interval = 1000 }; @@ -133,11 +133,21 @@ public void InitWevView() { if (!WebViewWindow.IsInstalled) return; _webViewWindow = new WebViewWindow(new Uri(VhAppUi.Url), Path.Combine(VhApp.AppDataFolderPath, "Temp")); + _webViewWindow.EnsureCoreWebView2Async() + .ContinueWith(x => + { + if (x.IsFaulted) + { + _webViewWindow?.Close(); + _webViewWindow = null; + } + }); //_webViewWindow.Init("https://www.google.com", @"C:\Users\Developer\AppData\Local\VpnHood\Temp\dd").Wait(); } catch (Exception ex) { + _webViewWindow = null; VhLogger.Instance.LogWarning($"Could not use WebView. Using the default browser. {ex.Message}"); } } @@ -207,17 +217,17 @@ public void OpenMainWindow() private void InitNotifyIcon() { - _notifyIcon = new System.Windows.Forms.NotifyIcon + _notifyIcon = new NotifyIcon { Icon = Resource.VpnHoodIcon }; _notifyIcon.MouseClick += (_, e) => { - if (e.Button == System.Windows.Forms.MouseButtons.Left) + if (e.Button == MouseButtons.Left) OpenMainWindow(); }; - var menu = new System.Windows.Forms.ContextMenuStrip(); + var menu = new ContextMenuStrip(); menu.Items.Add(AppUiResource.Open, null, (_, _) => OpenMainWindow()); menu.Items.Add("-"); @@ -256,7 +266,7 @@ private void ConnectMenuItem_Click(object? sender, EventArgs e) private void Menu_Opening(object? sender, CancelEventArgs e) { - var menu = (System.Windows.Forms.ContextMenuStrip)sender!; + var menu = (ContextMenuStrip)sender!; menu.Items["connect"].Enabled = VhApp.IsIdle; menu.Items["disconnect"].Enabled = !VhApp.IsIdle && VhApp.State.ConnectionState != AppConnectionState.Disconnecting; diff --git a/VpnHood.Client.App/VpnHood.Client.App.csproj b/VpnHood.Client.App/VpnHood.Client.App.csproj index 4cc2d0646..a9bce033e 100644 --- a/VpnHood.Client.App/VpnHood.Client.App.csproj +++ b/VpnHood.Client.App/VpnHood.Client.App.csproj @@ -11,9 +11,9 @@ 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.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 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 8780b2c7d..20345c29e 100644 --- a/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj +++ b/VpnHood.Client.Device.WinDivert/VpnHood.Client.Device.WinDivert.csproj @@ -9,11 +9,11 @@ https://github.com/vpnhood/vpnhood VpnHood.png VpnHood client device provider for Windows using WinDivert. - 2.6.326 + 2.6.328 VpnHood.Client.Device.WinDivert 1.1.226 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 enable latest diff --git a/VpnHood.Client.Device/VpnHood.Client.Device.csproj b/VpnHood.Client.Device/VpnHood.Client.Device.csproj index cf237a004..db7117ac9 100644 --- a/VpnHood.Client.Device/VpnHood.Client.Device.csproj +++ b/VpnHood.Client.Device/VpnHood.Client.Device.csproj @@ -14,9 +14,9 @@ VpnHood.Client.Device VpnHood.Client.Device - 2.6.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 enable latest diff --git a/VpnHood.Client/VpnHood.Client.csproj b/VpnHood.Client/VpnHood.Client.csproj index b4c76ffed..c3c58d379 100644 --- a/VpnHood.Client/VpnHood.Client.csproj +++ b/VpnHood.Client/VpnHood.Client.csproj @@ -13,9 +13,9 @@ 2022 VpnHood VpnHood.Client VPN VpnClient Proxy - 2.6.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 enable latest diff --git a/VpnHood.Common/Util.cs b/VpnHood.Common/Util.cs index 6ab46144a..e1930c16a 100644 --- a/VpnHood.Common/Util.cs +++ b/VpnHood.Common/Util.cs @@ -197,4 +197,48 @@ public static string RedactIpAddress(IPAddress ipAddress) return ipAddress.ToString(); } + + public static string FormatBytes(long size) + { + // Get absolute value + if (size >= 0x10000000000) // Terabyte + return ((double)(size >> 30) / 1024).ToString("0.## ") + "TB"; + + if (size >= 0x40000000) // Gigabyte + return ((double)(size >> 20) / 1024).ToString("0.# ") + "GB"; + + if (size >= 0x100000) // Megabyte + return ((double)(size >> 10) / 1024).ToString("0 ") + "MB"; + + if (size >= 1024) // Kilobyte + return ((double)size / 1024).ToString("0 ") + "KB"; + + if (size > 0) // Kilobyte + return size.ToString("0 ") + "B"; + + // Byte + return size.ToString("0"); + } + + [SuppressMessage("ReSharper", "PossibleLossOfFraction")] + public static string FormatBits(long bytes) + { + bytes *= 8; //convertTo bit + + // Get absolute value + if (bytes >= 0x40000000) // Gigabyte + return ((double)(bytes / 0x40000000)).ToString("0.# ") + "Gbps"; + + if (bytes >= 0x100000) // Megabyte + return ((double)(bytes / 0x100000)).ToString("0 ") + "Mbps"; + + if (bytes >= 1024) // Kilobyte + return ((double)(bytes / 1024)).ToString("0 ") + "Kbps"; + + if (bytes > 0) // Kilobyte + return ((double)bytes).ToString("0 ") + "bps"; + + // Byte + return bytes.ToString("0"); + } } \ No newline at end of file diff --git a/VpnHood.Common/VpnHood.Common.csproj b/VpnHood.Common/VpnHood.Common.csproj index 77b80cbaa..ff1ce36c5 100644 --- a/VpnHood.Common/VpnHood.Common.csproj +++ b/VpnHood.Common/VpnHood.Common.csproj @@ -12,9 +12,9 @@ VpnHood.Common VpnHood.png VpnHood Common Library is shared among all other VpnHood modules. - 2.6.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 enable latest diff --git a/VpnHood.Server.Access/ServerInfo.cs b/VpnHood.Server.Access/ServerInfo.cs index bbf3c7f35..e4b75e1cc 100644 --- a/VpnHood.Server.Access/ServerInfo.cs +++ b/VpnHood.Server.Access/ServerInfo.cs @@ -35,7 +35,8 @@ public ServerInfo(Version version, public ServerStatus Status { get; set; } public string? OsInfo { get; set; } public string? OsVersion { get; set; } - public long TotalMemory { get; set; } + public long? TotalMemory { get; set; } public string? MachineName { get; set; } public string? LastError { get; set; } + public int LogicalCoreCount { get; set; } } \ No newline at end of file diff --git a/VpnHood.Server.Access/ServerStatus.cs b/VpnHood.Server.Access/ServerStatus.cs index 2bc21dab2..cae71cf24 100644 --- a/VpnHood.Server.Access/ServerStatus.cs +++ b/VpnHood.Server.Access/ServerStatus.cs @@ -5,7 +5,8 @@ public class ServerStatus public int SessionCount { get; set; } public int TcpConnectionCount { get; set; } public int UdpConnectionCount { get; set; } - public long FreeMemory { get; set; } + public long? AvailableMemory { get; set; } + public int? CpuUsage { get; set; } public long UsedMemory { get; set; } public int ThreadCount { get; set; } public long TunnelSendSpeed { get; set; } diff --git a/VpnHood.Server.Access/VpnHood.Server.Access.csproj b/VpnHood.Server.Access/VpnHood.Server.Access.csproj index 1adb97a61..3919b9dd9 100644 --- a/VpnHood.Server.Access/VpnHood.Server.Access.csproj +++ b/VpnHood.Server.Access/VpnHood.Server.Access.csproj @@ -12,9 +12,9 @@ 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.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 enable latest diff --git a/VpnHood.Server.App.Net/AppSettings.cs b/VpnHood.Server.App.Net/AppSettings.cs index 51e534d58..423f4ba32 100644 --- a/VpnHood.Server.App.Net/AppSettings.cs +++ b/VpnHood.Server.App.Net/AppSettings.cs @@ -12,4 +12,7 @@ public class AppSettings public FileAccessServerOptions? FileAccessServer { get; set; } = new(); public bool IsAnonymousTrackerEnabled { get; set; } = true; public bool IsDiagnoseMode { get; set; } + public int MaxTcpConnectWaitCount { get; set; } = new ServerOptions().MaxTcpConnectWaitCount; + public int MaxTcpChannelCount { get; set; } = new ServerOptions().MaxTcpChannelCount; + public TimeSpan TcpConnectTimeout { get; set; } = new ServerOptions().TcpConnectTimeout; } \ No newline at end of file diff --git a/VpnHood.Server.App.Net/Program.cs b/VpnHood.Server.App.Net/Program.cs index 92747f664..5e937e207 100644 --- a/VpnHood.Server.App.Net/Program.cs +++ b/VpnHood.Server.App.Net/Program.cs @@ -13,7 +13,7 @@ private static void Main(string[] args) } catch (Exception ex) { - Console.WriteLine(ex.Message); + Console.WriteLine(ex); } } } \ No newline at end of file diff --git a/VpnHood.Server.App.Net/Properties/launchSettings.json b/VpnHood.Server.App.Net/Properties/launchSettings.json index a0617b72b..4b3167a0f 100644 --- a/VpnHood.Server.App.Net/Properties/launchSettings.json +++ b/VpnHood.Server.App.Net/Properties/launchSettings.json @@ -1,8 +1,7 @@ { "profiles": { "VpnHood.Server.App.Net": { - "commandName": "Project", - "commandLineArgs": "gen -domain" + "commandName": "Project" }, "WSL 2": { "commandName": "WSL2", diff --git a/VpnHood.Server.App.Net/ServerApp.cs b/VpnHood.Server.App.Net/ServerApp.cs index 0ceee84b5..b9314fb12 100644 --- a/VpnHood.Server.App.Net/ServerApp.cs +++ b/VpnHood.Server.App.Net/ServerApp.cs @@ -2,7 +2,6 @@ using System.IO; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; using McMaster.Extensions.CommandLineUtils; using Microsoft.Extensions.Logging; using NLog; @@ -46,7 +45,7 @@ public ServerApp() : base("VpnHoodServer") : Path.Combine(Directory.GetCurrentDirectory(), FolderNameStorage); Directory.CreateDirectory(storagePath); Directory.SetCurrentDirectory(storagePath); - + // internal folder InternalStoragePath = Path.Combine(storagePath, FolderNameInternal); Directory.CreateDirectory(InternalStoragePath); @@ -181,12 +180,12 @@ private bool IsAnotherInstanceRunning() try { _lockStream = File.OpenWrite(lockFile); - var stream =new StreamWriter(_lockStream, leaveOpen: true); + var stream = new StreamWriter(_lockStream, leaveOpen: true); stream.WriteLine(DateTime.UtcNow); stream.Dispose(); return false; } - catch (IOException) + catch (IOException) { return true; } @@ -223,7 +222,10 @@ private void StartServer(CommandLineApplication cmdApp) Tracker = _googleAnalytics, SystemInfoProvider = systemInfoProvider, SocketFactory = new ServerSocketFactory(), - StoragePath = InternalStoragePath + StoragePath = InternalStoragePath, + TcpConnectTimeout = AppSettings.TcpConnectTimeout, + MaxTcpConnectWaitCount = AppSettings.MaxTcpConnectWaitCount, + MaxTcpChannelCount = AppSettings.MaxTcpChannelCount }); // track diff --git a/VpnHood.Server.App.Net/SystemInformation/LinuxSystemInfoProvider.cs b/VpnHood.Server.App.Net/SystemInformation/LinuxSystemInfoProvider.cs index 8a2eb8f52..1f563c066 100644 --- a/VpnHood.Server.App.Net/SystemInformation/LinuxSystemInfoProvider.cs +++ b/VpnHood.Server.App.Net/SystemInformation/LinuxSystemInfoProvider.cs @@ -1,70 +1,103 @@ -using System; +using Microsoft.Extensions.Logging; +using System; using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Text.RegularExpressions; +using VpnHood.Common.Logging; using VpnHood.Server.SystemInformation; namespace VpnHood.Server.App.SystemInformation; public class LinuxSystemInfoProvider : ISystemInfoProvider { - public SystemInfo GetSystemInfo() + private long? GetMemInfoValue(string key) { - long totalMemory = 0; - long freeMemory = 0; - long buffers = 0; - long cached = 0; + try + { + var meminfo = File.ReadAllText("/proc/meminfo"); + var memTotalLine = meminfo.Split('\n').FirstOrDefault(line => line.StartsWith($"{key}:")); + if (memTotalLine == null) + return null; - string[] memInfoLines = File.ReadAllLines(@"/proc/meminfo"); + var tokenize = memTotalLine.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + if (tokenize.Length < 1) + return null; - MemInfoMatch[] memInfoMatches = - { - new(@"^Buffers:\s+(\d+)", value => buffers = Convert.ToInt64(value)), - new(@"^Cached:\s+(\d+)", value => cached = Convert.ToInt64(value)), - new(@"^MemFree:\s+(\d+)", value => freeMemory = Convert.ToInt64(value)), - new(@"^MemTotal:\s+(\d+)", value => totalMemory = Convert.ToInt64(value)) - }; + return long.Parse(tokenize[1]) * 1000; - foreach (var memInfoLine in memInfoLines) - foreach (var memInfoMatch in memInfoMatches) + } + catch (Exception ex) { - Match match = memInfoMatch.Regex.Match(memInfoLine); - if (match.Groups[1].Success) - { - var value = match.Groups[1].Value; - memInfoMatch.UpdateValue(value); - } + VhLogger.Instance.LogWarning(ex, $"Could not read {key} form /proc/meminfo."); + return null; } - - return new SystemInfo( - totalMemory * 1000, - (freeMemory + cached + buffers) * 1000); } - public string GetOperatingSystemInfo() + private int? GetCpuUsage() { + try + { + // Read the first line of the /proc/stat file + var statLine = File.ReadAllLines("/proc/stat")[0]; + + // Split the line into fields + var fields = statLine + .Split(' ') + .Where(x => x.Trim() != "").ToArray(); + + // Parse the fields + var userTime = long.Parse(fields[1]); + var niceTime = long.Parse(fields[2]); + var systemTime = long.Parse(fields[3]); + var idleTime = long.Parse(fields[4]); + var ioWaitTime = long.Parse(fields[5]); + var irqTime = long.Parse(fields[6]); + var softIrqTime = long.Parse(fields[7]); - var items = File - .ReadAllLines("/etc/os-release") - .Select(line => line.Split('=')) - .ToDictionary(split => split[0], split => split[1]); + // Calculate the total CPU time + var totalTime = userTime + niceTime + systemTime + idleTime + ioWaitTime + irqTime + softIrqTime; - var ret = items["PRETTY_NAME"].Replace("\"", ""); - ret += $", {RuntimeInformation.OSArchitecture}"; + // Calculate the CPU usage + var usage = 100.0 * (totalTime - idleTime) / totalTime; + return (int)usage; - return ret.Trim(); + } + catch (Exception ex) + { + VhLogger.Instance.LogWarning(ex, "Could not read CPU usage form /proc/stat."); + return null; + } } - private class MemInfoMatch + public SystemInfo GetSystemInfo() { - public MemInfoMatch(string pattern, Action update) + var ret = new SystemInfo( + GetOperatingSystemInfo(), + GetMemInfoValue("MemTotal"), + GetMemInfoValue("MemAvailable"), + GetCpuUsage(), + Environment.ProcessorCount + ); + return ret; + } + + public string GetOperatingSystemInfo() + { + string? prettyName = null; + if (File.Exists("/etc/os-release")) { - Regex = new Regex(pattern, RegexOptions.Compiled); - UpdateValue = update; + var items = File + .ReadAllLines("/etc/os-release") + .Select(line => line.Split('=')) + .Where(split => split.Length >= 1 && !string.IsNullOrEmpty(split[0])) + .ToDictionary(split => split[0], split => split[1]); + + if (items.TryGetValue("PRETTY_NAME", out prettyName)) + prettyName = prettyName.Replace("\"", "").Trim(); } - public Regex Regex { get; } - public Action UpdateValue { get; } + if (string.IsNullOrEmpty(prettyName)) prettyName = "Linux"; + prettyName += $", {RuntimeInformation.OSArchitecture}"; + return prettyName.Trim(); } } \ No newline at end of file diff --git a/VpnHood.Server.App.Net/SystemInformation/WinSystemInfoProvider.cs b/VpnHood.Server.App.Net/SystemInformation/WinSystemInfoProvider.cs index ff99ededb..52524ce3c 100644 --- a/VpnHood.Server.App.Net/SystemInformation/WinSystemInfoProvider.cs +++ b/VpnHood.Server.App.Net/SystemInformation/WinSystemInfoProvider.cs @@ -1,6 +1,10 @@ using System; using System.Runtime.InteropServices; using VpnHood.Server.SystemInformation; +using System.Diagnostics; +using VpnHood.Common.Logging; +using Microsoft.Extensions.Logging; +#pragma warning disable CA1416 // ReSharper disable MemberCanBePrivate.Local // ReSharper disable StructCanBeMadeReadOnly @@ -9,46 +13,34 @@ namespace VpnHood.Server.App.SystemInformation; public class WinSystemInfoProvider : ISystemInfoProvider { - [StructLayout(LayoutKind.Sequential)] - private struct PerformanceInformation - { - public readonly int Size; - public readonly IntPtr CommitTotal; - public readonly IntPtr CommitLimit; - public readonly IntPtr CommitPeak; - public readonly IntPtr PhysicalTotal; - public readonly IntPtr PhysicalAvailable; - public readonly IntPtr SystemCache; - public readonly IntPtr KernelTotal; - public readonly IntPtr KernelPaged; - public readonly IntPtr KernelNonPaged; - public readonly IntPtr PageSize; - public readonly int HandlesCount; - public readonly int ProcessCount; - public readonly int ThreadCount; - } + private readonly PerformanceCounter _cpuCounter = new ("Processor", "% Processor Time", "_Total"); + public SystemInfo GetSystemInfo() { - long totalMemory = 0; - long freeMemory = 0; + try + { - if (GetPerformanceInfo(out var pi, Marshal.SizeOf())) + var availableMemoryCounter = new PerformanceCounter("Memory", "Available Bytes"); + var availableMemory = availableMemoryCounter.RawValue; + var totalMemory = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; + var usage = _cpuCounter.NextValue(); + return new SystemInfo(GetOperatingSystemInfo(), + totalMemory, availableMemory, + (int)usage,Environment.ProcessorCount); + + } + catch (Exception ex) { - freeMemory = Convert.ToInt64(pi.PhysicalAvailable.ToInt64() * pi.PageSize.ToInt64()); - totalMemory = Convert.ToInt64(pi.PhysicalTotal.ToInt64() * pi.PageSize.ToInt64()); + VhLogger.Instance.LogWarning(ex, "Could not get SystemInfo."); + return new SystemInfo(GetOperatingSystemInfo(), + 0, 0, + 0, Environment.ProcessorCount); } - return new SystemInfo(totalMemory, freeMemory); } - public string GetOperatingSystemInfo() + public static string GetOperatingSystemInfo() { - return $"{Environment.OSVersion}, {RuntimeInformation.OSDescription}"; + return RuntimeInformation.OSDescription.Replace("Microsoft", "").Trim(); } - - // ReSharper disable once StringLiteralTypo - [DllImport("psapi.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool GetPerformanceInfo([Out] out PerformanceInformation performanceInformation, - [In] int size); } \ No newline at end of file diff --git a/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj b/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj index 152ea729d..12e19bbe6 100644 --- a/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj +++ b/VpnHood.Server.App.Net/VpnHood.Server.App.Net.csproj @@ -12,9 +12,9 @@ https://github.com/vpnhood/vpnhood LGPL-2.1-only VpnHood.png - 2.6.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 enable latest Linux @@ -43,6 +43,7 @@ + diff --git a/VpnHood.Server.App.Net/nlog.config b/VpnHood.Server.App.Net/nlog.config index cf81203cd..68b217efd 100644 --- a/VpnHood.Server.App.Net/nlog.config +++ b/VpnHood.Server.App.Net/nlog.config @@ -39,7 +39,7 @@ - + diff --git a/VpnHood.Server/Exceptions/MaxTcpChannelException.cs b/VpnHood.Server/Exceptions/MaxTcpChannelException.cs new file mode 100644 index 000000000..79e4b7d0a --- /dev/null +++ b/VpnHood.Server/Exceptions/MaxTcpChannelException.cs @@ -0,0 +1,12 @@ +using VpnHood.Common.Exceptions; +using VpnHood.Common.Messaging; + +namespace VpnHood.Server.Exceptions; + +internal class MaxTcpChannelException : SessionException +{ + public MaxTcpChannelException(uint sessionId) + : base(SessionErrorCode.GeneralError, $"Maximum TcpChannel has been reached. SessionId: {sessionId}.") + { + } +} \ No newline at end of file diff --git a/VpnHood.Server/Exceptions/MaxTcpConnectException.cs b/VpnHood.Server/Exceptions/MaxTcpConnectException.cs new file mode 100644 index 000000000..0540365f6 --- /dev/null +++ b/VpnHood.Server/Exceptions/MaxTcpConnectException.cs @@ -0,0 +1,12 @@ +using VpnHood.Common.Exceptions; +using VpnHood.Common.Messaging; + +namespace VpnHood.Server.Exceptions; + +internal class MaxTcpConnectException : SessionException +{ + public MaxTcpConnectException(uint sessionId) + : base(SessionErrorCode.GeneralError, $"Maximum TcpConnectWait has been reached. SessionId: {sessionId}.") + { + } +} \ No newline at end of file diff --git a/VpnHood.Server/Exceptions/TlsAuthenticateException.cs b/VpnHood.Server/Exceptions/TlsAuthenticateException.cs new file mode 100644 index 000000000..1af8dfcb2 --- /dev/null +++ b/VpnHood.Server/Exceptions/TlsAuthenticateException.cs @@ -0,0 +1,11 @@ +using System; + +namespace VpnHood.Server.Exceptions; + +internal class TlsAuthenticateException : Exception +{ + public TlsAuthenticateException(string message, Exception innerException) + : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/VpnHood.Server/ServerOptions.cs b/VpnHood.Server/ServerOptions.cs index e74ffdc55..55bc5f0ce 100644 --- a/VpnHood.Server/ServerOptions.cs +++ b/VpnHood.Server/ServerOptions.cs @@ -15,4 +15,7 @@ public class ServerOptions public TimeSpan ConfigureInterval { get; set; } = TimeSpan.FromSeconds(60); public string StoragePath { get; set; } = Directory.GetCurrentDirectory(); public bool PublicIpDiscovery { get; set; } = true; + public int MaxTcpConnectWaitCount { get; set; } = 500; + public int MaxTcpChannelCount { get; set; } = 1000; + public TimeSpan TcpConnectTimeout { get; set; } = TimeSpan.FromSeconds(60); } \ No newline at end of file diff --git a/VpnHood.Server/Session.cs b/VpnHood.Server/Session.cs index cf2f2063d..9eb34ace8 100644 --- a/VpnHood.Server/Session.cs +++ b/VpnHood.Server/Session.cs @@ -32,6 +32,7 @@ public class Session : IDisposable, IAsyncDisposable private readonly Timer _cleanupTimer; private DateTime _lastSyncedTime = DateTime.Now; private readonly TrackingOptions _trackingOptions; + public int TcpConnectWaitCount; public Tunnel Tunnel { get; } public uint SessionId { get; } @@ -39,14 +40,13 @@ public class Session : IDisposable, IAsyncDisposable public ResponseBase SessionResponse { get; private set; } public UdpChannel? UdpChannel { get; private set; } public bool IsDisposed { get; private set; } - - public int TcpConnectionCount => - Tunnel.StreamChannelCount + (UseUdpChannel ? 0 : Tunnel.DatagramChannels.Length); + public int TcpChannelCount => + Tunnel.StreamChannelCount + (UseUdpChannel ? 0 : Tunnel.DatagramChannels.Length); + public int UdpConnectionCount => _proxyManager.UdpConnectionCount + (UseUdpChannel ? 1 : 0); public DateTime LastActivityTime => Tunnel.LastActivityTime; - internal Session(IAccessServer accessServer, SessionResponse sessionResponse, SocketFactory socketFactory, IPEndPoint hostEndPoint, SessionOptions options, TrackingOptions trackingOptions) { @@ -66,8 +66,7 @@ internal Session(IAccessServer accessServer, SessionResponse sessionResponse, So if (options.MaxDatagramChannelCount > 0) tunnelOptions.MaxDatagramChannelCount = options.MaxDatagramChannelCount; Tunnel = new Tunnel(tunnelOptions); Tunnel.OnPacketReceived += Tunnel_OnPacketReceived; - Tunnel.OnTrafficChanged += Tunnel_OnTrafficChanged; - + if (trackingOptions.IsEnabled()) _proxyManager.OnNewEndPoint += OnNewEndPoint; } @@ -82,8 +81,8 @@ private void Cleanup(object state) _proxyManager.Cleanup(); Tunnel.Cleanup(); - if (DateTime.Now - _lastSyncedTime > _syncInterval) - _ = Sync(); + var force = DateTime.Now - _lastSyncedTime > _syncInterval; + _ = Sync(force, false); } public bool UseUdpChannel @@ -126,11 +125,6 @@ private void Tunnel_OnPacketReceived(object sender, ChannelPacketReceivedEventAr _proxyManager.SendPacket(e.IpPackets); } - private void Tunnel_OnTrafficChanged(object sender, EventArgs e) - { - _ = Sync(false, false); - } - public Task Sync() => Sync(true, false); private async Task Sync(bool force, bool closeSession) @@ -216,7 +210,6 @@ public async ValueTask DisposeAsync(bool closeSessionInAccessServer, bool log = IsDisposed = true; Tunnel.OnPacketReceived -= Tunnel_OnPacketReceived; - Tunnel.OnTrafficChanged -= Tunnel_OnTrafficChanged; Tunnel.Dispose(); _proxyManager.Dispose(); @@ -225,12 +218,8 @@ public async ValueTask DisposeAsync(bool closeSessionInAccessServer, bool log = // Report removing session if (log) - { - if (closeSessionInAccessServer) - VhLogger.Instance.LogInformation(GeneralEventId.Session, "The session has been permanently closed."); - else - VhLogger.Instance.LogInformation(GeneralEventId.Session, "The session has been temporarily closed."); - } + VhLogger.Instance.LogInformation(GeneralEventId.Session, "The session has been {State} closed. SessionId: {SessionId}.", + closeSessionInAccessServer ? "permanently" : "temporary", SessionId); } public void LogTrack(string protocol, int localPort, IPEndPoint destinationEndPoint) @@ -243,8 +232,8 @@ public void LogTrack(string protocol, int localPort, IPEndPoint destinationEndPo var destinationPortStr = _trackingOptions.TrackDestinationPort ? destinationEndPoint.Port.ToString() : "*"; VhLogger.Instance.LogInformation(GeneralEventId.Track, - "Proto: {Proto}, SessionId: {SessionId}, TcpCount: {TcpCount}, UdpCount: {UdpCount}, SrcPort: {SrcPort}, DstIp:{DstIp}, DstPort: {DstPort}", - protocol, SessionId, TcpConnectionCount, _proxyManager.UsedUdpPortCount, + "Proto: {Proto}, SessionId: {SessionId}, Tcp: {TcpCount}, Udp: {UdpCount}, TcpWait: {TcpConnectWaitCount}, SrcPort: {SrcPort}, DstIp:{DstIp}, DstPort: {DstPort}", + protocol, SessionId, TcpChannelCount, _proxyManager.UsedUdpPortCount, TcpConnectWaitCount, localPortStr, destinationIpStr, destinationPortStr); } diff --git a/VpnHood.Server/SystemInformation/BasicSystemInfoProvider.cs b/VpnHood.Server/SystemInformation/BasicSystemInfoProvider.cs index ef00283ff..eed2af52d 100644 --- a/VpnHood.Server/SystemInformation/BasicSystemInfoProvider.cs +++ b/VpnHood.Server/SystemInformation/BasicSystemInfoProvider.cs @@ -11,6 +11,7 @@ public string GetOperatingSystemInfo() public SystemInfo GetSystemInfo() { - return new SystemInfo(0, 0); + return new SystemInfo(Environment.OSVersion.ToString(), + null, null, 0, Environment.ProcessorCount); } } \ No newline at end of file diff --git a/VpnHood.Server/SystemInformation/ISystemInfoProvider.cs b/VpnHood.Server/SystemInformation/ISystemInfoProvider.cs index cc6b8ff66..e9ba9a4e8 100644 --- a/VpnHood.Server/SystemInformation/ISystemInfoProvider.cs +++ b/VpnHood.Server/SystemInformation/ISystemInfoProvider.cs @@ -3,5 +3,4 @@ public interface ISystemInfoProvider { SystemInfo GetSystemInfo(); - string GetOperatingSystemInfo(); } \ No newline at end of file diff --git a/VpnHood.Server/SystemInformation/SystemInfo.cs b/VpnHood.Server/SystemInformation/SystemInfo.cs index bf0f5526a..80d98a251 100644 --- a/VpnHood.Server/SystemInformation/SystemInfo.cs +++ b/VpnHood.Server/SystemInformation/SystemInfo.cs @@ -1,13 +1,28 @@ -namespace VpnHood.Server.SystemInformation; +using VpnHood.Common; + +namespace VpnHood.Server.SystemInformation; public class SystemInfo { - public SystemInfo(long totalMemory, long freeMemory) + public string OsInfo { get; } + public long? TotalMemory { get; set; } + public long? AvailableMemory { get; } + public int? CpuUsage { get; } + public int LogicalCoreCount { get; } + + public SystemInfo(string osInfo, long? totalMemory, long? availableMemory, int? cpuUsage, int logicalCoreCount) { + OsInfo = osInfo; TotalMemory = totalMemory; - FreeMemory = freeMemory; + AvailableMemory = availableMemory; + CpuUsage = cpuUsage; + LogicalCoreCount = logicalCoreCount; + } + + public override string ToString() + { + var totalMemory = TotalMemory != null ? Util.FormatBytes(TotalMemory.Value) : "*"; + return $"{OsInfo}, TotalMemory: {totalMemory}, Logical Cores: {LogicalCoreCount}"; } - public long TotalMemory { get; } - public long FreeMemory { get; } } \ No newline at end of file diff --git a/VpnHood.Server/TcpHost.cs b/VpnHood.Server/TcpHost.cs index 2d6ce1e1b..32f395a39 100644 --- a/VpnHood.Server/TcpHost.cs +++ b/VpnHood.Server/TcpHost.cs @@ -12,30 +12,42 @@ using VpnHood.Common.Exceptions; using VpnHood.Common.Logging; using VpnHood.Common.Messaging; +using VpnHood.Server.Exceptions; using VpnHood.Tunneling; using VpnHood.Tunneling.Factory; using VpnHood.Tunneling.Messaging; namespace VpnHood.Server; + internal class TcpHost : IDisposable { private const int ServerProtocolVersion = 2; - private readonly TimeSpan _remoteHostTimeout = TimeSpan.FromSeconds(60); + private readonly TimeSpan _requestTimeout = TimeSpan.FromSeconds(60); private CancellationTokenSource? _cancellationTokenSource; private readonly SessionManager _sessionManager; private readonly SocketFactory _socketFactory; private readonly SslCertificateManager _sslCertificateManager; private readonly List _tcpListeners = new(); private bool _isIpV6Supported; - public bool IsDisposed { get; private set; } private Task? _startTask; + private bool _isKeepAliveHasError; + private TimeSpan _tcpTimeout; + + public int MaxTcpChannelCount { get; set; } = int.MaxValue; + public int MaxTcpConnectWaitCount { get; set; } = int.MaxValue; + public TimeSpan TcpConnectTimeout { get; set; } = TimeSpan.FromSeconds(60); + public bool IsDisposed { get; private set; } + + public int OrgStreamReadBufferSize { get; set; } + public int TunnelStreamReadBufferSize { get; set; } + public bool IsStarted { get; private set; } - private class TlsAuthenticateException : Exception + public TimeSpan TcpTimeout { - public TlsAuthenticateException(string message, Exception innerException) - : base(message, innerException) - { - } + get => _tcpTimeout; + set => _tcpTimeout = (value == TimeSpan.Zero || value == Timeout.InfiniteTimeSpan) + ? TimeSpan.FromHours(48) + : value; } public TcpHost(SessionManager sessionManager, SslCertificateManager sslCertificateManager, SocketFactory socketFactory) @@ -45,11 +57,6 @@ public TcpHost(SessionManager sessionManager, SslCertificateManager sslCertifica _socketFactory = socketFactory; } - public TimeSpan TcpTimeout { get; set; } - public int OrgStreamReadBufferSize { get; set; } - public int TunnelStreamReadBufferSize { get; set; } - public bool IsStarted { get; private set; } - public void Start(IPEndPoint[] tcpEndPoints, bool isIpV6Supported) { if (IsStarted) throw new Exception($"{nameof(TcpHost)} is already Started!"); @@ -105,17 +112,19 @@ public async Task Stop() IsStarted = false; } - private bool _isKeepAliveSupported = true; private void EnableKeepAlive(Socket client) { + if (_isKeepAliveHasError) + return; + try { _socketFactory.SetKeepAlive(client, true, TcpTimeout / 2); } catch (Exception ex) { + _isKeepAliveHasError = true; VhLogger.Instance.LogWarning(ex, "KeepAlive is not supported! Consider upgrading your OS."); - _isKeepAliveSupported = false; } } @@ -129,9 +138,7 @@ private async Task ListenTask(TcpListener tcpListener, CancellationToken cancell while (!cancellationToken.IsCancellationRequested) { var tcpClient = await tcpListener.AcceptTcpClientAsync(); - - if (TcpTimeout != TimeSpan.Zero && TcpTimeout != Timeout.InfiniteTimeSpan && _isKeepAliveSupported) - EnableKeepAlive(tcpClient.Client); + EnableKeepAlive(tcpClient.Client); // create cancellation token _ = ProcessClient(tcpClient, cancellationToken); @@ -181,7 +188,7 @@ private async Task ProcessClient(TcpClient tcpClient, CancellationToken cancella using var scope = VhLogger.Instance.BeginScope($"RemoteEp: {VhLogger.Format(tcpClient.Client.RemoteEndPoint)}"); // add timeout to cancellationToken - using var timeoutCt = new CancellationTokenSource(_remoteHostTimeout); + using var timeoutCt = new CancellationTokenSource(_requestTimeout); using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutCt.Token, cancellationToken); cancellationToken = cancellationTokenSource.Token; TcpClientStream? tcpClientStream = null; @@ -219,7 +226,7 @@ private async Task ProcessClient(TcpClient tcpClient, CancellationToken cancella await StreamUtil.WriteJsonAsync(tcpClientStream.Stream, new ResponseBase(ex.SessionResponse), cancellationToken); - VhLogger.Instance.LogInformation(GeneralEventId.Tcp, ex, + VhLogger.Instance.LogInformation(GeneralEventId.Tcp, ex, $"Connection has been closed. SessionError: {ex.SessionResponse.ErrorCode}."); } catch (Exception ex) when (tcpClientStream != null) @@ -412,34 +419,53 @@ private async Task ProcessTcpProxyChannel(TcpClientStream tcpClientStream, Cance var request = await StreamUtil.ReadJsonAsync(tcpClientStream.Stream, cancellationToken); // find session - using var _ = VhLogger.Instance.BeginScope($"SessionId: {VhLogger.FormatSessionId(request.SessionId)}"); + using var scope = VhLogger.Instance.BeginScope($"SessionId: {VhLogger.FormatSessionId(request.SessionId)}"); + var session = await _sessionManager.GetSession(request, tcpClientStream.LocalEndPoint, tcpClientStream.RemoteEndPoint.Address); + var isRequestedEpException = false; + var isTcpConnectIncreased = false; TcpClient? tcpClient2 = null; TcpClientStream? tcpClientStream2 = null; TcpProxyChannel? tcpProxyChannel = null; try { - var session = await _sessionManager.GetSession(request, tcpClientStream.LocalEndPoint, - tcpClientStream.RemoteEndPoint.Address); - // connect to requested site VhLogger.Instance.LogTrace(GeneralEventId.StreamChannel, $"Connecting to the requested endpoint. RequestedEP: {VhLogger.Format(request.DestinationEndPoint)}"); + // Check tcp wait limit + lock (session) + { + if (session.TcpConnectWaitCount >= MaxTcpConnectWaitCount) + throw new MaxTcpConnectException(session.SessionId); + + if (session.TcpChannelCount >= MaxTcpChannelCount) + throw new MaxTcpChannelException(session.SessionId); + + Interlocked.Increment(ref session.TcpConnectWaitCount); + isTcpConnectIncreased = true; + } + + // prepare client tcpClient2 = _socketFactory.CreateTcpClient(request.DestinationEndPoint.AddressFamily); - if (TcpTimeout != TimeSpan.Zero && TcpTimeout != Timeout.InfiniteTimeSpan) - EnableKeepAlive(tcpClient2.Client); + EnableKeepAlive(tcpClient2.Client); //tracking - session.LogTrack(ProtocolType.Tcp.ToString(), ((IPEndPoint)tcpClient2.Client.LocalEndPoint).Port, request.DestinationEndPoint); + session.LogTrack(ProtocolType.Tcp.ToString(), + ((IPEndPoint)tcpClient2.Client.LocalEndPoint).Port, request.DestinationEndPoint); // connect to requested destination isRequestedEpException = true; - await Util.RunTask(tcpClient2.ConnectAsync(request.DestinationEndPoint.Address, request.DestinationEndPoint.Port), - _remoteHostTimeout, cancellationToken); + await Util.RunTask( + tcpClient2.ConnectAsync(request.DestinationEndPoint.Address, request.DestinationEndPoint.Port), + TcpConnectTimeout, cancellationToken); isRequestedEpException = false; + // Release TcpConnectWaitCount + Interlocked.Decrement(ref session.TcpConnectWaitCount); + isTcpConnectIncreased = false; + // send response await StreamUtil.WriteJsonAsync(tcpClientStream.Stream, session.SessionResponse, cancellationToken); @@ -449,7 +475,8 @@ await Util.RunTask(tcpClient2.ConnectAsync(request.DestinationEndPoint.Address, request.CipherKey, null, request.CipherLength); // add the connection - VhLogger.Instance.LogTrace(GeneralEventId.StreamChannel, $"Adding a {nameof(TcpProxyChannel)}. SessionId: {VhLogger.FormatSessionId(session.SessionId)}, CipherLength: {request.CipherLength}"); + VhLogger.Instance.LogTrace(GeneralEventId.StreamChannel, + $"Adding a {nameof(TcpProxyChannel)}. SessionId: {VhLogger.FormatSessionId(session.SessionId)}, CipherLength: {request.CipherLength}"); tcpClientStream2 = new TcpClientStream(tcpClient2, tcpClient2.GetStream()); tcpProxyChannel = new TcpProxyChannel(tcpClientStream2, tcpClientStream, TcpTimeout, @@ -462,11 +489,17 @@ await Util.RunTask(tcpClient2.ConnectAsync(request.DestinationEndPoint.Address, tcpClient2?.Dispose(); tcpClientStream2?.Dispose(); tcpProxyChannel?.Dispose(); - + if (isRequestedEpException) throw new SessionException(SessionErrorCode.GeneralError, ex.Message); + throw; } + finally + { + if (isTcpConnectIncreased) + Interlocked.Decrement(ref session.TcpConnectWaitCount); + } } public void Dispose() diff --git a/VpnHood.Server/VpnHood.Server.csproj b/VpnHood.Server/VpnHood.Server.csproj index 6cfe70859..498f39526 100644 --- a/VpnHood.Server/VpnHood.Server.csproj +++ b/VpnHood.Server/VpnHood.Server.csproj @@ -13,9 +13,9 @@ VpnHood.png The core of VpnHood server. It can listen and accept connections from VpnHood clients. VpnHood.Server - 2.6.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 enable latest diff --git a/VpnHood.Server/VpnHoodServer.cs b/VpnHood.Server/VpnHoodServer.cs index 9d90671a9..7bd5dc124 100644 --- a/VpnHood.Server/VpnHoodServer.cs +++ b/VpnHood.Server/VpnHoodServer.cs @@ -32,14 +32,22 @@ public class VpnHoodServer : IDisposable public VpnHoodServer(IAccessServer accessServer, ServerOptions options) { - if (options.SocketFactory == null) throw new ArgumentNullException(nameof(options.SocketFactory)); - _autoDisposeAccessServer = options.AutoDisposeAccessServer; - _lastConfigFilePath = Path.Combine(options.StoragePath, "last-config.json"); + if (options.SocketFactory == null) + throw new ArgumentNullException(nameof(options.SocketFactory)); + AccessServer = accessServer; SystemInfoProvider = options.SystemInfoProvider ?? new BasicSystemInfoProvider(); SessionManager = new SessionManager(accessServer, options.SocketFactory, options.Tracker); - _tcpHost = new TcpHost(SessionManager, new SslCertificateManager(AccessServer), options.SocketFactory); + + _autoDisposeAccessServer = options.AutoDisposeAccessServer; + _lastConfigFilePath = Path.Combine(options.StoragePath, "last-config.json"); _publicIpDiscovery = options.PublicIpDiscovery; + _tcpHost = new TcpHost(SessionManager, new SslCertificateManager(AccessServer), options.SocketFactory) + { + TcpConnectTimeout = options.TcpConnectTimeout, + MaxTcpConnectWaitCount = options.MaxTcpConnectWaitCount, + MaxTcpChannelCount = options.MaxTcpChannelCount + }; // Configure thread pool size ThreadPool.GetMinThreads(out var workerThreads, out var completionPortThreads); @@ -109,7 +117,7 @@ public async Task Start() // Report current OS Version VhLogger.Instance.LogInformation($"{GetType().Assembly.GetName().FullName}"); - VhLogger.Instance.LogInformation($"OS: {SystemInfoProvider.GetOperatingSystemInfo()}"); + VhLogger.Instance.LogInformation($"OS: {SystemInfoProvider.GetSystemInfo()}"); // report config ThreadPool.GetMinThreads(out var minWorkerThreads, out var minCompletionPortThreads); @@ -137,6 +145,7 @@ private async Task Configure() // get server info VhLogger.Instance.LogInformation("Configuring by the Access Server..."); + var providerSystemInfo = SystemInfoProvider.GetSystemInfo(); var serverInfo = new ServerInfo( environmentVersion: Environment.Version, version: typeof(VpnHoodServer).Assembly.GetName().Version, @@ -146,9 +155,10 @@ private async Task Configure() ) { MachineName = Environment.MachineName, - OsInfo = SystemInfoProvider.GetOperatingSystemInfo(), + OsInfo = providerSystemInfo.OsInfo, OsVersion = Environment.OSVersion.ToString(), - TotalMemory = SystemInfoProvider.GetSystemInfo().TotalMemory, + TotalMemory = providerSystemInfo.TotalMemory, + LogicalCoreCount = providerSystemInfo.LogicalCoreCount, LastError = _lastConfigError }; var isIpv6Supported = serverInfo.PublicIpAddresses.Any(x => x.AddressFamily == AddressFamily.InterNetworkV6); @@ -198,8 +208,11 @@ private async Task Configure() } } - private static int GetBestTcpBufferSize(long totalMemory) + private static int GetBestTcpBufferSize(long? totalMemory) { + if (totalMemory == null) + return 8192; + var bufferSize = (long)Math.Round((double)totalMemory / 0x80000000) * 4096; bufferSize = Math.Max(bufferSize, 8192); bufferSize = Math.Min(bufferSize, 8192); //81920, it looks it doesn't have effect @@ -244,10 +257,11 @@ public ServerStatus Status var serverStatus = new ServerStatus { SessionCount = SessionManager.Sessions.Count(x => !x.Value.IsDisposed), - TcpConnectionCount = SessionManager.Sessions.Values.Sum(x => x.TcpConnectionCount), + TcpConnectionCount = SessionManager.Sessions.Values.Sum(x => x.TcpChannelCount), UdpConnectionCount = SessionManager.Sessions.Values.Sum(x => x.UdpConnectionCount), ThreadCount = Process.GetCurrentProcess().Threads.Count, - FreeMemory = systemInfo.FreeMemory, + AvailableMemory = systemInfo.AvailableMemory, + CpuUsage = systemInfo.CpuUsage, UsedMemory = Process.GetCurrentProcess().WorkingSet64, TunnelSendSpeed = SessionManager.Sessions.Sum(x => x.Value.Tunnel.SendSpeed), TunnelReceiveSpeed = SessionManager.Sessions.Sum(x => x.Value.Tunnel.ReceiveSpeed), diff --git a/VpnHood.Tunneling/Tunnel.cs b/VpnHood.Tunneling/Tunnel.cs index cb87b5497..e53167324 100644 --- a/VpnHood.Tunneling/Tunnel.cs +++ b/VpnHood.Tunneling/Tunnel.cs @@ -12,7 +12,6 @@ namespace VpnHood.Tunneling; public class Tunnel : IDisposable { - private const int SpeedThreshold = 2; private readonly object _channelListLock = new(); private readonly int _maxQueueLength = 100; private readonly int _mtuNoFragment = TunnelUtil.MtuWithoutFragmentation; @@ -20,9 +19,6 @@ public class Tunnel : IDisposable private readonly Queue _packetQueue = new(); private readonly SemaphoreSlim _packetSentEvent = new(0); private readonly SemaphoreSlim _packetSenderSemaphore = new(0); - private readonly Queue _receivedBytes = new(); - private readonly Queue _sentBytes = new(); - private readonly object _speedMonitorLock = new(); private readonly HashSet _streamChannels = new(); private readonly Timer _speedMonitorTimer; private bool _disposed; @@ -33,14 +29,20 @@ public class Tunnel : IDisposable private long _sentByteCount; private readonly TimeSpan _datagramPacketTimeout = TimeSpan.FromSeconds(100); private readonly TimeSpan _tcpTimeout; - private DateTime _lastTcpProxyCleanup = DateTime.Now; + private DateTime _lastTcpProxyCleanupTime = DateTime.Now; + private DateTime _lastSpeedUpdateTime = DateTime.Now; + private readonly TimeSpan _speedTestThreshold = TimeSpan.FromSeconds(2); + + public long SendSpeed { get; private set; } + public long ReceiveSpeed { get; private set; } + public DateTime LastActivityTime { get; private set; } = DateTime.Now; public Tunnel(TunnelOptions? options = null) { options ??= new TunnelOptions(); _tcpTimeout = options.TcpTimeout; _maxDatagramChannelCount = options.MaxDatagramChannelCount; - _speedMonitorTimer = new Timer(SpeedMonitor, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); + _speedMonitorTimer = new Timer(_ => UpdateSpeed(), null, TimeSpan.Zero, _speedTestThreshold); } public int StreamChannelCount => _streamChannels.Count; @@ -72,7 +74,7 @@ public long SentByteCount public void Cleanup() { - if (_lastTcpProxyCleanup + _tcpTimeout / 2 < DateTime.Now) + if (_lastTcpProxyCleanupTime + _tcpTimeout / 2 < DateTime.Now) { var channels = Util.SafeToArray(_streamChannels, _streamChannels); foreach (var item in channels) @@ -80,14 +82,10 @@ public void Cleanup() if (item is TcpProxyChannel tcpProxyChannel) tcpProxyChannel.CheckConnection(); } - _lastTcpProxyCleanup = DateTime.Now; + _lastTcpProxyCleanupTime = DateTime.Now; } } - public long SendSpeed { get; private set; } - public long ReceiveSpeed { get; private set; } - public DateTime LastActivityTime { get; private set; } = DateTime.Now; - public int MaxDatagramChannelCount { get => _maxDatagramChannelCount; @@ -102,40 +100,28 @@ public int MaxDatagramChannelCount public event EventHandler? OnPacketReceived; public event EventHandler? OnChannelAdded; public event EventHandler? OnChannelRemoved; - public event EventHandler? OnTrafficChanged; - private void SpeedMonitor(object state) + private void UpdateSpeed() { - if (_disposed) return; - - bool trafficChanged; - lock (_speedMonitorLock) - { - var sentByteCount = SentByteCount; - var receivedByteCount = ReceivedByteCount; - trafficChanged = _lastSentByteCount != sentByteCount || _lastReceivedByteCount != receivedByteCount; - - // add transferred bytes - _sentBytes.Enqueue(sentByteCount - _lastSentByteCount); - _receivedBytes.Enqueue(receivedByteCount - _lastReceivedByteCount); - if (_sentBytes.Count > SpeedThreshold) _sentBytes.TryDequeue(out _); - if (_receivedBytes.Count > SpeedThreshold) _receivedBytes.TryDequeue(out _); - - // calculate speed - SendSpeed = _sentBytes.Sum() / SpeedThreshold; - ReceiveSpeed = _receivedBytes.Sum() / SpeedThreshold; - - // save last traffic - _lastSentByteCount = sentByteCount; - _lastReceivedByteCount = receivedByteCount; - } + if (_disposed) + return; + + if (DateTime.Now - _lastSpeedUpdateTime < _speedTestThreshold) + return; - // fire traffic changed + var sentByteCount = SentByteCount; + var receivedByteCount = ReceivedByteCount; + var trafficChanged = _lastSentByteCount != sentByteCount || _lastReceivedByteCount != receivedByteCount; + var duration = (DateTime.Now - _lastSpeedUpdateTime).TotalSeconds; + + SendSpeed = (int)((sentByteCount - _lastSentByteCount) / duration); + ReceiveSpeed = (int)((receivedByteCount - _lastReceivedByteCount) / duration); + + _lastSpeedUpdateTime = DateTime.Now; + _lastSentByteCount = sentByteCount; + _lastReceivedByteCount = receivedByteCount; if (trafficChanged) - { LastActivityTime = DateTime.Now; - OnTrafficChanged?.Invoke(this, EventArgs.Empty); - } } private bool IsChannelExists(IChannel channel) @@ -423,6 +409,8 @@ public void Dispose() _packetQueue.Clear(); _speedMonitorTimer.Dispose(); + SendSpeed = 0; + ReceiveSpeed = 0; // release worker threads _packetSenderSemaphore.Release(MaxDatagramChannelCount * 10); //make sure to release all semaphores diff --git a/VpnHood.Tunneling/VpnHood.Tunneling.csproj b/VpnHood.Tunneling/VpnHood.Tunneling.csproj index 4e9ec1364..2598b8598 100644 --- a/VpnHood.Tunneling/VpnHood.Tunneling.csproj +++ b/VpnHood.Tunneling/VpnHood.Tunneling.csproj @@ -15,9 +15,9 @@ VpnHood.png VpnHood.Tunneling Provides tunnelling classes and protocols shared between VpnHood.Client and VpnHood.Server. - 2.6.326 - 2.6.326 - 2.6.326 + 2.6.328 + 2.6.328 + 2.6.328 enable latest diff --git a/VpnHood.ZTest/TestHelper.cs b/VpnHood.ZTest/TestHelper.cs index 47b5db28f..d55ec5ada 100644 --- a/VpnHood.ZTest/TestHelper.cs +++ b/VpnHood.ZTest/TestHelper.cs @@ -140,9 +140,9 @@ public static void Test_Udp(UdpClient? udpClient = null, IPEndPoint? ntpEndPoint Assert.IsTrue(time > DateTime.Now.AddMinutes(-10)); } - public static void Test_Https(HttpClient? httpClient = default, Uri? uri = default, int timeout = 3000) + public static void Test_Https(HttpClient? httpClient = default, Uri? uri = default, int timeout = 3000, bool throwError = true) { - if (!SendHttpGet(httpClient, uri, timeout)) + if (!SendHttpGet(httpClient, uri, timeout) && throwError) throw new Exception("Https get doesn't work!"); } @@ -157,6 +157,7 @@ public static IPAddress[] GetTestIpAddresses() addresses.Add(TEST_NtpEndPoint2.Address); addresses.Add(TEST_PingAddress1); addresses.Add(TEST_PingAddress2); + addresses.Add(TEST_InvalidIp); addresses.Add(new ClientOptions().TcpProxyLoopbackAddressIpV4); return addresses.ToArray(); } @@ -195,8 +196,9 @@ public static FileAccessServerOptions CreateFileAccessServerOptions() TcpEndPoints = new[] { Util.GetFreeEndPoint(IPAddress.Loopback) }, SessionOptions = { - SyncCacheSize = 50 - } + SyncCacheSize = 50, + IcmpTimeout = TimeSpan.FromMilliseconds(100) + }, }; return options; } diff --git a/VpnHood.ZTest/Tests/AccessTest.cs b/VpnHood.ZTest/Tests/AccessTest.cs index 309b26c5c..2f8c30226 100644 --- a/VpnHood.ZTest/Tests/AccessTest.cs +++ b/VpnHood.ZTest/Tests/AccessTest.cs @@ -1,8 +1,10 @@ using System; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; using VpnHood.Client; +using VpnHood.Common.Logging; using VpnHood.Common.Messaging; namespace VpnHood.Test.Tests; @@ -10,6 +12,12 @@ namespace VpnHood.Test.Tests; [TestClass] public class AccessTest { + [TestInitialize] + public void Initialize() + { + VhLogger.Instance = VhLogger.CreateConsoleLogger(true); + } + [TestMethod] public void Server_reject_invalid_requests() { diff --git a/VpnHood.ZTest/Tests/ClientServerTest.cs b/VpnHood.ZTest/Tests/ClientServerTest.cs index eb10ff3be..97d0f61e7 100644 --- a/VpnHood.ZTest/Tests/ClientServerTest.cs +++ b/VpnHood.ZTest/Tests/ClientServerTest.cs @@ -15,6 +15,7 @@ using VpnHood.Common.Messaging; using VpnHood.Server; using VpnHood.Server.Providers.FileAccessServerProvider; +using VpnHood.Test.Factory; namespace VpnHood.Test.Tests; @@ -253,7 +254,6 @@ public void Client_must_dispose_after_device_closed() Assert.AreEqual(ClientState.Disposed, client.State); } - [TestMethod] public void Client_must_dispose_after_server_stopped() { @@ -506,7 +506,7 @@ public void AutoReconnect() TestHelper.Test_Https(); //let transfer something - fileAccessServer.SessionManager.Sessions.TryRemove(clientConnect.Client.SessionId, out _ ); + fileAccessServer.SessionManager.Sessions.TryRemove(clientConnect.Client.SessionId, out _); server.SessionManager.Sessions.TryRemove(clientConnect.Client.SessionId, out _); try @@ -584,5 +584,79 @@ public void Disconnect_for_unsupported_client() Assert.AreEqual(SessionErrorCode.UnsupportedClient, client.SessionStatus.ErrorCode); } } + + [TestMethod] + public async Task Server_limit_by_Max_TcpConnectWait() + { + // create access server + using var fileAccessServer = TestHelper.CreateFileAccessServer(); + using var testAccessServer = new TestAccessServer(fileAccessServer); + + // ser server options + var serverOptions = new ServerOptions + { + SocketFactory = new TestSocketFactory(true), + StoragePath = TestHelper.WorkingPath, + PublicIpDiscovery = false, //it slows down the running tests + MaxTcpConnectWaitCount = 2 + }; + using var server = new VpnHoodServer(testAccessServer, serverOptions); + await server.Start(); + + // create client + var token = TestHelper.CreateAccessToken(server); + using var client = TestHelper.CreateClient(token); + + using var httpClient = new HttpClient(); + _ = httpClient.GetStringAsync($"https://{TestHelper.TEST_InvalidIp}:4441"); + _ = httpClient.GetStringAsync($"https://{TestHelper.TEST_InvalidIp}:4442"); + _ = httpClient.GetStringAsync($"https://{TestHelper.TEST_InvalidIp}:4443"); + _ = httpClient.GetStringAsync($"https://{TestHelper.TEST_InvalidIp}:4445"); + + await Task.Delay(1000); + var session = server.SessionManager.GetSessionById(client.SessionId); + Assert.AreEqual(serverOptions.MaxTcpConnectWaitCount, session?.TcpConnectWaitCount); + } + + [TestMethod] + public async Task Server_limit_by_Max_TcpChannel() + { + // create access server + using var fileAccessServer = TestHelper.CreateFileAccessServer(); + using var testAccessServer = new TestAccessServer(fileAccessServer); + + // ser server options + var serverOptions = new ServerOptions + { + SocketFactory = new TestSocketFactory(true), + StoragePath = TestHelper.WorkingPath, + PublicIpDiscovery = false, //it slows down the running tests + MaxTcpChannelCount = 2 + }; + using var server = new VpnHoodServer(testAccessServer, serverOptions); + await server.Start(); + + // create client + var token = TestHelper.CreateAccessToken(server); + using var client = TestHelper.CreateClient(token); + + using var tcpClient1 = new TcpClient(); + using var tcpClient2 = new TcpClient(); + using var tcpClient3 = new TcpClient(); + using var tcpClient4 = new TcpClient(); + + await tcpClient1.ConnectAsync(TestHelper.TEST_HttpsUri1.Host, 443); + await Task.Delay(250); + await tcpClient2.ConnectAsync(TestHelper.TEST_HttpsUri1.Host, 443); + await Task.Delay(250); + await tcpClient3.ConnectAsync(TestHelper.TEST_HttpsUri2.Host, 443); + await Task.Delay(250); + await tcpClient4.ConnectAsync(TestHelper.TEST_HttpsUri2.Host, 443); + await Task.Delay(250); + + var session = server.SessionManager.GetSessionById(client.SessionId); + Assert.AreEqual(serverOptions.MaxTcpChannelCount, session?.TcpChannelCount); + } + #endif } \ No newline at end of file diff --git a/VpnHood.ZTest/VpnHood.ZTest.csproj b/VpnHood.ZTest/VpnHood.ZTest.csproj index 4e0e580f8..2f6049cd7 100644 --- a/VpnHood.ZTest/VpnHood.ZTest.csproj +++ b/VpnHood.ZTest/VpnHood.ZTest.csproj @@ -16,7 +16,7 @@ - + diff --git a/VpnHood.sln.DotSettings b/VpnHood.sln.DotSettings index ed88892b1..f02685df3 100644 --- a/VpnHood.sln.DotSettings +++ b/VpnHood.sln.DotSettings @@ -15,13 +15,16 @@ True True True + True True True True + True True True True True + True True True True