forked from xkjyeah/openvpnserv2
-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathOpenVPNChild.cs
More file actions
189 lines (167 loc) · 7.24 KB
/
OpenVPNChild.cs
File metadata and controls
189 lines (167 loc) · 7.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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
{
/// <summary>
/// Represents single OpenVPN connection
/// </summary>
class OpenVpnChild
{
string logFile;
Process process;
System.Timers.Timer restartTimer;
OpenVpnServiceConfiguration config;
string configFile;
string exitEvent;
private CancellationTokenSource exitPollingToken = new CancellationTokenSource();
/// <summary>
/// Constructs OpenVpnChild object
/// </summary>
/// <param name="config"></param>
/// <param name="configFile">path to ovpn profile</param>
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");
}
/// <summary>
/// Signal OpenVPN process exit event and cancel polling task.
/// </summary>
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) { }
}
/// <summary>
/// Polling task to detect process exit and restart it.
/// </summary>
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);
}
}
/// <summary>
/// Restart OpenVPN process after delay
/// </summary>
/// <param name="delayMs"></param>
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();
}
/// <summary>
/// Name of the OpenVPN interactive service pipe
/// </summary>
private const string PipeName = @"openvpn\service";
/// <summary>
/// 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.
/// </summary>
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);
}
}
}
}
}
}