diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbc77e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,257 @@ +## Custom + +*.DS_Store +.idea + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +# .idea/ +# *.sln.iml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a4dd1a7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "src/Logixware.SoftEther.Client.Core"] + path = src/Logixware.SoftEther.Client.Core + url = https://github.com/Gaulomatic/Logixware.SoftEther.Client.Core.git +[submodule "src/Logixware.SoftEther.Client.Manager"] + path = src/Logixware.SoftEther.Client.Manager + url = https://github.com/Gaulomatic/Logixware.SoftEther.Client.Manager.git +[submodule "src/Logixware.SoftEther.Client.Daemon"] + path = src/Logixware.SoftEther.Client.Daemon + url = https://github.com/Gaulomatic/Logixware.SoftEther.Client.Daemon.git diff --git a/Logixware.SoftEther.Client.sln b/Logixware.SoftEther.Client.sln new file mode 100644 index 0000000..fdde954 --- /dev/null +++ b/Logixware.SoftEther.Client.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logixware.SoftEther.Client.Manager", "src\Logixware.SoftEther.Client.Manager\Logixware.SoftEther.Client.Manager.csproj", "{4E1D138F-EA24-4BD0-8E69-4CD6AACD203E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logixware.SoftEther.Client.Core", "src\Logixware.SoftEther.Client.Core\Logixware.SoftEther.Client.Core.csproj", "{46B34AA8-92B6-45FF-8890-3BFAFC90B67E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logixware.SoftEther.Client.Daemon", "src\Logixware.SoftEther.Client.Daemon\Logixware.SoftEther.Client.Daemon.csproj", "{004238EF-98C3-4DCA-A40F-62227F5CA16D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4E1D138F-EA24-4BD0-8E69-4CD6AACD203E}.Debug|Any CPU.ActiveCfg = Debug|x86 + {4E1D138F-EA24-4BD0-8E69-4CD6AACD203E}.Debug|Any CPU.Build.0 = Debug|x86 + {4E1D138F-EA24-4BD0-8E69-4CD6AACD203E}.Release|Any CPU.ActiveCfg = Release|x86 + {4E1D138F-EA24-4BD0-8E69-4CD6AACD203E}.Release|Any CPU.Build.0 = Release|x86 + {46B34AA8-92B6-45FF-8890-3BFAFC90B67E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46B34AA8-92B6-45FF-8890-3BFAFC90B67E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46B34AA8-92B6-45FF-8890-3BFAFC90B67E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46B34AA8-92B6-45FF-8890-3BFAFC90B67E}.Release|Any CPU.Build.0 = Release|Any CPU + {004238EF-98C3-4DCA-A40F-62227F5CA16D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {004238EF-98C3-4DCA-A40F-62227F5CA16D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {004238EF-98C3-4DCA-A40F-62227F5CA16D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {004238EF-98C3-4DCA-A40F-62227F5CA16D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/Logixware.SoftEther.Client.Core/.gitignore b/src/Logixware.SoftEther.Client.Core/.gitignore new file mode 100644 index 0000000..cbc77e0 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/.gitignore @@ -0,0 +1,257 @@ +## Custom + +*.DS_Store +.idea + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +# .idea/ +# *.sln.iml diff --git a/src/Logixware.SoftEther.Client.Core/Logixware.SoftEther.Client.Core.csproj b/src/Logixware.SoftEther.Client.Core/Logixware.SoftEther.Client.Core.csproj new file mode 100644 index 0000000..0eff3c8 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware.SoftEther.Client.Core.csproj @@ -0,0 +1,16 @@ + + + netstandard2.0 + latest + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/ConnectionState.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/ConnectionState.cs new file mode 100644 index 0000000..a5e2a61 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/ConnectionState.cs @@ -0,0 +1,9 @@ +namespace Logixware.SoftEther.Client +{ + public enum ConnectionState + { + Disconnected, + Connected, + Retrying + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/LoggerExtensions.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/LoggerExtensions.cs new file mode 100644 index 0000000..b17fe25 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/LoggerExtensions.cs @@ -0,0 +1,29 @@ +using System; + +using Microsoft.Extensions.Logging; + +namespace Logixware.SoftEther.Client +{ + public static class LoggerExtensions + { + public static void Inform(this ILogger target, String message) + { + target.Log(LogLevel.Information, 1, message, null, (s, e) => DateTime.Now + " " + s.ToString()); + } + + public static void Warn(this ILogger target, String message) + { + target.Log(LogLevel.Warning, 1, message, null, (s, e) => DateTime.Now + " " + s.ToString()); + } + + public static void Error(this ILogger target, String message, Exception exception = null) + { + target.Log(LogLevel.Error, 1, message, exception, (s, e) => DateTime.Now + " " + s.ToString()); + } + + public static void Critical(this ILogger target, String message, Exception exception = null) + { + target.Log(LogLevel.Critical, 1, message, exception, (s, e) => DateTime.Now + " " + s.ToString()); + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/Shell/ExecutionResult.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/Shell/ExecutionResult.cs new file mode 100644 index 0000000..d0d987c --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/Shell/ExecutionResult.cs @@ -0,0 +1,16 @@ +using System; + +namespace Logixware.SoftEther.Client.Shell +{ + public class ExecutionResult + { + public ExecutionResult(Boolean succeeded, String result) + { + this.Succeeded = succeeded; + this.Result = result; + } + + public Boolean Succeeded { get; } + public String Result { get; } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/Shell/IShell.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/Shell/IShell.cs new file mode 100644 index 0000000..7ee252a --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/Shell/IShell.cs @@ -0,0 +1,9 @@ +using System; + +namespace Logixware.SoftEther.Client.Shell +{ + public interface IShell + { + ExecutionResult ExecuteCommand(String command); + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/Shell/Platform/MacShell.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/Shell/Platform/MacShell.cs new file mode 100644 index 0000000..d12f388 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/Shell/Platform/MacShell.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading; +using System.Diagnostics; + +namespace Logixware.SoftEther.Client.Shell.Platform +{ + + public class MacShell : IShell + { + public ExecutionResult ExecuteCommand(String command) + { + var __EscapedArgs = command.Replace("\"", "\\\""); + + var __Process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = $"-c \"{__EscapedArgs}\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = false + } + }; + + using (new Timer(delegate { __Process.Kill(); }, null, 5000, Timeout.Infinite)) + { + __Process.Start(); + + var __Error = __Process.StandardError.ReadToEnd(); + + if (!String.IsNullOrEmpty(__Error)) + { + __Process.WaitForExit(10000); + return new ExecutionResult(true, __Error); + } + + __Process.WaitForExit(10000); + + return new ExecutionResult(true, __Process.StandardOutput.ReadToEnd()); + } + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/Account.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/Account.cs new file mode 100644 index 0000000..49aa4f1 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/Account.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Logixware.SoftEther.Client.VpnService +{ + public class Account : QueryResult + { + public Account(ICommandLineInterface cli, Dictionary data) + : base(cli, data) + { + } + + public String Name => base.Get("VPN Connection Setting Name"); + public String DeviceName => base.Get("Device Name Used for Connection"); + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/AccountStatus.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/AccountStatus.cs new file mode 100644 index 0000000..2709c38 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/AccountStatus.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; + +namespace Logixware.SoftEther.Client.VpnService +{ + public class AccountStatus : QueryResult + { + public AccountStatus(ICommandLineInterface cli, Dictionary data) + : base(cli, data) + { + } + + public ConnectionState ConnectionState + { + get + { + var __State = base.Get("Session Status"); + + if (String.Equals(__State, "Connection Completed (Session Established)")) + { + return ConnectionState.Connected; + } + + return String.Equals(__State, "Retrying") ? ConnectionState.Retrying : ConnectionState.Disconnected; + } + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/CommandLineInterface.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/CommandLineInterface.cs new file mode 100644 index 0000000..3243a6e --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/CommandLineInterface.cs @@ -0,0 +1,203 @@ +using System; +using System.Linq; +using System.Threading; +using System.Collections.Generic; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; + +using Logixware.SoftEther.Client.Shell; + +namespace Logixware.SoftEther.Client.VpnService +{ + public class CommandLineInterface : ICommandLineInterface + { + private readonly ILogger _Logger; + private readonly CommandLineInterfaceConfiguration _Configuration; + private readonly IShell _Shell; + + public CommandLineInterface + ( + ILogger logger, + IConfiguration configuration, + IShell shell + ) + { + if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + + this._Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this._Shell = shell ?? throw new ArgumentNullException(nameof(shell)); + + this._Configuration = configuration.GetSection("VPN:CommandLineInterface")?.Get(); + + if (this._Configuration == null) + { + throw new InvalidOperationException("No command line interface configuration found."); + } + + if (String.IsNullOrEmpty(this._Configuration.PathToClient)) + { + throw new InvalidOperationException("No path to the VPN client service found."); + } + + if (String.IsNullOrEmpty(this._Configuration.PathToCli)) + { + throw new InvalidOperationException("No path to the VPN client command line interface found."); + } + } + + public IEnumerable GetAccounts() + { + foreach (var __Item in this.GetList("AccountList", "VPN Connection Setting Name").Keys) + { + yield return this.GetAccount(__Item); + } + } + + public Account GetAccount(String name) + { + return new Account(this, this.GetSingle($"AccountGet \"{name}\"")); + } + + public AccountStatus GetAccountStatus(String name) + { + return new AccountStatus(this, this.GetSingle($"AccountStatusGet \"{name}\"")); + } + + public IEnumerable GetDevices() + { + foreach (var __Item in this.GetList("NicList", "Virtual Network Adapter Name").Values) + { + yield return new Device(this, __Item); + } + } + + public Device GetDevice(String name) + { + return this.GetDevices().SingleOrDefault(i => String.Equals(i.Name, name)); + } + + private Dictionary GetSingle(String command) + { + return CommandLineInterface.ParseSingleResponse(this.ExecuteCommand(command)); + } + + private Dictionary> GetList(String command, String key) + { + return CommandLineInterface.ParseListResponse(this.ExecuteCommand(command), key); + } + + public void RestartClient() + { + this.StopClient(); + Thread.Sleep(5000); + this.StartClient(); + } + + public void StartClient() + { + var __StartCommand = $"\"{this._Configuration.PathToClient}\" start"; + var __StartExecution = this._Shell.ExecuteCommand(__StartCommand); + + if (__StartExecution.Succeeded) + { + var __Message = $"Successfully started the VPN client: {__StartExecution.Result}"; + this._Logger.Inform(__Message); + } + else + { + var __Message = $"Error starting the VPN client: {__StartExecution.Result}"; + this._Logger.Error(__Message); + } + } + + public void StopClient() + { + var __StopCommand = $"\"{this._Configuration.PathToClient}\" stop"; + var __StopExecution = this._Shell.ExecuteCommand(__StopCommand); + + if (__StopExecution.Succeeded) + { + var __Message = $"Successfully stopped the VPN client: {__StopExecution.Result}"; + this._Logger.Inform(__Message); + } + else + { + var __Message = $"Error stopping the VPN client: {__StopExecution.Result}"; + this._Logger.Error(__Message); + } + } + + private String ExecuteCommand(String command) + { + var __Command = $"\"{this._Configuration.PathToCli}\" localhost /CLIENT /PASSWORD=\"{this._Configuration.CliPassword}\" /CMD {command}"; + var __Execution = this._Shell.ExecuteCommand(__Command); + + if (__Execution.Succeeded) + { + if (__Execution.Result.Contains("Access has been denied")) + { + throw new AccessViolationException("Access to the vpn client has been denied. Password wrong?"); + } + + return __Execution.Result; + } + + throw new InvalidOperationException(__Execution.Result); + } + + private static Dictionary ParseSingleResponse(String response) + { + var __Settings = new Dictionary(); + + foreach (var __Line in response.Split("\n".ToCharArray())) + { + var __Setting = __Line.Split("|".ToCharArray()); + + if (__Setting.Length == 2 && !(String.Equals(__Setting[0], "Item") & String.Equals(__Setting[0], "Value"))) + { + var __Key = __Setting[0].Trim(); + var __Value = __Setting[1].Trim(); + + if (!(String.Equals(__Key, "Item", StringComparison.OrdinalIgnoreCase) & String.Equals(__Value, "Value", StringComparison.OrdinalIgnoreCase))) + { + __Settings.Add(__Key, __Value); + } + } + } + + return __Settings; + } + + private static Dictionary> ParseListResponse(String response, String key) + { + var __Items = new Dictionary>(); + + String __CurrentItem = null; + + foreach (var __Line in response.Split("\n".ToCharArray())) + { + var __Setting = __Line.Split("|".ToCharArray()); + + if (__Setting.Length == 2 && !(String.Equals(__Setting[0], "Item") & String.Equals(__Setting[0], "Value"))) + { + var __Key = __Setting[0].Trim(); + var __Value = __Setting[1].Trim(); + + if (String.Equals(__Key, key)) + { + __Items.Add(__Value, new Dictionary()); + __CurrentItem = __Value; + } + + if (!(String.Equals(__Key, "Item", StringComparison.OrdinalIgnoreCase) & String.Equals(__Value, "Value", StringComparison.OrdinalIgnoreCase))) + { + __Items[__CurrentItem].Add(__Key, __Value); + } + } + } + + return __Items; + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/CommandLineInterfaceConfiguration.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/CommandLineInterfaceConfiguration.cs new file mode 100644 index 0000000..f2716d5 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/CommandLineInterfaceConfiguration.cs @@ -0,0 +1,11 @@ +using System; + +namespace Logixware.SoftEther.Client.Shell +{ + public class CommandLineInterfaceConfiguration + { + public String PathToClient { get; set; } + public String PathToCli { get; set; } + public String CliPassword { get; set; } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/Device.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/Device.cs new file mode 100644 index 0000000..9b03705 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/Device.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Logixware.SoftEther.Client.VpnService +{ + public class Device : QueryResult + { + public Device(ICommandLineInterface cli, Dictionary data) + : base(cli, data) + { + } + + public String Name => base.Get("Virtual Network Adapter Name"); + public String PhysicalAddress => base.Get("MAC Address"); + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/ICommandLineInterface.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/ICommandLineInterface.cs new file mode 100644 index 0000000..44ec202 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/ICommandLineInterface.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace Logixware.SoftEther.Client.VpnService +{ + public interface ICommandLineInterface + { + IEnumerable GetAccounts(); + Account GetAccount(String name); + + AccountStatus GetAccountStatus(String name); + + IEnumerable GetDevices(); + Device GetDevice(String name); + + void RestartClient(); + void StartClient(); + void StopClient(); + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/QueryResult.cs b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/QueryResult.cs new file mode 100644 index 0000000..48f6ca8 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Core/Logixware/SoftEther/Client/VpnService/QueryResult.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; + +namespace Logixware.SoftEther.Client.VpnService +{ + public abstract class QueryResult + { + private readonly ICommandLineInterface _Cli; + private readonly Dictionary _Data; + + protected QueryResult(ICommandLineInterface cli, Dictionary data) + { + this._Cli = cli ?? throw new ArgumentNullException(nameof(cli)); + this._Data = data ?? throw new ArgumentNullException(nameof(data)); + } + + protected ICommandLineInterface Cli => this._Cli; + protected Dictionary Data => this._Data; + + protected String Get(String key) + { + return !this._Data.ContainsKey(key) ? String.Empty : this._Data[key]; + } + + public override Boolean Equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (!(obj is QueryResult __Other)) + { + return false; + } + + if (this._Data.Count != __Other._Data.Count) + { + return false; + } + + foreach (var __Pair in this._Data) + { + if (__Other._Data.TryGetValue(__Pair.Key, out var __Value)) + { + if (__Value != __Pair.Value) + { + return false; + } + } + else + { + return false; + } + } + + return true; + } + + public override Int32 GetHashCode() + { + return this._Data.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/.gitignore b/src/Logixware.SoftEther.Client.Daemon/.gitignore new file mode 100644 index 0000000..cbc77e0 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/.gitignore @@ -0,0 +1,257 @@ +## Custom + +*.DS_Store +.idea + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +# .idea/ +# *.sln.iml diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware.SoftEther.Client.Daemon.csproj b/src/Logixware.SoftEther.Client.Daemon/Logixware.SoftEther.Client.Daemon.csproj new file mode 100644 index 0000000..247f297 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware.SoftEther.Client.Daemon.csproj @@ -0,0 +1,35 @@ + + + Exe + netcoreapp2.1 + 7.2 + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + + + + \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ClientConfiguration.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ClientConfiguration.cs new file mode 100644 index 0000000..2f14f1b --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ClientConfiguration.cs @@ -0,0 +1,235 @@ +using System; +using System.Net; +using System.Linq; +using System.Collections.Generic; + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; + +using Logixware.SoftEther.Client.VpnService; + +namespace Logixware.SoftEther.Client.Daemon +{ + public class ClientConfiguration : IClientConfiguration + { + private readonly ILogger _Logger; + private readonly ClientConfigurationSection _ProvidedConfiguration; + + public ClientConfiguration + ( + ILogger logger, + IConfigurationRoot configurationRoot + ) + { + if (configurationRoot == null) throw new ArgumentNullException(nameof(configurationRoot)); + this._Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + var __Section = configurationRoot.GetSection("VPN"); + + if (__Section == null) + { + throw new InvalidOperationException("No VPN configuration section."); + } + + this._ProvidedConfiguration = __Section.Get(); + + if (this._ProvidedConfiguration == null) + { + throw new InvalidOperationException("No VPN configuration found."); + } + + if (this._ProvidedConfiguration.Networks == null || this._ProvidedConfiguration.Networks.ToList().Count == 0) + { + throw new InvalidOperationException("No virtual networks defined."); + } + + this.Settings = new ClientConfigurationSection + { + Networks = new List() + }; + + if (String.IsNullOrEmpty(this._ProvidedConfiguration.InternetConnectionTestUrl)) + { + this.Settings.InternetConnectionTestUrl = "http://clients3.google.com/generate_204"; + this._Logger.Inform($"\"InternetConnectionTestUrl\" not defined. Using \"{this.Settings.InternetConnectionTestUrl}\"."); + } + else + { + this.Settings.InternetConnectionTestUrl = this._ProvidedConfiguration.InternetConnectionTestUrl; + } + + if (this._ProvidedConfiguration.ConnectionAttemptsBeforeClientRestart == 0) + { + this.Settings.ConnectionAttemptsBeforeClientRestart = 10; + this._Logger.Inform($"\"ConnectionAttemptsBeforeClientRestart\" not defined. Using \"{this.Settings.ConnectionAttemptsBeforeClientRestart}\"."); + } + else + { + this.Settings.ConnectionAttemptsBeforeClientRestart = this._ProvidedConfiguration.ConnectionAttemptsBeforeClientRestart; + } + + if (this._ProvidedConfiguration.Interval == 0) + { + this.Settings.Interval = 5000; + this._Logger.Inform($"\"Interval\" not defined. Using \"{this.Settings.Interval / 1000}\" (seconds)."); + } + else + { + this.Settings.Interval = this._ProvidedConfiguration.Interval; + } + } + + public ClientConfigurationSection Settings { get; } + + public IEnumerable GetValidNetworks(ICommandLineInterface cli) + { + var __Accounts = cli.GetAccounts().ToList(); + + foreach (var __Network in this._ProvidedConfiguration.Networks) + { + var __ValidationResult = this.ValidateVirtualNetworkConfiguration(__Network); + + if (!__ValidationResult) + { + this._Logger.Error($"Virtual network configuration \"{__Network.Name}\" invalid. This network will not be monitored."); + } + else if (__Accounts.SingleOrDefault(i => String.Equals(__Network.Name, i.Name)) == null) + { + this._Logger.Warn($"Account \"{__Network.Name}\" not found in VPN client service."); + this.Settings.Networks.Add(__Network); + } + else + { + this.Settings.Networks.Add(__Network); + } + } + + return this.Settings.Networks; + } + + private Boolean ValidateVirtualNetworkConfiguration(VirtualNetwork network) + { + if (String.IsNullOrEmpty(network.ConnectionTestHost)) + { + this._Logger.Warn($"VPN \"{network.Name}\": No connection test host defined."); + return false; + } + + if (network.IPv4 == null & network.IPv6 == null) + { + this._Logger.Warn($"VPN \"{network.Name}\": Neither IPv4 nor IPv6 address defined."); + return false; + } + + return this.ValidateIPv4(network) && this.ValidateIPv6(network); + } + + private Boolean ValidateIPv4(VirtualNetwork network) + { + if (network.IPv4 == null) + { + return true; + } + + ; + + if (!IPAddress.TryParse(network.IPv4.Address, out var __IPAddress)) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv4 address \"{network.IPv4.Address}\" cound not be parsed."); + return false; + } + + if (!IPAddress.TryParse(network.IPv4.Mask, out var __IPMask)) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv4 netmask \"{network.IPv4.Mask}\" cound not be parsed."); + return false; + } + + return this.ValidateIPv4Routes(network); + } + + private Boolean ValidateIPv6(VirtualNetwork network) + { + if (network.IPv6 == null) + { + return true; + } + + if (!IPAddress.TryParse(network.IPv6.Address, out var __IPAddress)) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv6 address \"{network.IPv6.Address}\" cound not be parsed."); + return false; + } + + if (network.IPv6.Prefix < 1 || network.IPv6.Prefix > 128) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv6 address prefix invalid: \"{network.IPv6.Prefix}\". Values needs to be between 1 and 128."); + return false; + } + + return this.ValidateIPv6Routes(network); + } + + private Boolean ValidateIPv4Routes(VirtualNetwork network) + { + if (network.IPv4.Routes == null) + { + return true; + } + + foreach (var __Route in network.IPv4.Routes) + { + if (!IPAddress.TryParse(__Route.Network, out var __IPAddress)) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv4 route target network address \"{__Route.Network}\" could not be parsed."); + return false; + } + + if (__Route.Prefix < 8 || __Route.Prefix > 30) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv4 route target network prefix invalid: \"{__Route.Prefix}\". Values needs to be between 8 and 30."); + return false; + } + + if (!IPAddress.TryParse(__Route.Gateway, out var __Gateway)) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv4 route gateway address \"{__Route.Gateway}\" could not be parsed."); + return false; + } + } + + return true; + } + + private Boolean ValidateIPv6Routes(VirtualNetwork network) + { + if (network.IPv4.Routes == null) + { + return true; + } + + foreach (var __Route in network.IPv6.Routes) + { + if (!IPAddress.TryParse(__Route.Network, out var __IPAddress)) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv6 route target network address \"{__Route.Network}\" could not be parsed."); + return false; + } + + if (!IPAddress.TryParse(__Route.Gateway, out var __Gateway)) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv6 route gateway address \"{__Route.Gateway}\" could not be parsed."); + return false; + } + + if (__Route.Prefix < 1 || __Route.Prefix > 128) + { + this._Logger.Warn($"VPN \"{network.Name}\": IPv6 route target network prefix invalid: \"{__Route.Prefix}\". Values needs to be between 1 and 128."); + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ClientConfigurationSection.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ClientConfigurationSection.cs new file mode 100644 index 0000000..1a57441 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ClientConfigurationSection.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace Logixware.SoftEther.Client.Daemon +{ + public class ClientConfigurationSection + { + public Int32 Interval { get; set; } + public Int32 ConnectionAttemptsBeforeClientRestart { get; set; } + public String InternetConnectionTestUrl { get; set; } + public IList Networks { get; set; } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ConfigurationState.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ConfigurationState.cs new file mode 100644 index 0000000..8fba3d1 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ConfigurationState.cs @@ -0,0 +1,8 @@ +namespace Logixware.SoftEther.Client.Daemon +{ + public enum ConfigurationState + { + Error, + OK + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ConnectionVerificationResult.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ConnectionVerificationResult.cs new file mode 100644 index 0000000..25bb87f --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ConnectionVerificationResult.cs @@ -0,0 +1,36 @@ +using System; + +namespace Logixware.SoftEther.Client.Daemon +{ + public class ConnectionVerificationResult + { + public ConnectionVerificationResult(ReachableState reachable, Object details) + { + this.Reachable = reachable; + this.Details = details; + } + + public ReachableState Reachable { get; } + public Object Details { get; } + + public override Boolean Equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (!(obj is ConnectionVerificationResult __Other)) + { + return false; + } + + return Object.Equals(this.Details, __Other.Details) && Object.Equals(this.Details, __Other.Details); + } + + public override Int32 GetHashCode() + { + return HashCode.Combine(this.Details, this.Reachable); + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IClientConfiguration.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IClientConfiguration.cs new file mode 100644 index 0000000..8209b46 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IClientConfiguration.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +using Logixware.SoftEther.Client.VpnService; + +namespace Logixware.SoftEther.Client.Daemon +{ + public interface IClientConfiguration + { + ClientConfigurationSection Settings { get; } + IEnumerable GetValidNetworks(ICommandLineInterface cli); + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IConnectionVerifier.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IConnectionVerifier.cs new file mode 100644 index 0000000..aafe753 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IConnectionVerifier.cs @@ -0,0 +1,9 @@ +using System; + +namespace Logixware.SoftEther.Client.Daemon +{ + public interface IConnectionVerifier + { + ConnectionVerificationResult Verify(String host); + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPlatform.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPlatform.cs new file mode 100644 index 0000000..08d901f --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPlatform.cs @@ -0,0 +1,23 @@ +using System; +using System.Net.NetworkInformation; + +using Logixware.SoftEther.Client.Shell; + +namespace Logixware.SoftEther.Client.Daemon +{ + public interface IPlatform + { + void Initialize(); + + ExecutionResult AssignIPAddress(NetworkInterface networkInterface, IPv4Information info); + ExecutionResult AssignIPAddress(NetworkInterface networkInterface, IPv6Information info); + + ExecutionResult ReleaseIPAddress(NetworkInterface networkInterface, IPv4Information info); + ExecutionResult ReleaseIPAddress(NetworkInterface networkInterface, IPv6Information info); + + ExecutionResult AssignRoute(NetworkInterface networkInterface, IPv4Route info); + ExecutionResult AssignRoute(NetworkInterface networkInterface, IPv6Route info); + ExecutionResult ReleaseRoute(NetworkInterface networkInterface, IPv4Route info); + ExecutionResult ReleaseRoute(NetworkInterface networkInterface, IPv6Route info); + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv4Information.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv4Information.cs new file mode 100644 index 0000000..901b5ae --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv4Information.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Logixware.SoftEther.Client.Daemon +{ + // ReSharper disable once InconsistentNaming + public class IPv4Information + { + public String Address { get; set; } + public String Mask { get; set; } + + public IList Routes { get; set; } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv4Route.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv4Route.cs new file mode 100644 index 0000000..16f7b24 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv4Route.cs @@ -0,0 +1,17 @@ +using System; + +namespace Logixware.SoftEther.Client.Daemon +{ + // ReSharper disable once InconsistentNaming + public class IPv4Route + { + public String Network { get; set; } + public Int32 Prefix { get; set; } + public String Gateway { get; set; } + + public override String ToString() + { + return $"{this.Network}/{this.Prefix}"; + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv6Information.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv6Information.cs new file mode 100644 index 0000000..e5704e9 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv6Information.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Logixware.SoftEther.Client.Daemon +{ + // ReSharper disable once InconsistentNaming + public class IPv6Information + { + public String Address { get; set; } + public Int32 Prefix { get; set; } + + public IList Routes { get; set; } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv6Route.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv6Route.cs new file mode 100644 index 0000000..1c151a0 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/IPv6Route.cs @@ -0,0 +1,17 @@ +using System; + +namespace Logixware.SoftEther.Client.Daemon +{ + // ReSharper disable once InconsistentNaming + public class IPv6Route + { + public String Network { get; set; } + public Int32 Prefix { get; set; } + public String Gateway { get; set; } + + public override String ToString() + { + return $"{this.Network}/{this.Prefix}"; + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/InternetConnection.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/InternetConnection.cs new file mode 100644 index 0000000..a9e8256 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/InternetConnection.cs @@ -0,0 +1,41 @@ +using System; +using System.Net; +using System.Threading.Tasks; + +namespace Logixware.SoftEther.Client.Daemon +{ + public class InternetConnection + { + public static Boolean IsAvailible(String url) + { + try + { + using (var __Client = new WebClient()) + using (__Client.OpenRead(url)) + { + return true; + } + } + catch + { + return false; + } + } + + public static async Task IsAvailibleAsync(String url) + { + try + { + using (var __Client = new WebClient()) + using (await __Client.OpenReadTaskAsync(url).ConfigureAwait(false)) + { + return true; + } + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/PingConnectionVerifier.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/PingConnectionVerifier.cs new file mode 100644 index 0000000..27b12a3 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/PingConnectionVerifier.cs @@ -0,0 +1,46 @@ +using System; +using System.Text; +using System.Net.NetworkInformation; + +namespace Logixware.SoftEther.Client.Daemon +{ + public class PingConnectionVerifier : IConnectionVerifier + { + private IClientConfiguration _Configuration; + + public PingConnectionVerifier(IClientConfiguration configuration) + { + this._Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + } + public ConnectionVerificationResult Verify(String host) + { + const String __Data = "a quick brown fox jumped over the lazy dog"; + const Int32 __Timeout = 1024; + + var __PingSender = new Ping(); + var __Options = new PingOptions + { + DontFragment = true + }; + + var __Buffer = Encoding.ASCII.GetBytes(__Data); + var __Status = IPStatus.Unknown; + + try + { + var __Reply = __PingSender.Send(host, __Timeout, __Buffer, __Options); + __Status = __Reply?.Status ?? IPStatus.Unknown; + } + catch + { + __Status = IPStatus.Unknown; + } + + return new ConnectionVerificationResult + ( + __Status == IPStatus.Success ? ReachableState.Reachable : ReachableState.Unreachable, + __Status + ); + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/Platform/MacPlatform.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/Platform/MacPlatform.cs new file mode 100644 index 0000000..fd35e95 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/Platform/MacPlatform.cs @@ -0,0 +1,184 @@ +using System; +using System.Threading; +using System.Net.NetworkInformation; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +using Logixware.SoftEther.Client.Shell; + +//using System.Runtime.InteropServices; + +namespace Logixware.SoftEther.Client.Daemon.Platform +{ + + public class MacPlatform : IPlatform + { + private readonly ILogger _Logger; + private readonly IShell _Shell; + + private readonly MacPlatformConfigurationSection _Configuration; + + public MacPlatform + ( + ILogger logger, + IConfiguration configuration, + IShell shell + ) + { + if (configuration == null) throw new ArgumentNullException(nameof(configuration)); + + this._Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this._Shell = shell ?? throw new ArgumentNullException(nameof(shell)); + + this._Configuration = configuration.GetSection("VPN:Platform:Mac")?.Get() ?? new MacPlatformConfigurationSection(); + + if (String.IsNullOrEmpty(this._Configuration.TapKextIdentifier)) + { + this._Configuration.TapKextIdentifier = "net.sf.tuntaposx.tap"; + this._Logger.Inform($"\"TapKextIdentifier\" not defined. Using \"{this._Configuration.TapKextIdentifier}\"."); + } + + if (String.IsNullOrEmpty(this._Configuration.PathToTapKext)) + { + this._Configuration.PathToTapKext = "/Library/Extensions/tap.kext"; + this._Logger.Inform($"\"PathToTapKext\" not defined. Using \"{this._Configuration.PathToTapKext}\"."); + } + } + + public void Initialize() + { + if (!this.IsDriverLoaded()) + { + this._Logger.Inform($"Kernel extension \"{this._Configuration.TapKextIdentifier}\" not loaded."); + this._Logger.Inform($"Loading Kernel extension \"{this._Configuration.PathToTapKext}\"."); + + var __Command = $"kextload \"{this._Configuration.PathToTapKext}\""; + var __Response = this._Shell.ExecuteCommand(__Command); + + if (!__Response.Succeeded) + { + throw new Exception(__Response.Result); + } + + while (!this.IsDriverLoaded()) + { + Thread.Sleep(1000); + this._Logger.Inform($"Kernel extension \"{this._Configuration.TapKextIdentifier}\" still not loaded."); + } + } + } + + private Boolean IsDriverLoaded() + { + var __Command = $"kextstat | grep \"{this._Configuration.TapKextIdentifier}\""; + var __Response = this._Shell.ExecuteCommand(__Command); + + if (!__Response.Succeeded) + { + throw new Exception(__Response.Result); + } + + return !String.IsNullOrEmpty(__Response.Result) && __Response.Result.Contains(this._Configuration.TapKextIdentifier) && !__Response.Result.Contains("failed to load"); + } + + public ExecutionResult AssignIPAddress(NetworkInterface networkInterface, IPv4Information info) + { + var __Command = $"ifconfig {networkInterface.Name} inet {info.Address} netmask {info.Mask}"; + return this._Shell.ExecuteCommand(__Command); + } + + public ExecutionResult AssignIPAddress(NetworkInterface networkInterface, IPv6Information info) + { + var __Command = $"ifconfig {networkInterface.Name} inet6 {info.Address} prefixlen {info.Prefix}"; + return this._Shell.ExecuteCommand(__Command); + } + + public ExecutionResult ReleaseIPAddress(NetworkInterface networkInterface, IPv4Information info) + { + var __Command = $"ifconfig {networkInterface.Name} inet {info.Address} delete"; + return this._Shell.ExecuteCommand(__Command); + } + + public ExecutionResult ReleaseIPAddress(NetworkInterface networkInterface, IPv6Information info) + { + var __Command = $"ifconfig {networkInterface.Name} inet6 {info.Address}/{info.Prefix} delete"; + return this._Shell.ExecuteCommand(__Command); + } + + public ExecutionResult AssignRoute(NetworkInterface networkInterface, IPv4Route info) + { + var __Command = $"route -n add -net {info.Network}/{info.Prefix} {info.Gateway}"; + return this._Shell.ExecuteCommand(__Command); + } + + public ExecutionResult AssignRoute(NetworkInterface networkInterface, IPv6Route info) + { + var __Command = $"route -n add -net {info.Network}/{info.Prefix} {info.Gateway}"; + return this._Shell.ExecuteCommand(__Command); + } + + public ExecutionResult ReleaseRoute(NetworkInterface networkInterface, IPv4Route info) + { + var __Command = $"route -n delete -net {info.Network}/{info.Prefix} {info.Gateway}"; + return this._Shell.ExecuteCommand(__Command); + } + + public ExecutionResult ReleaseRoute(NetworkInterface networkInterface, IPv6Route info) + { + var __Command = $"route -n delete -net {info.Network}/{info.Prefix} {info.Gateway}"; + return this._Shell.ExecuteCommand(__Command); + } + +// [DllImport("/System/Library/Frameworks/IOKit.framework/IOKit")] +// public static extern Int32 IORegisterForSystemPower(IntPtr refcon, ref Int32 thePortRef, MyCallback func, ref Int32 notifier); +// +//// CFRunLoopRef CFRunLoopGetCurrent(void); +// [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")] +// extern static Int32 CFRunLoopGetCurrent(); +// +//// void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode); +// [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")] +// extern static void CFRunLoopAddSource(ref Int32 rl, ref Int32 source, IntPtr e); +// +//// CFRunLoopSourceRef IONotificationPortGetRunLoopSource(IONotificationPortRef notify); +// [DllImport("/System/Library/Frameworks/IOKit.framework/IOKit")] +// public static extern Int32 IONotificationPortGetRunLoopSource(ref Int32 notify); +// +// [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", CharSet = CharSet.Unicode)] +// extern static IntPtr CFStringCreateWithCharacters(IntPtr allocator, string str, Int32 count); +// +//// void CFRunLoopRun(void); +// [DllImport("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")] +// extern static void CFRunLoopRun(); +// +// // void MySleepCallBack( void * refCon, io_service_t service, natural_t messageType, void * messageArgument ) +// public delegate void MyCallback(IntPtr refCon, Int32 service, Int32 messageType, IntPtr messageArgument); +// +// public static void MySleepCallBack(IntPtr refCon, Int32 service, Int32 messageType, IntPtr messageArgument) +// { +// Console.WriteLine("REC: " + messageType); +// } +// +// public MacPlatform() +// { +// IntPtr e = new IntPtr(); +// IntPtr e2 = new IntPtr(); +// var __NotifyPortRef = 0; +// var notifier = 0; +// +// var yillmazz = IORegisterForSystemPower(e, ref __NotifyPortRef, MySleepCallBack, ref notifier); +// +// var __RunLoopRef = CFRunLoopGetCurrent(); +// var __RunLoopSourceRef = IONotificationPortGetRunLoopSource(ref __NotifyPortRef); +// +// IntPtr handle = CFStringCreateWithCharacters(IntPtr.Zero, "kCFRunLoopCommonModes", "kCFRunLoopCommonModes".Length); +// +// CFRunLoopAddSource(ref __RunLoopRef, ref __RunLoopSourceRef, handle); +// +//// CFRunLoopRun(); +// +// Console.WriteLine("kk"); +// } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/Platform/MacPlatformConfigurationSection.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/Platform/MacPlatformConfigurationSection.cs new file mode 100644 index 0000000..f0551d2 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/Platform/MacPlatformConfigurationSection.cs @@ -0,0 +1,10 @@ +using System; + +namespace Logixware.SoftEther.Client.Daemon.Platform +{ + public class MacPlatformConfigurationSection + { + public String TapKextIdentifier { get; set; } + public String PathToTapKext { get; set; } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/Program.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/Program.cs new file mode 100644 index 0000000..ecbb82d --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/Program.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using System.Runtime.InteropServices; + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +using Logixware.SoftEther.Client.VpnService; +using Logixware.SoftEther.Client.Daemon.Platform; + +using Logixware.SoftEther.Client.Shell; +using Logixware.SoftEther.Client.Shell.Platform; + +namespace Logixware.SoftEther.Client.Daemon +{ + public class Program + { + // ReSharper disable once AsyncConverter.AsyncMethodNamingHighlighting + public static async Task Main(String[] args) + { + IConfigurationRoot __ConfigurationRoot = null; + + var host = new HostBuilder() + + .ConfigureHostConfiguration(configHost => + { + configHost.SetBasePath(Directory.GetCurrentDirectory()); + configHost.AddJsonFile("hostsettings.json", optional: true); + configHost.AddEnvironmentVariables(prefix: "PREFIX_"); + configHost.AddCommandLine(args); + }) + + .ConfigureAppConfiguration((hostContext, configApp) => + { + configApp.AddJsonFile("appsettings.json", optional: true); + configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true); + configApp.AddEnvironmentVariables(prefix: "PREFIX_"); + configApp.AddCommandLine(args); + + __ConfigurationRoot = configApp.Build(); + }) + + .ConfigureServices((hostContext, services) => + { + services.AddLogging(); + services.AddHostedService(); + services.AddSingleton(services); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddSingleton(__ConfigurationRoot); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + services.AddSingleton(); + services.AddSingleton(); + } + else + { + throw new NotSupportedException("Platform not supported."); + } + }) + + .ConfigureLogging((hostContext, configLogging) => + { + configLogging.AddConsole(); + configLogging.AddDebug(); + }) + + .UseConsoleLifetime() + .Build(); + + await host.RunAsync() + + .ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ProgramService.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ProgramService.cs new file mode 100644 index 0000000..f4c3dda --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ProgramService.cs @@ -0,0 +1,257 @@ +using System; +using System.Linq; + +using System.Threading; +using System.Threading.Tasks; + +using System.Reactive.Subjects; + +using System.Collections.Generic; + +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +using Logixware.SoftEther.Client.VpnService; + +namespace Logixware.SoftEther.Client.Daemon +{ + public class ProgramService : IHostedService + { + private readonly ILogger _Logger; + private readonly ILogger _NetworkLogger; + private readonly IApplicationLifetime _AppLifetime; + private readonly IClientConfiguration _Configuration; + private readonly ICommandLineInterface _Cli; + private readonly IConnectionVerifier _ConnectionVerifier; + private readonly IPlatform _Platform; + + private readonly Subject _ClientServiceRestarting; + private readonly Subject _ClientServiceRestarted; + + private readonly Dictionary _Networks; + private Boolean? _IsInternetConnected; + private Boolean _Run; + + private Timer _Timer; + + public ProgramService + ( + ILogger logger, + ILogger networklogger, + IApplicationLifetime appLifetime, + IClientConfiguration configuration, + ICommandLineInterface cli, + IConnectionVerifier connectionVerifier, + IPlatform platform + ) + { + this._Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this._NetworkLogger = networklogger ?? throw new ArgumentNullException(nameof(networklogger)); + this._AppLifetime = appLifetime ?? throw new ArgumentNullException(nameof(appLifetime)); + this._Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + this._Cli = cli ?? throw new ArgumentNullException(nameof(cli)); + this._ConnectionVerifier = connectionVerifier ?? throw new ArgumentNullException(nameof(connectionVerifier)); + this._Platform = platform ?? throw new ArgumentNullException(nameof(platform)); + + this._ClientServiceRestarting = new Subject(); + this._ClientServiceRestarted = new Subject(); + + this._Networks = new Dictionary(); + this._IsInternetConnected = null; + this._Run = false; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + this._AppLifetime.ApplicationStarted.Register(this.OnStarted); + this._AppLifetime.ApplicationStopping.Register(this.OnStopping); + this._AppLifetime.ApplicationStopped.Register(this.OnStopped); + + return Task.CompletedTask; + } + + private void OnStarted() + { + this._Logger.Inform("Starting application..."); + + var __StartTimeSpan = TimeSpan.Zero; + var __PeriodTimeSpan = TimeSpan.FromSeconds(5); + + var __ValidNetworks = this._Configuration.GetValidNetworks(this._Cli).ToList(); + + if (__ValidNetworks.Count == 0) + { + this._Logger.Critical("No valid network found."); + this._AppLifetime.StopApplication(); + + return; + } + + this._Logger.Inform("Initializing platform."); + this._Platform.Initialize(); + + this._Logger.Inform("Starting VPN client service..."); + this._Cli.StartClient(); + + foreach (var __Network in __ValidNetworks) + { + this._Networks.Add(new VirtualNetworkService( + + this._NetworkLogger, + this._Cli, + this._ConnectionVerifier, + this._Platform, + this._ClientServiceRestarting, + this._ClientServiceRestarted, + __Network + + ), 0); + } + + this._Run = true; + this._Timer = new Timer(this.Tick, null, __StartTimeSpan, __PeriodTimeSpan); + } + + private async void Tick(Object parameter) + { + if (!this._Run) + { + this._Timer.Dispose(); + this._AppLifetime.StopApplication(); + + return; + } + + if (await InternetConnection.IsAvailibleAsync(this._Configuration.Settings.InternetConnectionTestUrl).ConfigureAwait(false)) + { + if (this._IsInternetConnected == null || !(Boolean) this._IsInternetConnected) + { + this._IsInternetConnected = true; + this._Logger.Inform("Connected to the internet"); + } + + var __Results = new List(); + + foreach (var __VirtualNetworkService in this._Networks.Keys.ToList()) + { + __Results.Add(await __VirtualNetworkService.IsReachableAsync().ConfigureAwait(false)); + } + + var __ReachableServices = __Results + + .Where(r => r.Configuration != ConfigurationState.Error) + .Where(r => r.Connection == ConnectionState.Connected) + .Where(r => r.Reachable == ReachableState.Reachable) + .ToList(); + + var __DisconnectedServices = __Results + + .Where(r => r.Configuration != ConfigurationState.Error) + .Where(r => r.Connection == ConnectionState.Disconnected) + .ToList(); + + var __NonReachableServices = __Results + + .Where(r => r.Network.Configuration.AlwaysOn) + .Where(r => r.Configuration != ConfigurationState.Error) + .Where(r => r.Connection != ConnectionState.Disconnected) + .Where(r => r.Reachable == ReachableState.Unreachable) + .ToList(); + + var __ServicesThatShouldBeReachable = __Results + + .Where(r => r.Network.Configuration.AlwaysOn) + .Where(r => r.Configuration != ConfigurationState.Error) + .Where(r => r.Connection != ConnectionState.Disconnected) + .ToList(); + + __ServicesThatShouldBeReachable.ForEach(s => this._Networks[s.Network] += 1); + + this.ResetAttemptCounters(__ReachableServices.Select(x => x.Network)); + + var __OverdueNetworks = this._Networks + + .ToList() + .Where(p => p.Value >= this._Configuration.Settings.ConnectionAttemptsBeforeClientRestart) + .Select(p => p.Key) + .ToList(); + + __NonReachableServices.ForEach(r => + { + this._Logger.Error($"VPN \"{r.Network.Configuration.Name}\": Connection test host \"{r.Network.Configuration.ConnectionTestHost}\" did not respond {this._Networks[r.Network]} times."); + }); + + if (__ReachableServices.Count == 0 && __DisconnectedServices.Count == 0) + { + this.RestartClientService(); + } + else if (__ReachableServices.Count == 0 && __OverdueNetworks.Count > 0 && __OverdueNetworks.Count >= __ServicesThatShouldBeReachable.Count) + { + this.RestartClientService(); + } + } + else + { + if (this._IsInternetConnected == null || (Boolean) this._IsInternetConnected) + { + this._IsInternetConnected = false; + this._Logger.Inform("Not connected to the internet"); + } + + this.ResetAttemptCounters(this._Networks.Keys); + } + } + + private void ResetAttemptCounters(IEnumerable items) + { + foreach (var __VirtualNetwork in items.ToList()) + { + this._Networks[__VirtualNetwork] = 0; + } + } + + private void RestartClientService() + { + this._Logger.Warn("Restarting the VPN client..."); + + this._ClientServiceRestarting.OnNext(null); + + this._Cli.RestartClient(); + this.ResetAttemptCounters(this._Networks.Keys); + + this._ClientServiceRestarted.OnNext(null); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + this._Logger.Inform("Shutting down application..."); + this._Run = false; + + return Task.CompletedTask; + } + + private void OnStopping() + { + this._Logger.Inform("Stoppping VPN client service..."); + this._Cli.StopClient(); + } + + private void OnStopped() + { + this._Logger.Inform("Application shut down. C ya."); + } + +// private Task Sleep(int millisecondsTimeout) +// { +// var taskCompletionSource = new TaskCompletionSource(); +// +// ThreadPool.QueueUserWorkItem((state) => +// { +// Thread.Sleep(millisecondsTimeout); +// taskCompletionSource.SetResult(true); +// }, null); +// +// return taskCompletionSource.Task; +// } + } +} diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ReachableResult.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ReachableResult.cs new file mode 100644 index 0000000..0fc7f6f --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ReachableResult.cs @@ -0,0 +1,36 @@ +using System; + +namespace Logixware.SoftEther.Client.Daemon +{ + public struct ReachableResult + { + private readonly VirtualNetworkService _Network; + private readonly ConfigurationState _Configuration; + private readonly ConnectionState _Connection; + private readonly ReachableState _Reachable; + + public ReachableResult + ( + VirtualNetworkService network, + ConfigurationState configuration, + ConnectionState connection, + ReachableState reachable + ) + { + this._Network = network; + this._Configuration = configuration; + this._Connection = connection; + this._Reachable = reachable; + } + + public VirtualNetworkService Network => this._Network; + public ConfigurationState Configuration => this._Configuration; + public ConnectionState Connection => this._Connection; + public ReachableState Reachable => this._Reachable; + + public override String ToString() + { + return $"{this._Network.Configuration.Name}, {this._Configuration}, {this._Connection}, {this._Reachable}"; + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ReachableState.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ReachableState.cs new file mode 100644 index 0000000..a8b11f6 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/ReachableState.cs @@ -0,0 +1,8 @@ +namespace Logixware.SoftEther.Client.Daemon +{ + public enum ReachableState + { + Reachable, + Unreachable + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/VirtualNetwork.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/VirtualNetwork.cs new file mode 100644 index 0000000..e388453 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/VirtualNetwork.cs @@ -0,0 +1,18 @@ +using System; + +namespace Logixware.SoftEther.Client.Daemon +{ + public class VirtualNetwork + { + public String Name { get; set; } + public Boolean AlwaysOn { get; set; } + + public String ConnectionTestHost { get; set; } + + // ReSharper disable once InconsistentNaming + public IPv4Information IPv4 { get; set; } + + // ReSharper disable once InconsistentNaming + public IPv6Information IPv6 { get; set; } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/VirtualNetworkService.cs b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/VirtualNetworkService.cs new file mode 100644 index 0000000..fa47071 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/Logixware/SoftEther/Client/Daemon/VirtualNetworkService.cs @@ -0,0 +1,600 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Net.NetworkInformation; + +using System.Reactive.Linq; +using System.Reactive.Subjects; + +using Microsoft.Extensions.Logging; + +using Logixware.SoftEther.Client.VpnService; + +namespace Logixware.SoftEther.Client.Daemon +{ + public class VirtualNetworkService : IDisposable + { + private readonly ILogger _Logger; + private readonly ICommandLineInterface _Cli; + private readonly IConnectionVerifier _ConnectionVerifier; + private readonly IPlatform _Platform; + + private readonly Subject _IsDisposed; + + private readonly BehaviorSubject _InterfaceName; + private readonly BehaviorSubject _Account; + private readonly BehaviorSubject _Device; + + private readonly BehaviorSubject _IPv4AddressAssigned; + private readonly BehaviorSubject _IPv6AddressAssigned; + + private readonly BehaviorSubject _IPv4RoutesAssigned; + private readonly BehaviorSubject _IPv6RoutesAssigned; + + private readonly BehaviorSubject _ConnectionVerificationResult; + + private readonly BehaviorSubject _ConfigurationState; + private readonly BehaviorSubject _ConnectionState; + private readonly BehaviorSubject _ReachableState; + + public VirtualNetworkService + ( + ILogger logger, + ICommandLineInterface cli, + IConnectionVerifier connectionVerifier, + IPlatform platform, + Subject clientServiceRestarting, + Subject clientServiceRestarted, + VirtualNetwork network + ) + { + if (clientServiceRestarting == null) throw new ArgumentNullException(nameof(clientServiceRestarting)); + if (clientServiceRestarted == null) throw new ArgumentNullException(nameof(clientServiceRestarted)); + + this._Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + this._Cli = cli ?? throw new ArgumentNullException(nameof(cli)); + this._ConnectionVerifier = connectionVerifier ?? throw new ArgumentNullException(nameof(connectionVerifier)); + this._Platform = platform ?? throw new ArgumentNullException(nameof(platform)); + this.Configuration = network ?? throw new ArgumentNullException(nameof(network)); + + this._IsDisposed = new Subject(); + + var __IsInitialized = new Subject(); + + clientServiceRestarting + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .Subscribe(this.OnClientServiceRestarting); + + clientServiceRestarted + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .Subscribe(this.OnClientServiceRestarted); + + this._Account = new BehaviorSubject(null); + this._Account + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .DistinctUntilChanged() + .Subscribe(this.OnAccountFoundChanged); + + this._Device = new BehaviorSubject(null); + this._Device + + .SkipUntil(__IsInitialized) + .TakeUntil(this._IsDisposed) + .DistinctUntilChanged() + .Subscribe(this.OnDeviceFoundChanged); + + this._InterfaceName = new BehaviorSubject(null); + this._InterfaceName + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .DistinctUntilChanged() + .Subscribe(this.OnInterfaceFoundChanged); + + this._IPv4AddressAssigned = new BehaviorSubject(null); + this._IPv4AddressAssigned + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .NotNull() + .DistinctUntilChanged() + .Subscribe(this.OnIPv4AddressAssignedChanged, this.OnIPv4AddressAssignmentError); + + this._IPv6AddressAssigned = new BehaviorSubject(null); + this._IPv6AddressAssigned + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .NotNull() + .DistinctUntilChanged() + .Subscribe(this.OnIPv6AddressAssignedChanged, this.OnIPv6AddressAssignmentError); + + this._IPv4RoutesAssigned = new BehaviorSubject(null); + this._IPv4RoutesAssigned + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .NotNull() + .DistinctUntilChanged() + .Subscribe(this.OnIPv4RoutesAppliedChanged, this.OnIPv4RoutesAssignmentError); + + this._IPv6RoutesAssigned = new BehaviorSubject(null); + this._IPv6RoutesAssigned + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .NotNull() + .DistinctUntilChanged() + .Subscribe(this.OnIPv6RoutesAppliedChanged, this.OnIPv6RoutesAssignmentError); + + this._ConfigurationState = new BehaviorSubject(null); + this._ConfigurationState + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .NotNull() + .DistinctUntilChanged() + .Subscribe(this.OnConfigurationStateChanged); + + this._ConnectionState = new BehaviorSubject(null); + this._ConnectionState + + .TakeUntil(this._IsDisposed) + .SkipUntil(__IsInitialized) + .NotNull() + .DistinctUntilChanged() + .Subscribe(this.OnConnectionStateChanged); + + this._ReachableState = new BehaviorSubject(null); + this._ReachableState + + .TakeUntil(this._IsDisposed) + .NotNull() + .DistinctUntilChanged() + .Subscribe(this.OnReachableStateChanged); + + this._ConnectionVerificationResult = new BehaviorSubject(null); + this._ConnectionVerificationResult + + .TakeUntil(this._IsDisposed) + .NotNull() + .DistinctUntilChanged() + .Subscribe(this.OnConnectionVerificationResultChanged); + + __IsInitialized.OnNext(null); + } + + public VirtualNetwork Configuration { get; } + + private void OnClientServiceRestarting(Object e) + { + this._ConnectionState.OnNext(ConnectionState.Disconnected); + this._ReachableState.OnNext(ReachableState.Unreachable); + } + + private void OnClientServiceRestarted(Object e) + { + } + + private void OnConfigurationStateChanged(ConfigurationState? state) + { + if (state == ConfigurationState.OK) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": Interface configuration completed."); + } + else + { + this._Logger.Warn($"VPN \"{this.Configuration.Name}\": Interface configuration error."); + } + } + + private void OnConnectionStateChanged(ConnectionState? state) + { + if (state == ConnectionState.Connected) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": Connection is established."); + this.AssignIPAddresses(); + } + else + { + this._Logger.Warn($"VPN \"{this.Configuration.Name}\": Connection is not established, State: \"{state}\"."); + this.ReleaseIPAddresses(); + } + } + + private void OnReachableStateChanged(ReachableState? state) + { + if (state == ReachableState.Reachable) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": Network is reachable."); + } + else + { + this._Logger.Warn($"VPN \"{this.Configuration.Name}\": Network is not reachable."); + } + } + + private void OnAccountFoundChanged(Account value) + { + if (value != null) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": Account found in the VPN client service."); + } + else + { + this._Logger.Warn($"VPN \"{this.Configuration.Name}\": Account not found in the VPN client service."); + } + } + + private void OnDeviceFoundChanged(Device value) + { + if (value != null) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": Device \"{this._Account.Value?.DeviceName}\" in the VPN client service found."); + } + else + { + this._Logger.Warn($"VPN \"{this.Configuration.Name}\": Device \"{this._Account.Value?.DeviceName}\" in the VPN client service not found."); + } + } + + private void OnInterfaceFoundChanged(String value) + { + if (!String.IsNullOrEmpty(value)) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": Interface \"{value} found."); + } + else + { + this._Logger.Warn($"VPN \"{this.Configuration.Name}\": No interface for physical address \"{this._Device.Value?.PhysicalAddress}\" found."); + } + } + + private void OnIPv4AddressAssignedChanged(Boolean? value) + { + if (value == null || this.Configuration.IPv4 == null) return; + + if ((Boolean) value) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": IP address \"{this.Configuration.IPv4.Address}\" assigned to adapter \"{this._InterfaceName.Value}\"."); + this.AssignIPv4Routes(); + } + else + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": IP address \"{this.Configuration.IPv4.Address}\" released from adapter \"{this._InterfaceName.Value}\"."); + this.ReleaseIPv4Routes(); + } + } + + private void OnIPv4AddressAssignmentError(Exception ex) + { + if (this.Configuration.IPv4 == null) return; + this._Logger.Error($"VPN \"{this.Configuration.Name}\": Error assigning or releasing IP address \"{this.Configuration.IPv4.Address}\" on adapter \"{this._InterfaceName.Value}\": {ex.Message}"); + } + + private void OnIPv6AddressAssignedChanged(Boolean? value) + { + if (value == null || this.Configuration.IPv6 == null) return; + + if ((Boolean) value) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": IP address \"{this.Configuration.IPv6.Address}/{this.Configuration.IPv6.Prefix}\" assigned to adapter \"{this._InterfaceName.Value}\"."); + this.AssignIPv6Routes(); + } + else + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": IP address \"{this.Configuration.IPv6.Address}/{this.Configuration.IPv6.Prefix}\" released from adapter \"{this._InterfaceName.Value}\"."); + this.ReleaseIPv6Routes(); + } + } + + private void OnIPv6AddressAssignmentError(Exception ex) + { + if (this.Configuration.IPv6 == null) return; + this._Logger.Error($"VPN \"{this.Configuration.Name}\": Error assigning or releasing IP address \"{this.Configuration.IPv6.Address}/{this.Configuration.IPv6.Prefix}\" on adapter \"{this._InterfaceName.Value}\": {ex.Message}"); + } + + private void OnIPv4RoutesAppliedChanged(Boolean? value) + { + if (value == null) return; + + if ((Boolean) value) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": IPv4 routes assigned."); + this.AssignIPv6Routes(); + } + else + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": IPv4 routes released."); + this.ReleaseIPv6Routes(); + } + } + + private void OnIPv4RoutesAssignmentError(Exception ex) + { + this._Logger.Error($"VPN \"{this.Configuration.Name}\": Error assigning or releasing IPv4 routes: {ex.Message}"); + } + + private void OnIPv6RoutesAppliedChanged(Boolean? value) + { + if (value == null) return; + + if ((Boolean) value) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": IPv6 routes assigned."); + this.AssignIPv6Routes(); + } + else + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": IPv6 routes released."); + this.ReleaseIPv6Routes(); + } + } + + private void OnIPv6RoutesAssignmentError(Exception ex) + { + this._Logger.Error($"VPN \"{this.Configuration.Name}\": Error assigning or releasing IPv6 routes: {ex.Message}"); + } + + private void OnConnectionVerificationResultChanged(ConnectionVerificationResult value) + { + if (value == null) return; + if (value.Reachable == ReachableState.Reachable) + { + this._Logger.Inform($"VPN \"{this.Configuration.Name}\": Connection test host \"{this.Configuration.ConnectionTestHost}\" successfully reached: \"{value.Details}\"."); + } + else + { + this._Logger.Error($"VPN \"{this.Configuration.Name}\": Error reaching connection test host \"{this.Configuration.ConnectionTestHost}\": \"{value.Details}\"."); + } + } + + public async Task IsReachableAsync() + { + // ReSharper disable once InconsistentNaming + var ReturnResult = (Func>) (() => Task.FromResult(new ReachableResult( + + this, + // ReSharper disable once PossibleInvalidOperationException + (ConfigurationState) this._ConfigurationState.Value, + // ReSharper disable once PossibleInvalidOperationException + (ConnectionState) this._ConnectionState.Value, + // ReSharper disable once PossibleInvalidOperationException + (ReachableState) this._ReachableState.Value))); + + var __ConfigurationState = await this.ConfigureInterfaceAsync().ConfigureAwait(false); + this._ConfigurationState.OnNext(__ConfigurationState); + + if (__ConfigurationState == ConfigurationState.Error) + { + this._ConfigurationState.OnNext(ConfigurationState.Error); + this._ConnectionState.OnNext(ConnectionState.Disconnected); + this._ReachableState.OnNext(ReachableState.Unreachable); + return await ReturnResult().ConfigureAwait(false); + } + + var __ConnectionState = this._Cli.GetAccountStatus(this.Configuration.Name).ConnectionState; + this._ConnectionState.OnNext(__ConnectionState); + + if (__ConnectionState != ConnectionState.Connected) + { + this._ReachableState.OnNext(ReachableState.Unreachable); + return await ReturnResult().ConfigureAwait(false); + } + + this._ConnectionVerificationResult.OnNext(this._ConnectionVerifier.Verify(this.Configuration.ConnectionTestHost)); + this._ReachableState.OnNext(this._ConnectionVerificationResult.Value.Reachable); + + return await ReturnResult().ConfigureAwait(false); + } + + private async Task ConfigureInterfaceAsync() + { + this._Account.OnNext(this._Cli.GetAccount(this.Configuration.Name)); + + if (this._Account.Value == null) + { + return await Task.FromResult(ConfigurationState.Error).ConfigureAwait(false); + } + + this._Device.OnNext(this._Cli.GetDevice(this._Account.Value.DeviceName)); + + if (this._Device.Value == null) + { + return await Task.FromResult(ConfigurationState.Error).ConfigureAwait(false); + } + + var __Interface = NetworkInterface + + .GetAllNetworkInterfaces() + .SingleOrDefault(i => String.Equals(i.GetPhysicalAddress().ToString(), this._Device.Value.PhysicalAddress, StringComparison.OrdinalIgnoreCase)); + + if (__Interface == null) + { + return await Task.FromResult(ConfigurationState.Error).ConfigureAwait(false); + } + + this._InterfaceName.OnNext(__Interface.Name); + + return await Task.FromResult(ConfigurationState.OK).ConfigureAwait(false); + } + + // ReSharper disable once InconsistentNaming + private void AssignIPAddresses() + { + if (this.Configuration.IPv4 != null && !this.HasAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv4.Address)) + { + var __Execution = this._Platform.AssignIPAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv4); + + if (__Execution.Succeeded) + { + this._IPv4AddressAssigned.OnNext(true); + } + else + { + this._IPv4AddressAssigned.OnError(new Exception(__Execution.Result)); + } + } + + if (this.Configuration.IPv6 != null && !this.HasAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv6.Address)) + { + var __Execution = this._Platform.AssignIPAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv6); + + if (__Execution.Succeeded) + { + this._IPv6AddressAssigned.OnNext(true); + } + else + { + this._IPv6AddressAssigned.OnError(new Exception(__Execution.Result)); + } + } + } + + // ReSharper disable once InconsistentNaming + private void ReleaseIPAddresses() + { + if (this.Configuration.IPv4 != null && this.HasAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv4.Address)) + { + var __Execution = this._Platform.ReleaseIPAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv4); + + if (__Execution.Succeeded) + { + this._IPv4AddressAssigned.OnNext(false); + } + else + { + this._IPv4AddressAssigned.OnError(new Exception(__Execution.Result)); + } + } + + if (this.Configuration.IPv6 != null && this.HasAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv6.Address)) + { + var __Execution = this._Platform.ReleaseIPAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv6); + + if (__Execution.Succeeded) + { + this._IPv6AddressAssigned.OnNext(false); + } + else + { + this._IPv6AddressAssigned.OnError(new Exception(__Execution.Result)); + } + } + } + + private void AssignIPv4Routes() + { + if (this.Configuration.IPv4?.Routes != null && this.Configuration.IPv4.Routes.Count > 0 && this.HasAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv4.Address)) + { + foreach (var __Route in this.Configuration.IPv4.Routes) + { + var __Execution = this._Platform.AssignRoute(this.GetInterface(this._InterfaceName.Value), __Route); + + if (__Execution.Succeeded) continue; + + this._IPv4RoutesAssigned.OnError(new Exception(__Execution.Result)); + return; + } + + this._IPv4RoutesAssigned.OnNext(true); + } + } + + private void ReleaseIPv4Routes() + { + if (this.Configuration.IPv4?.Routes != null && this.Configuration.IPv4.Routes.Count > 0) + { + foreach (var __Route in this.Configuration.IPv4.Routes) + { + var __Execution = this._Platform.ReleaseRoute(this.GetInterface(this._InterfaceName.Value), __Route); + + if (__Execution.Succeeded) continue; + + this._IPv4RoutesAssigned.OnError(new Exception(__Execution.Result)); + return; + } + + this._IPv4RoutesAssigned.OnNext(false); + } + } + + private void AssignIPv6Routes() + { + if (this.Configuration.IPv6?.Routes != null && this.Configuration.IPv6.Routes.Count > 0 && this.HasAddress(this.GetInterface(this._InterfaceName.Value), this.Configuration.IPv6.Address)) + { + foreach (var __Route in this.Configuration.IPv6.Routes) + { + var __Execution = this._Platform.AssignRoute(this.GetInterface(this._InterfaceName.Value), __Route); + + if (__Execution.Succeeded) continue; + + this._IPv6RoutesAssigned.OnError(new Exception(__Execution.Result)); + return; + } + + this._IPv6RoutesAssigned.OnNext(true); + } + } + + private void ReleaseIPv6Routes() + { + if (this.Configuration.IPv6?.Routes != null && this.Configuration.IPv6.Routes.Count > 0) + { + foreach (var __Route in this.Configuration.IPv6.Routes) + { + var __Execution = this._Platform.ReleaseRoute(this.GetInterface(this._InterfaceName.Value), __Route); + + if (__Execution.Succeeded) continue; + + this._IPv6RoutesAssigned.OnError(new Exception(__Execution.Result)); + return; + } + + this._IPv6RoutesAssigned.OnNext(false); + } + } + + private NetworkInterface GetInterface(String name) + { + return NetworkInterface + + .GetAllNetworkInterfaces() + .SingleOrDefault(i => String.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + private Boolean HasAddress(NetworkInterface networkInterface, String address) + { + var __IpAddresses = networkInterface.GetIPProperties().UnicastAddresses; + var __HasIpAddress = false; + + foreach (var __IpAddress in __IpAddresses) + { + __HasIpAddress = String.Equals(__IpAddress.Address.ToString(), address, StringComparison.OrdinalIgnoreCase); + + if (__HasIpAddress) + { + break; + } + } + + return __HasIpAddress; + } + + public void Dispose() + { + this._IsDisposed.OnNext(null); + } + + public override String ToString() + { + return this.Configuration.Name; + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/System/Reactive/Observable.cs b/src/Logixware.SoftEther.Client.Daemon/System/Reactive/Observable.cs new file mode 100644 index 0000000..892b14a --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/System/Reactive/Observable.cs @@ -0,0 +1,11 @@ +namespace System.Reactive.Linq +{ + public static class ObservableExtensions + { + public static IObservable NotNull(this IObservable source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + return source.SkipWhile(x => x == null); + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/appsettings.Development.json b/src/Logixware.SoftEther.Client.Daemon/appsettings.Development.json new file mode 100644 index 0000000..f999bc2 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/appsettings.Production.json b/src/Logixware.SoftEther.Client.Daemon/appsettings.Production.json new file mode 100644 index 0000000..302a911 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/appsettings.Production.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Error", + "System": "Information", + "Microsoft": "Information" + } + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Daemon/de.logixware.SoftEther.Client.Daemon.plist b/src/Logixware.SoftEther.Client.Daemon/de.logixware.SoftEther.Client.Daemon.plist new file mode 100755 index 0000000..99d79dd --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/de.logixware.SoftEther.Client.Daemon.plist @@ -0,0 +1,25 @@ + + + + + EnvironmentVariables + + PATH + /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/sbin + + Label + de.logixware.SoftEther.Client + ProgramArguments + + /Applications/VPN Client.app/Contents/MacOS/Current/Logixware.SoftEther.Client + + RunAtLoad + + WorkingDirectory + /Applications/VPN Client.app/Contents/MacOS/Current + StandardOutPath + /var/log/logixware/vpnclient/output.log + StandardErrorPath + /var/log/logixware/vpnclient/error.log + + diff --git a/src/Logixware.SoftEther.Client.Daemon/hostsettings.json b/src/Logixware.SoftEther.Client.Daemon/hostsettings.json new file mode 100644 index 0000000..5fa9430 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Daemon/hostsettings.json @@ -0,0 +1,3 @@ +{ + "environment": "Development" +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Manager/.gitignore b/src/Logixware.SoftEther.Client.Manager/.gitignore new file mode 100644 index 0000000..cbc77e0 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/.gitignore @@ -0,0 +1,257 @@ +## Custom + +*.DS_Store +.idea + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +# .idea/ +# *.sln.iml diff --git a/src/Logixware.SoftEther.Client.Manager/AppDelegate.cs b/src/Logixware.SoftEther.Client.Manager/AppDelegate.cs new file mode 100644 index 0000000..f9fb84d --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/AppDelegate.cs @@ -0,0 +1,23 @@ +using AppKit; +using Foundation; + +namespace Logixware.SoftEther.Client.Manager +{ + [Register("AppDelegate")] + public class AppDelegate : NSApplicationDelegate + { + public AppDelegate() + { + } + + public override void DidFinishLaunching(NSNotification notification) + { + // Insert code here to initialize your application + } + + public override void WillTerminate(NSNotification notification) + { + // Insert code here to tear down your application + } + } +} diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png new file mode 100644 index 0000000..d0b5a80 Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-128.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png new file mode 100644 index 0000000..f4c8d29 Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-128@2x.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png new file mode 100644 index 0000000..ebb5a0f Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-16.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png new file mode 100644 index 0000000..0986d31 Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-16@2x.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png new file mode 100644 index 0000000..f4c8d29 Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-256.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png new file mode 100644 index 0000000..a142c83 Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-256@2x.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png new file mode 100644 index 0000000..0986d31 Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-32.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png new file mode 100644 index 0000000..412d6ca Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-32@2x.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png new file mode 100644 index 0000000..a142c83 Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-512.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png new file mode 100644 index 0000000..e99022a Binary files /dev/null and b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png differ diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/Contents.json b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..6b28545 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images": [ + { + "filename": "AppIcon-16.png", + "size": "16x16", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-16@2x.png", + "size": "16x16", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-32.png", + "size": "32x32", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-32@2x.png", + "size": "32x32", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-128.png", + "size": "128x128", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-128@2x.png", + "size": "128x128", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-256.png", + "size": "256x256", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-256@2x.png", + "size": "256x256", + "scale": "2x", + "idiom": "mac" + }, + { + "filename": "AppIcon-512.png", + "size": "512x512", + "scale": "1x", + "idiom": "mac" + }, + { + "filename": "AppIcon-512@2x.png", + "size": "512x512", + "scale": "2x", + "idiom": "mac" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/Contents.json b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/Contents.json new file mode 100644 index 0000000..4caf392 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Manager/Entitlements.plist b/src/Logixware.SoftEther.Client.Manager/Entitlements.plist new file mode 100644 index 0000000..9ae5993 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Logixware.SoftEther.Client.Manager/Info.plist b/src/Logixware.SoftEther.Client.Manager/Info.plist new file mode 100644 index 0000000..bcd3e6c --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/Info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleName + SoftEther VPN Client Manager + CFBundleIdentifier + de.logixware.SoftEther.Client.Manager + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 10.13 + CFBundleDevelopmentRegion + en + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + NSHumanReadableCopyright + ${AuthorCopyright:HtmlEncode} + NSPrincipalClass + NSApplication + NSMainStoryboardFile + Main + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + + diff --git a/src/Logixware.SoftEther.Client.Manager/Logixware.SoftEther.Client.Manager.csproj b/src/Logixware.SoftEther.Client.Manager/Logixware.SoftEther.Client.Manager.csproj new file mode 100644 index 0000000..225e049 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/Logixware.SoftEther.Client.Manager.csproj @@ -0,0 +1,89 @@ + + + + Debug + x86 + {4E1D138F-EA24-4BD0-8E69-4CD6AACD203E} + {A3F8F2AB-B479-4A4A-A458-A89E7DC349F1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Logixware.SoftEther.Client.Manager + SoftEther VPN Client Manager + v2.0 + Resources + Xamarin.Mac + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + Mac Developer + false + false + false + true + true + x86 + None + Latest + + + pdbonly + true + bin\Release + prompt + 4 + false + true + false + true + true + true + None + x86 + None + Latest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ViewController.cs + + + + + + + \ No newline at end of file diff --git a/src/Logixware.SoftEther.Client.Manager/Main.cs b/src/Logixware.SoftEther.Client.Manager/Main.cs new file mode 100644 index 0000000..b3c769f --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/Main.cs @@ -0,0 +1,13 @@ +using AppKit; + +namespace Logixware.SoftEther.Client.Manager +{ + static class MainClass + { + static void Main(string[] args) + { + NSApplication.Init(); + NSApplication.Main(args); + } + } +} diff --git a/src/Logixware.SoftEther.Client.Manager/Main.storyboard b/src/Logixware.SoftEther.Client.Manager/Main.storyboard new file mode 100644 index 0000000..db0beea --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/Main.storyboard @@ -0,0 +1,685 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Logixware.SoftEther.Client.Manager/ViewController.cs b/src/Logixware.SoftEther.Client.Manager/ViewController.cs new file mode 100644 index 0000000..031a52b --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/ViewController.cs @@ -0,0 +1,34 @@ +using System; + +using AppKit; +using Foundation; + +namespace Logixware.SoftEther.Client.Manager +{ + public partial class ViewController : NSViewController + { + public ViewController(IntPtr handle) : base(handle) + { + } + + public override void ViewDidLoad() + { + base.ViewDidLoad(); + + // Do any additional setup after loading the view. + } + + public override NSObject RepresentedObject + { + get + { + return base.RepresentedObject; + } + set + { + base.RepresentedObject = value; + // Update the view, if already loaded. + } + } + } +} diff --git a/src/Logixware.SoftEther.Client.Manager/ViewController.designer.cs b/src/Logixware.SoftEther.Client.Manager/ViewController.designer.cs new file mode 100644 index 0000000..33fec51 --- /dev/null +++ b/src/Logixware.SoftEther.Client.Manager/ViewController.designer.cs @@ -0,0 +1,18 @@ +// WARNING +// +// This file has been generated automatically by Xamarin Studio to store outlets and +// actions made in the UI designer. If it is removed, they will be lost. +// Manual changes to this file may not be handled correctly. +// +using Foundation; + +namespace Logixware.SoftEther.Client.Manager +{ + [Register("ViewController")] + partial class ViewController + { + void ReleaseDesignerOutlets() + { + } + } +}