diff --git a/Netch/Controllers/HTTPController.cs b/Netch/Controllers/HTTPController.cs
index 83a5c798..1ff5fdfd 100644
--- a/Netch/Controllers/HTTPController.cs
+++ b/Netch/Controllers/HTTPController.cs
@@ -31,7 +31,10 @@ namespace Netch.Controllers
try
{
- pPrivoxyController.Start(MainController.ServerController.Server, mode);
+ if (pPrivoxyController.Start(MainController.ServerController.Server, mode))
+ {
+ ChildProcessTracker.AddProcess(pPrivoxyController.Instance);
+ }
if (mode.Type == 3) NativeMethods.SetGlobal($"127.0.0.1:{Global.Settings.HTTPLocalPort}", IEProxyExceptions);
}
diff --git a/Netch/Controllers/MainController.cs b/Netch/Controllers/MainController.cs
index 468e334d..5bf97acc 100644
--- a/Netch/Controllers/MainController.cs
+++ b/Netch/Controllers/MainController.cs
@@ -122,6 +122,14 @@ namespace Netch.Controllers
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", controller.Name));
if (controller.Start(in server, mode))
{
+ if (controller is Guard guard)
+ {
+ if (guard.Instance != null)
+ {
+ ChildProcessTracker.AddProcess(guard.Instance);
+ }
+ }
+
UsingPorts.Add(StatusPortInfoText.Socks5Port = controller.Socks5LocalPort());
StatusPortInfoText.ShareLan = controller.LocalAddress == "0.0.0.0";
@@ -143,7 +151,20 @@ namespace Netch.Controllers
if (ModeController != null)
{
Global.MainForm.StatusText(i18N.TranslateFormat("Starting {0}", ModeController.Name));
- return await Task.Run(() => ModeController.Start(mode));
+ if (await Task.Run(() => ModeController.Start(mode)))
+ {
+ if (ModeController is Guard guard)
+ {
+ if (guard.Instance != null)
+ {
+ ChildProcessTracker.AddProcess(guard.Instance);
+ }
+ }
+
+ return true;
+ }
+
+ return false;
}
return true;
diff --git a/Netch/Utils/ChildProcessTracker.cs b/Netch/Utils/ChildProcessTracker.cs
new file mode 100644
index 00000000..750954cd
--- /dev/null
+++ b/Netch/Utils/ChildProcessTracker.cs
@@ -0,0 +1,145 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Netch.Utils
+{
+ ///
+ /// Allows processes to be automatically killed if this parent process unexpectedly quits.
+ /// This feature requires Windows 8 or greater. On Windows 7, nothing is done.
+ /// References:
+ /// https://stackoverflow.com/a/4657392/386091
+ /// https://stackoverflow.com/a/9164742/386091
+ public static class ChildProcessTracker
+ {
+ ///
+ /// Add the process to be tracked. If our current process is killed, the child processes
+ /// that we are tracking will be automatically killed, too. If the child process terminates
+ /// first, that's fine, too.
+ ///
+ public static void AddProcess(Process process)
+ {
+ if (s_jobHandle != IntPtr.Zero)
+ {
+ var success = AssignProcessToJobObject(s_jobHandle, process.Handle);
+ if (!success && !process.HasExited)
+ throw new Win32Exception();
+ }
+ }
+
+ static ChildProcessTracker()
+ {
+ // This feature requires Windows 8 or later. To support Windows 7 requires
+ // registry settings to be added if you are using Visual Studio plus an
+ // app.manifest change.
+ // https://stackoverflow.com/a/4232259/386091
+ // https://stackoverflow.com/a/9507862/386091
+ /*if (Environment.OSVersion.Version < new Version(6, 2))
+ return;*/
+
+ // The job name is optional (and can be null) but it helps with diagnostics.
+ // If it's not null, it has to be unique. Use SysInternals' Handle command-line
+ // utility: handle -a ChildProcessTracker
+ var jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id;
+ s_jobHandle = CreateJobObject(IntPtr.Zero, jobName);
+
+ var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
+
+ // This is the key flag. When our process is killed, Windows will automatically
+ // close the job handle, and when that happens, we want the child processes to
+ // be killed, too.
+ info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
+ {
+ BasicLimitInformation = info
+ };
+
+ var length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
+ var extendedInfoPtr = Marshal.AllocHGlobal(length);
+ try
+ {
+ Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
+
+ if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation,
+ extendedInfoPtr, (uint) length))
+ {
+ throw new Win32Exception();
+ }
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(extendedInfoPtr);
+ }
+ }
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
+ private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name);
+
+ [DllImport("kernel32.dll")]
+ private static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType,
+ IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
+
+ // Windows will automatically close any open job handles when our process terminates.
+ // This can be verified by using SysInternals' Handle utility. When the job handle
+ // is closed, the child processes will be killed.
+ private static readonly IntPtr s_jobHandle;
+ }
+
+ public enum JobObjectInfoType
+ {
+ AssociateCompletionPortInformation = 7,
+ BasicLimitInformation = 2,
+ BasicUIRestrictions = 4,
+ EndOfJobTimeInformation = 6,
+ ExtendedLimitInformation = 9,
+ SecurityLimitInformation = 5,
+ GroupInformation = 11
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
+ {
+ public Int64 PerProcessUserTimeLimit;
+ public Int64 PerJobUserTimeLimit;
+ public JOBOBJECTLIMIT LimitFlags;
+ public UIntPtr MinimumWorkingSetSize;
+ public UIntPtr MaximumWorkingSetSize;
+ public UInt32 ActiveProcessLimit;
+ public Int64 Affinity;
+ public UInt32 PriorityClass;
+ public UInt32 SchedulingClass;
+ }
+
+ [Flags]
+ public enum JOBOBJECTLIMIT : uint
+ {
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct IO_COUNTERS
+ {
+ public UInt64 ReadOperationCount;
+ public UInt64 WriteOperationCount;
+ public UInt64 OtherOperationCount;
+ public UInt64 ReadTransferCount;
+ public UInt64 WriteTransferCount;
+ public UInt64 OtherTransferCount;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
+ {
+ public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
+ public IO_COUNTERS IoInfo;
+ public UIntPtr ProcessMemoryLimit;
+ public UIntPtr JobMemoryLimit;
+ public UIntPtr PeakProcessMemoryUsed;
+ public UIntPtr PeakJobMemoryUsed;
+ }
+}
\ No newline at end of file