diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 291eae5..52dae35 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,21 +2,34 @@ name: OpenVPNServ2
on:
push:
- branches:
- - master
pull_request:
- branches:
- - master
jobs:
ubuntu:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v3
- - name: Install dependencies
- run: sudo apt-get update && sudo apt-get install mono-devel
- - name: Build
- run: ./build.sh
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+
+ - name: Install Latest Mono & MSBuild
+ run: |
+ sudo apt update
+ sudo apt install -y gnupg ca-certificates
+
+ sudo gpg --homedir /tmp --no-default-keyring \
+ --keyring /usr/share/keyrings/mono-official-archive-keyring.gpg \
+ --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
+
+ echo \
+ "deb [signed-by=/usr/share/keyrings/mono-official-archive-keyring.gpg] https://download.mono-project.com/repo/ubuntu stable-focal main" | \
+ sudo tee /etc/apt/sources.list.d/mono-official-stable.list
+
+ # Update and install Mono & MSBuild
+ sudo apt update
+ sudo apt install -y mono-devel msbuild
+
+ - name: Build
+ run: ./build.sh
win:
runs-on: windows-latest
diff --git a/.gitignore b/.gitignore
index df11d43..357eb4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,6 @@
obj
/bin
+.vs
+.vscode
+AssemblyInfo.cs
+git_hash.txt
diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs
deleted file mode 100644
index 6fdbde7..0000000
--- a/AssemblyInfo.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-[assembly: AssemblyTitle("Openvpnserv2")]
-[assembly: AssemblyDescription("Windows service for running OpenVPN connections in the background")]
-[assembly: AssemblyProduct("Openvpnserv2")]
-[assembly: AssemblyVersion("1.4.0.1")]
-[assembly: AssemblyFileVersion("1.4.0.1")]
-[assembly: AssemblyInformationalVersion("1.4.0.1")]
-[assembly: AssemblyCompany("The OpenVPN project")]
-[assembly: AssemblyCopyright("Copyright © OpenVPN project 2020")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
\ No newline at end of file
diff --git a/OpenVPNChild.cs b/OpenVPNChild.cs
new file mode 100644
index 0000000..485efe4
--- /dev/null
+++ b/OpenVPNChild.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Pipes;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Timers;
+
+namespace OpenVpn
+{
+ ///
+ /// Represents single OpenVPN connection
+ ///
+ class OpenVpnChild
+ {
+ string logFile;
+ Process process;
+ System.Timers.Timer restartTimer;
+ OpenVpnServiceConfiguration config;
+ string configFile;
+ string exitEvent;
+ private CancellationTokenSource exitPollingToken = new CancellationTokenSource();
+
+ ///
+ /// Constructs OpenVpnChild object
+ ///
+ ///
+ /// path to ovpn profile
+ public OpenVpnChild(OpenVpnServiceConfiguration config, string configFile)
+ {
+ this.config = config;
+ this.configFile = configFile;
+ this.exitEvent = Path.GetFileName(configFile) + "_" + Process.GetCurrentProcess().Id.ToString();
+ var justFilename = Path.GetFileName(configFile);
+ logFile = Path.Combine(config.logDir, justFilename.Substring(0, justFilename.Length - config.configExt.Length) + ".log");
+ }
+
+ ///
+ /// Signal OpenVPN process exit event and cancel polling task.
+ ///
+ public void SignalProcess()
+ {
+ if (restartTimer != null)
+ {
+ restartTimer.Stop();
+ }
+ try
+ {
+ if (process != null && !process.HasExited)
+ {
+ try
+ {
+ config.LogMessage($"Signalling PID {process.Id} for config {configFile} to exit");
+
+ using (var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, exitEvent))
+ {
+ waitHandle.Set(); // Signal OpenVPN to exit gracefully
+ }
+
+ exitPollingToken.Cancel(); // Stop monitoring
+ }
+ catch (IOException e)
+ {
+ config.LogMessage("IOException creating exit event named '" + exitEvent + "' " + e.Message + e.StackTrace, EventLogEntryType.Error);
+ }
+ catch (UnauthorizedAccessException e)
+ {
+ config.LogMessage("UnauthorizedAccessException creating exit event named '" + exitEvent + "' " + e.Message + e.StackTrace, EventLogEntryType.Error);
+ }
+ catch (WaitHandleCannotBeOpenedException e)
+ {
+ config.LogMessage("WaitHandleCannotBeOpenedException creating exit event named '" + exitEvent + "' " + e.Message + e.StackTrace, EventLogEntryType.Error);
+ }
+ catch (ArgumentException e)
+ {
+ config.LogMessage("ArgumentException creating exit event named '" + exitEvent + "' " + e.Message + e.StackTrace, EventLogEntryType.Error);
+ }
+ }
+ }
+ catch (InvalidOperationException) { }
+ }
+
+ ///
+ /// Polling task to detect process exit and restart it.
+ ///
+ private async void MonitorProcessExit()
+ {
+ if (process == null) return;
+
+ config.LogMessage($"Started polling for OpenVPN process, PID {process.Id}");
+
+ try
+ {
+ while (!process.HasExited)
+ {
+ await Task.Delay(1000, exitPollingToken.Token);
+ }
+
+ config.LogMessage($"Process {process.Id} has exited.", EventLogEntryType.Warning);
+ RestartAfterDelay(10000);
+ }
+ catch (TaskCanceledException)
+ {
+ config.LogMessage("Process monitoring cancelled.");
+ }
+ catch (Exception ex)
+ {
+ config.LogMessage($"Error in MonitorProcessExit: {ex.Message}", EventLogEntryType.Error);
+ }
+ }
+
+ ///
+ /// Restart OpenVPN process after delay
+ ///
+ ///
+ private void RestartAfterDelay(int delayMs)
+ {
+ config.LogMessage($"Restarting process for {configFile} in {delayMs / 1000} sec.");
+
+ restartTimer = new System.Timers.Timer(delayMs);
+ restartTimer.AutoReset = false;
+ restartTimer.Elapsed += (object source, ElapsedEventArgs ev) =>
+ {
+ Start();
+ };
+ restartTimer.Start();
+ }
+
+ ///
+ /// Name of the OpenVPN interactive service pipe
+ ///
+ private const string PipeName = @"openvpn\service";
+
+ ///
+ /// Start OpenVPN child process.
+ /// Connect to interactive service via named pipe and pass a startup info.
+ /// Read OpenVPN process PID from the pipe and set up polling task
+ /// to detect process exit.
+ ///
+ public void Start()
+ {
+ using (var pipeClient = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.Asynchronous))
+ {
+ config.LogMessage("Connecting to iservice pipe...");
+ pipeClient.Connect(5000);
+
+ using (var writer = new BinaryWriter(pipeClient, Encoding.Unicode))
+ using (var reader = new StreamReader(pipeClient, Encoding.Unicode))
+ {
+ // send startup info
+ var logOption = config.logAppend ? "--log-append " : "--log";
+ var cmdLine = $"{logOption} \"{logFile}\" --config \"{configFile}\" --service \"{exitEvent}\" 0 --pull-filter ignore route-method";
+
+ // config_dir + \0 + options + \0 + password + \0
+ var startupInfo = $"{config.configDir}\0{cmdLine}\0\0";
+
+ byte[] messageBytes = Encoding.Unicode.GetBytes(startupInfo);
+ writer.Write(messageBytes);
+ writer.Flush();
+
+ config.LogMessage("Sent startupInfo to iservice");
+
+ // read openvpn process pid from the pipe
+ string[] lines = { reader.ReadLine(), reader.ReadLine() };
+
+ config.LogMessage($"Read from iservice: {string.Join(" ", lines)}");
+ var errorCode = Convert.ToInt32(lines[0], 16);
+
+ if (errorCode == 0)
+ {
+ var pid = Convert.ToInt32(lines[1], 16);
+ process = Process.GetProcessById(pid);
+
+ exitPollingToken = new CancellationTokenSource();
+ Task.Run(() => MonitorProcessExit(), exitPollingToken.Token);
+
+ config.LogMessage($"Started monitoring OpenVPN process, PID {pid}");
+ } else
+ {
+ config.LogMessage("Error getting openvpn process PID", EventLogEntryType.Error);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/OpenVPNServiceConfiguration.cs b/OpenVPNServiceConfiguration.cs
new file mode 100644
index 0000000..9b7fdea
--- /dev/null
+++ b/OpenVPNServiceConfiguration.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Diagnostics;
+
+namespace OpenVpn
+{
+ class OpenVpnServiceConfiguration
+ {
+ public string exePath { get; set; }
+ public string configExt { get; set; }
+ public string configDir { get; set; }
+ public string logDir { get; set; }
+ public bool logAppend { get; set; }
+
+ ///
+ /// Delegate used to log messages with a specified severity level.
+ ///
+ public Action Log;
+
+ ///
+ /// Constructs OpenVpnServiceConfiguration object
+ ///
+ /// Log callback
+ public OpenVpnServiceConfiguration(Action logAction)
+ {
+ Log = logAction;
+ }
+
+ ///
+ /// Writes log message via log callback
+ ///
+ ///
+ ///
+ public void LogMessage(string message, EventLogEntryType type = EventLogEntryType.Information)
+ {
+ Log(message, type);
+ }
+ }
+}
diff --git a/OpenVPNServiceRunner.cs b/OpenVPNServiceRunner.cs
new file mode 100644
index 0000000..a872680
--- /dev/null
+++ b/OpenVPNServiceRunner.cs
@@ -0,0 +1,170 @@
+using Microsoft.Win32;
+using OpenVpn;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+
+namespace OpenVpn
+{
+ ///
+ /// Main class implementing OpenVPN service functionality,
+ /// without dependencies to Windows service infrastructure
+ ///
+ class OpenVPNServiceRunner
+ {
+ private List Subprocesses;
+ private EventLog _eventLog;
+
+ ///
+ /// Creates OpenVPNServiceRunner object
+ ///
+ /// EventLog or null
+ public OpenVPNServiceRunner(EventLog eventLog)
+ {
+ this.Subprocesses = new List();
+ _eventLog = eventLog;
+ }
+
+ ///
+ /// Stops all OpenVPN child processes
+ ///
+ public void Stop()
+ {
+ foreach (var child in Subprocesses)
+ {
+ child.SignalProcess();
+ }
+ }
+
+ ///
+ /// Gets registry subkey
+ ///
+ ///
+ /// Registry key, null if not found
+ private RegistryKey GetRegistrySubkey(RegistryView rView)
+ {
+ try
+ {
+ return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, rView)
+ .OpenSubKey("Software\\OpenVPN");
+ }
+ catch (ArgumentException)
+ {
+ return null;
+ }
+ catch (NullReferenceException)
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Reads configuration from the registry and starts OpenVPN process for every ovpn config found.
+ ///
+ ///
+ public void Start(string[] args)
+ {
+ try
+ {
+ List rkOvpns = new List();
+
+ // Search 64-bit registry, then 32-bit registry for OpenVpn
+ var key = GetRegistrySubkey(RegistryView.Registry64);
+ if (key != null) rkOvpns.Add(key);
+ key = GetRegistrySubkey(RegistryView.Registry32);
+ if (key != null) rkOvpns.Add(key);
+
+ if (rkOvpns.Count() == 0)
+ throw new Exception("Registry key missing");
+
+ var configDirsConsidered = new HashSet();
+
+ foreach (var rkOvpn in rkOvpns)
+ {
+ try
+ {
+ bool append = false;
+ {
+ var logAppend = (string)rkOvpn.GetValue("log_append");
+ if (logAppend[0] == '0' || logAppend[0] == '1')
+ append = logAppend[0] == '1';
+ else
+ throw new Exception("Log file append flag must be 1 or 0");
+ }
+
+ var config = new OpenVpnServiceConfiguration(Log)
+ {
+ exePath = (string)rkOvpn.GetValue("exe_path"),
+ configDir = (string)rkOvpn.GetValue("autostart_config_dir"),
+ configExt = "." + (string)rkOvpn.GetValue("config_ext"),
+ logDir = (string)rkOvpn.GetValue("log_dir"),
+ logAppend = append
+ };
+
+ if (String.IsNullOrEmpty(config.configDir) || configDirsConsidered.Contains(config.configDir))
+ {
+ continue;
+ }
+ configDirsConsidered.Add(config.configDir);
+
+ /// Only attempt to start the service
+ /// if openvpn.exe is present. This should help if there are old files
+ /// and registry settings left behind from a previous OpenVPN 32-bit installation
+ /// on a 64-bit system.
+ if (!File.Exists(config.exePath))
+ {
+ Log("OpenVPN binary does not exist at " + config.exePath);
+ continue;
+ }
+
+ foreach (var configFilename in Directory.EnumerateFiles(config.configDir,
+ "*" + config.configExt,
+ System.IO.SearchOption.AllDirectories))
+ {
+ try
+ {
+ var child = new OpenVpnChild(config, configFilename);
+ Subprocesses.Add(child);
+ child.Start();
+ }
+ catch (Exception e)
+ {
+ Log("Caught exception " + e.Message + " when starting openvpn for "
+ + configFilename, EventLogEntryType.Error);
+ }
+ }
+ }
+ catch (NullReferenceException e) /* e.g. missing registry values */
+ {
+ Log("Registry values are incomplete for " + rkOvpn.View.ToString() + e.StackTrace, EventLogEntryType.Error);
+ }
+ }
+
+ }
+ catch (Exception e)
+ {
+ Log("Exception occured during OpenVPN service start: " + e.Message + e.StackTrace, EventLogEntryType.Error);
+ throw e;
+ }
+ }
+
+ ///
+ /// Writes log message either to event log or console
+ ///
+ ///
+ ///
+ private void Log(string message, EventLogEntryType type = EventLogEntryType.Information)
+ {
+ if (_eventLog != null)
+ {
+ _eventLog.WriteEntry(message, type);
+ }
+ else
+ {
+ Console.WriteLine($"[{type}] {message}");
+ }
+ }
+ }
+}
diff --git a/OpenVpnService.csproj b/OpenVpnService.csproj
index 87b02b1..1576dee 100644
--- a/OpenVpnService.csproj
+++ b/OpenVpnService.csproj
@@ -15,7 +15,7 @@
512
false
- 1.4.0.1
+ 2.0.0.0
publish\
true
Disk
@@ -32,7 +32,7 @@
true
- OpenVpn.OpenVpnService
+ OpenVpn.Program
x86
@@ -93,16 +93,14 @@
4
-
- Component
-
-
- ProjectInstaller.cs
-
+
+
+
Component
+
@@ -111,9 +109,6 @@
-
- ProjectInstaller.cs
-
Service.cs
@@ -148,4 +143,33 @@
-->
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+ unknown
+
+
+
+
+
+
+
+
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..6fce30a
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.ServiceProcess;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OpenVpn
+{
+ internal class Program
+ {
+ public static int Main(string[] args)
+ {
+ if (!Environment.UserInteractive)
+ {
+ // Running as a Windows Service
+ ServiceBase.Run(new OpenVpnService());
+ }
+ else
+ {
+ // Running as a console application
+ Console.WriteLine("Running in console mode...");
+ var runner = new OpenVPNServiceRunner(null);
+ runner.Start(args);
+
+ Console.WriteLine("Press Enter to stop...");
+ Console.ReadLine();
+
+ runner.Stop();
+ }
+ return 0;
+ }
+ }
+}
diff --git a/ProjectInstaller.Designer.cs b/ProjectInstaller.Designer.cs
deleted file mode 100644
index cd7a928..0000000
--- a/ProjectInstaller.Designer.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-namespace OpenVpn
-{
- partial class ProjectInstaller
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Component Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- this.serviceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller();
- this.serviceInstaller = new System.ServiceProcess.ServiceInstaller();
- //
- // serviceProcessInstaller
- //
- this.serviceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
- this.serviceProcessInstaller.Password = null;
- this.serviceProcessInstaller.Username = null;
- //
- // serviceInstaller
- //
- this.serviceInstaller.ServiceName = "OpenVpnService";
- this.serviceInstaller.ServicesDependedOn = new string[] {
- "Dhcp",
- "tap0901"};
- this.serviceInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
- //
- // ProjectInstaller
- //
- this.Installers.AddRange(new System.Configuration.Install.Installer[] {
- this.serviceProcessInstaller,
- this.serviceInstaller});
-
- }
-
- #endregion
-
- private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller;
- private System.ServiceProcess.ServiceInstaller serviceInstaller;
- }
-}
diff --git a/ProjectInstaller.cs b/ProjectInstaller.cs
deleted file mode 100644
index b91065f..0000000
--- a/ProjectInstaller.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Configuration.Install;
-using System.Linq;
-using System.ServiceProcess;
-
-namespace OpenVpn
-{
- [RunInstaller(true)]
- public partial class ProjectInstaller : System.Configuration.Install.Installer
- {
- public ProjectInstaller()
- {
- InitializeComponent();
- }
-
-
- // References http://stackoverflow.com/questions/1195478/how-to-make-a-net-windows-service-start-right-after-the-installation/1195621#1195621
- public static void Install()
- {
- using (AssemblyInstaller installer =
- new AssemblyInstaller(typeof(OpenVpnService).Assembly, null))
- {
- installer.UseNewContext = true;
- var state = new System.Collections.Hashtable();
- try
- {
- installer.Install(state);
- installer.Commit(state);
- } catch
- {
- installer.Rollback(state);
- throw;
- }
- }
- }
-
- public static void Uninstall()
- {
- using (AssemblyInstaller installer =
- new AssemblyInstaller(typeof(OpenVpnService).Assembly, null))
- {
- installer.UseNewContext = true;
- var state = new System.Collections.Hashtable();
- try
- {
- installer.Uninstall(state);
- }
- catch
- {
- throw;
- }
- }
- }
-
- public static void Stop()
- {
- using (ServiceController controller =
- new ServiceController(OpenVpnService.DefaultServiceName))
- {
- try
- {
- if (controller.Status != ServiceControllerStatus.Stopped)
- {
- controller.Stop();
- controller.WaitForStatus(ServiceControllerStatus.Stopped,
- TimeSpan.FromSeconds(10));
- }
- }
- catch
- {
- throw;
- }
- }
- }
-
- public static void Start()
- {
- using (ServiceController controller =
- new ServiceController(OpenVpnService.DefaultServiceName))
- {
- try
- {
- if (controller.Status != ServiceControllerStatus.Running)
- {
- controller.Start();
- controller.WaitForStatus(ServiceControllerStatus.Running,
- TimeSpan.FromSeconds(10));
- }
- }
- catch
- {
- throw;
- }
- }
- }
- }
-}
diff --git a/ProjectInstaller.resx b/ProjectInstaller.resx
deleted file mode 100755
index 27dace8..0000000
--- a/ProjectInstaller.resx
+++ /dev/null
@@ -1,129 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- 17, 56
-
-
- 17, 17
-
-
- False
-
-
\ No newline at end of file
diff --git a/Service.cs b/Service.cs
index 19766eb..82567dc 100644
--- a/Service.cs
+++ b/Service.cs
@@ -10,13 +10,11 @@
namespace OpenVpn
{
-
- class OpenVpnService : System.ServiceProcess.ServiceBase
+ public class OpenVpnService : ServiceBase
{
public static string DefaultServiceName = "OpenVpnService";
-
- public const string Package = "openvpn";
- private List Subprocesses;
+ private OpenVPNServiceRunner _serviceRunner;
+ private EventLog _eventLog;
public OpenVpnService()
{
@@ -29,388 +27,26 @@ public OpenVpnService()
this.CanHandlePowerEvent = false;
this.AutoLog = true;
- this.Subprocesses = new List();
- }
-
- protected override void OnStop()
- {
- RequestAdditionalTime(3000);
- foreach (var child in Subprocesses)
+ _eventLog = new EventLog();
+ if (!EventLog.SourceExists(this.ServiceName))
{
- child.SignalProcess();
+ EventLog.CreateEventSource(this.ServiceName, "Application");
}
- // Kill all processes -- wait for 2500 msec at most
- DateTime tEnd = DateTime.Now.AddMilliseconds(2500.0);
- foreach (var child in Subprocesses)
- {
- int timeout = (int) (tEnd - DateTime.Now).TotalMilliseconds;
- child.StopProcess(timeout > 0 ? timeout : 0);
- }
- }
+ _eventLog.Source = this.ServiceName;
+ _eventLog.Log = "Application";
- private RegistryKey GetRegistrySubkey(RegistryView rView)
- {
- try
- {
- return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, rView)
- .OpenSubKey("Software\\OpenVPN");
- }
- catch (ArgumentException)
- {
- return null;
- }
- catch (NullReferenceException)
- {
- return null;
- }
+ _serviceRunner = new OpenVPNServiceRunner(_eventLog); // Decoupled service logic
}
protected override void OnStart(string[] args)
{
- try
- {
- List rkOvpns = new List();
-
- // Search 64-bit registry, then 32-bit registry for OpenVpn
- var key = GetRegistrySubkey(RegistryView.Registry64);
- if (key != null) rkOvpns.Add(key);
- key = GetRegistrySubkey(RegistryView.Registry32);
- if (key != null) rkOvpns.Add(key);
-
- if (rkOvpns.Count() == 0)
- throw new Exception("Registry key missing");
-
- var configDirsConsidered = new HashSet();
-
- foreach (var rkOvpn in rkOvpns)
- {
- try {
- bool append = false;
- {
- var logAppend = (string)rkOvpn.GetValue("log_append");
- if (logAppend[0] == '0' || logAppend[0] == '1')
- append = logAppend[0] == '1';
- else
- throw new Exception("Log file append flag must be 1 or 0");
- }
-
- var config = new OpenVpnServiceConfiguration()
- {
- exePath = (string)rkOvpn.GetValue("exe_path"),
- configDir = (string)rkOvpn.GetValue("autostart_config_dir"),
- configExt = "." + (string)rkOvpn.GetValue("config_ext"),
- logDir = (string)rkOvpn.GetValue("log_dir"),
- logAppend = append,
- priorityClass = GetPriorityClass((string)rkOvpn.GetValue("priority")),
-
- eventLog = EventLog,
- };
-
- if (String.IsNullOrEmpty(config.configDir) || configDirsConsidered.Contains(config.configDir)) {
- continue;
- }
- configDirsConsidered.Add(config.configDir);
-
- /// Only attempt to start the service
- /// if openvpn.exe is present. This should help if there are old files
- /// and registry settings left behind from a previous OpenVPN 32-bit installation
- /// on a 64-bit system.
- if (!File.Exists(config.exePath))
- {
- EventLog.WriteEntry("OpenVPN binary does not exist at " + config.exePath);
- continue;
- }
-
- foreach (var configFilename in Directory.EnumerateFiles(config.configDir,
- "*" + config.configExt,
- System.IO.SearchOption.AllDirectories))
- {
- try {
- var child = new OpenVpnChild(config, configFilename);
- Subprocesses.Add(child);
- child.Start();
- }
- catch (Exception e)
- {
- EventLog.WriteEntry("Caught exception " + e.Message + " when starting openvpn for "
- + configFilename);
- }
- }
- }
- catch (NullReferenceException e) /* e.g. missing registry values */
- {
- EventLog.WriteEntry("Registry values are incomplete for " + rkOvpn.View.ToString() + e.StackTrace);
- }
- }
-
- }
- catch (Exception e)
- {
- EventLog.WriteEntry("Exception occured during OpenVPN service start: " + e.Message + e.StackTrace);
- throw e;
- }
- }
-
- private System.Diagnostics.ProcessPriorityClass GetPriorityClass(string priorityString)
- {
- if (String.Equals(priorityString, "IDLE_PRIORITY_CLASS", StringComparison.InvariantCultureIgnoreCase)) {
- return System.Diagnostics.ProcessPriorityClass.Idle;
- }
- else if (String.Equals(priorityString, "BELOW_NORMAL_PRIORITY_CLASS", StringComparison.InvariantCultureIgnoreCase))
- {
- return System.Diagnostics.ProcessPriorityClass.BelowNormal;
- }
- else if (String.Equals(priorityString, "NORMAL_PRIORITY_CLASS", StringComparison.InvariantCultureIgnoreCase))
- {
- return System.Diagnostics.ProcessPriorityClass.Normal;
- }
- else if (String.Equals(priorityString, "ABOVE_NORMAL_PRIORITY_CLASS", StringComparison.InvariantCultureIgnoreCase))
- {
- return System.Diagnostics.ProcessPriorityClass.AboveNormal;
- }
- else if (String.Equals(priorityString, "HIGH_PRIORITY_CLASS", StringComparison.InvariantCultureIgnoreCase))
- {
- return System.Diagnostics.ProcessPriorityClass.High;
- }
- else {
- throw new Exception("Unknown priority name: " + priorityString);
- }
+ _serviceRunner.Start(args);
}
- public static int Main(string[] args)
- {
- if (args.Length == 0)
- {
- Run(new OpenVpnService());
- }
- else if (args[0] == "-install")
- {
- try
- {
- ProjectInstaller.Install();
- }
- catch (Exception e)
- {
- Console.Error.WriteLine(e.Message);
- Console.Error.WriteLine(e.StackTrace);
- return 1;
- }
- }
- else if (args[0] == "-remove")
- {
- try
- {
- ProjectInstaller.Stop();
- ProjectInstaller.Uninstall();
- }
- catch (Exception e)
- {
- Console.Error.WriteLine(e.Message);
- Console.Error.WriteLine(e.StackTrace);
- return 1;
- }
- }
- else
- {
- Console.Error.WriteLine("Unknown command: " + args[0]);
- return 1;
- }
- return 0;
- }
-
- }
-
- class OpenVpnServiceConfiguration {
- public string exePath {get;set;}
- public string configExt {get;set;}
- public string configDir {get;set;}
- public string logDir {get;set;}
- public bool logAppend {get;set;}
- public System.Diagnostics.ProcessPriorityClass priorityClass {get;set;}
-
- public EventLog eventLog {get;set;}
- }
-
- class OpenVpnChild {
- StreamWriter logFile;
- Process process;
- ProcessStartInfo startInfo;
- System.Timers.Timer restartTimer;
- OpenVpnServiceConfiguration config;
- string configFile;
- string exitEvent;
-
- public OpenVpnChild(OpenVpnServiceConfiguration config, string configFile) {
- this.config = config;
- /// SET UP LOG FILES
- /* Because we will be using the filenames in our closures,
- * so make sure we are working on a copy */
- this.configFile = String.Copy(configFile);
- this.exitEvent = Path.GetFileName(configFile) + "_" + Process.GetCurrentProcess().Id.ToString();
- var justFilename = System.IO.Path.GetFileName(configFile);
- var logFilename = config.logDir + "\\" +
- justFilename.Substring(0, justFilename.Length - config.configExt.Length) + ".log";
-
- // FIXME: if (!init_security_attributes_allow_all (&sa))
- //{
- // MSG (M_SYSERR, "InitializeSecurityDescriptor start_" PACKAGE " failed");
- // goto finish;
- //}
-
- logFile = new StreamWriter(File.Open(logFilename,
- config.logAppend ? FileMode.Append : FileMode.Create,
- FileAccess.Write,
- FileShare.Read), new UTF8Encoding(false));
- logFile.AutoFlush = true;
-
- /// SET UP PROCESS START INFO
- string[] procArgs = {
- "--config",
- "\"" + configFile + "\"",
- "--service ",
- "\"" + exitEvent + "\"" + " 0"
- };
- this.startInfo = new System.Diagnostics.ProcessStartInfo()
- {
- RedirectStandardInput = true,
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden,
-
- FileName = config.exePath,
- Arguments = String.Join(" ", procArgs),
- WorkingDirectory = config.configDir,
-
- UseShellExecute = false,
- /* create_new_console is not exposed -- but we probably don't need it?*/
- };
- }
-
- // set exit event so that openvpn will terminate
- public void SignalProcess() {
- if (restartTimer != null) {
- restartTimer.Stop();
- }
- try
- {
- if (!process.HasExited)
- {
-
- try {
- var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, exitEvent);
-
- process.Exited -= Watchdog; // Don't restart the process after exit
-
- waitHandle.Set();
- waitHandle.Close();
- } catch (IOException e) {
- config.eventLog.WriteEntry("IOException creating exit event named '" + exitEvent + "' " + e.Message + e.StackTrace);
- } catch (UnauthorizedAccessException e) {
- config.eventLog.WriteEntry("UnauthorizedAccessException creating exit event named '" + exitEvent + "' " + e.Message + e.StackTrace);
- } catch (WaitHandleCannotBeOpenedException e) {
- config.eventLog.WriteEntry("WaitHandleCannotBeOpenedException creating exit event named '" + exitEvent + "' " + e.Message + e.StackTrace);
- } catch (ArgumentException e) {
- config.eventLog.WriteEntry("ArgumentException creating exit event named '" + exitEvent + "' " + e.Message + e.StackTrace);
- }
- }
- }
- catch (InvalidOperationException) { }
- }
-
- // terminate process after a timeout
- public void StopProcess(int timeout) {
- if (restartTimer != null) {
- restartTimer.Stop();
- }
- try
- {
- if (!process.WaitForExit(timeout))
- {
- process.Exited -= Watchdog; // Don't restart the process after kill
- process.Kill();
- }
- }
- catch (InvalidOperationException) { }
- }
-
- public void Wait() {
- process.WaitForExit();
- logFile.Close();
- }
-
- public void Restart() {
- if (restartTimer != null) {
- restartTimer.Stop();
- }
- /* try-catch... because there could be a concurrency issue (write-after-read) here? */
- if (!process.HasExited)
- {
- process.Exited -= Watchdog;
- process.Exited += FastRestart; // Restart the process after kill
- try
- {
- process.Kill();
- }
- catch (InvalidOperationException)
- {
- Start();
- }
- }
- else
- {
- Start();
- }
- }
-
- private void WriteToLog(object sendingProcess, DataReceivedEventArgs e) {
- if (e != null)
- logFile.WriteLine(e.Data);
- }
-
- /// Restart after 10 seconds
- /// For use with unexpected terminations
- private void Watchdog(object sender, EventArgs e)
- {
- config.eventLog.WriteEntry("Process for " + configFile + " exited. Restarting in 10 sec.");
-
- restartTimer = new System.Timers.Timer(10000);
- restartTimer.AutoReset = false;
- restartTimer.Elapsed += (object source, System.Timers.ElapsedEventArgs ev) =>
- {
- Start();
- };
- restartTimer.Start();
- }
-
- /// Restart after 3 seconds
- /// For use with Restart() (e.g. after a resume)
- private void FastRestart(object sender, EventArgs e)
+ protected override void OnStop()
{
- config.eventLog.WriteEntry("Process for " + configFile + " restarting in 3 sec");
- restartTimer = new System.Timers.Timer(3000);
- restartTimer.AutoReset = false;
- restartTimer.Elapsed += (object source, System.Timers.ElapsedEventArgs ev) =>
- {
- Start();
- };
- restartTimer.Start();
- }
-
- public void Start() {
- process = new System.Diagnostics.Process();
-
- process.StartInfo = startInfo;
- process.EnableRaisingEvents = true;
-
- process.OutputDataReceived += WriteToLog;
- process.ErrorDataReceived += WriteToLog;
- process.Exited += Watchdog;
-
- process.Start();
- process.BeginErrorReadLine();
- process.BeginOutputReadLine();
- process.PriorityClass = config.priorityClass;
+ RequestAdditionalTime(3000);
+ _serviceRunner.Stop();
}
-
}
}
diff --git a/build.sh b/build.sh
index 8d4b483..90b3742 100755
--- a/build.sh
+++ b/build.sh
@@ -2,10 +2,8 @@
set -eux
-xbuild /p:Configuration=Release "/p:Platform=Any CPU" OpenVpnService.sln
-xbuild /p:Configuration=Release "/p:Platform=x86" OpenVpnService.sln
-xbuild /p:Configuration=Release "/p:Platform=x64" OpenVpnService.sln
-
-xbuild /p:Configuration=Debug "/p:Platform=Any CPU" OpenVpnService.sln
-xbuild /p:Configuration=Debug "/p:Platform=x86" OpenVpnService.sln
-xbuild /p:Configuration=Debug "/p:Platform=x64" OpenVpnService.sln
+for config in Release Debug; do
+ for platform in "Any CPU" x86 x64; do
+ msbuild /p:Configuration=$config /p:Platform="$platform" OpenVpnService.sln
+ done
+done