* Use job object to manage polipo service's external processes. Signed-off-by: noisyfox <timemanager.rick@gmail.com> * Only kill those ss_privoxy.exe created by this ss instance. Signed-off-by: noisyfox <timemanager.rick@gmail.com> * Update README Signed-off-by: noisyfox <timemanager.rick@gmail.com>tags/3.3
@@ -54,9 +54,6 @@ If you want to manage multiple servers using other tools like SwitchyOmega, | |||||
you can start multiple Shadowsocks instances. To avoid configuration conflicts, | you can start multiple Shadowsocks instances. To avoid configuration conflicts, | ||||
copy Shadowsocks to a new directory and choose a different local port. | copy Shadowsocks to a new directory and choose a different local port. | ||||
Also, make sure to use `SOCKS5` proxy in SwitchyOmega, since we have only | |||||
one HTTP proxy instance. | |||||
#### Server Configuration | #### Server Configuration | ||||
Please visit [Servers] for more information. | Please visit [Servers] for more information. | ||||
@@ -1,20 +1,26 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.ComponentModel; | |||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.IO; | using System.IO; | ||||
using System.Linq; | |||||
using System.Net; | using System.Net; | ||||
using System.Net.NetworkInformation; | using System.Net.NetworkInformation; | ||||
using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||
using System.Text; | using System.Text; | ||||
using System.Windows.Forms; | |||||
using Shadowsocks.Model; | using Shadowsocks.Model; | ||||
using Shadowsocks.Properties; | using Shadowsocks.Properties; | ||||
using Shadowsocks.Util; | using Shadowsocks.Util; | ||||
using Shadowsocks.Util.ProcessManagement; | |||||
namespace Shadowsocks.Controller | namespace Shadowsocks.Controller | ||||
{ | { | ||||
class PolipoRunner | class PolipoRunner | ||||
{ | { | ||||
private static int Uid; | |||||
private static string UniqueConfigFile; | |||||
private static Job PolipoJob; | |||||
private Process _process; | private Process _process; | ||||
private int _runningPort; | private int _runningPort; | ||||
@@ -22,6 +28,10 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
Uid = Application.StartupPath.GetHashCode(); // Currently we use ss's StartupPath to identify different polipo instance. | |||||
UniqueConfigFile = $"privoxy_{Uid}.conf"; | |||||
PolipoJob = new Job(); | |||||
FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe); | FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe); | ||||
FileManager.UncompressFile(Utils.GetTempPath("mgwz.dll"), Resources.mgwz_dll); | FileManager.UncompressFile(Utils.GetTempPath("mgwz.dll"), Resources.mgwz_dll); | ||||
} | } | ||||
@@ -45,7 +55,7 @@ namespace Shadowsocks.Controller | |||||
if (_process == null) | if (_process == null) | ||||
{ | { | ||||
Process[] existingPolipo = Process.GetProcessesByName("ss_privoxy"); | Process[] existingPolipo = Process.GetProcessesByName("ss_privoxy"); | ||||
foreach (Process p in existingPolipo) | |||||
foreach (Process p in existingPolipo.Where(IsChildProcess)) | |||||
{ | { | ||||
KillProcess(p); | KillProcess(p); | ||||
} | } | ||||
@@ -54,17 +64,23 @@ namespace Shadowsocks.Controller | |||||
polipoConfig = polipoConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString()); | polipoConfig = polipoConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString()); | ||||
polipoConfig = polipoConfig.Replace("__POLIPO_BIND_PORT__", _runningPort.ToString()); | polipoConfig = polipoConfig.Replace("__POLIPO_BIND_PORT__", _runningPort.ToString()); | ||||
polipoConfig = polipoConfig.Replace("__POLIPO_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1"); | polipoConfig = polipoConfig.Replace("__POLIPO_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1"); | ||||
FileManager.ByteArrayToFile(Utils.GetTempPath("privoxy.conf"), Encoding.UTF8.GetBytes(polipoConfig)); | |||||
FileManager.ByteArrayToFile(Utils.GetTempPath(UniqueConfigFile), Encoding.UTF8.GetBytes(polipoConfig)); | |||||
_process = new Process(); | _process = new Process(); | ||||
// Configure the process using the StartInfo properties. | // Configure the process using the StartInfo properties. | ||||
_process.StartInfo.FileName = "ss_privoxy.exe"; | _process.StartInfo.FileName = "ss_privoxy.exe"; | ||||
_process.StartInfo.Arguments = "privoxy.conf"; | |||||
_process.StartInfo.Arguments = UniqueConfigFile; | |||||
_process.StartInfo.WorkingDirectory = Utils.GetTempPath(); | _process.StartInfo.WorkingDirectory = Utils.GetTempPath(); | ||||
_process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; | _process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; | ||||
_process.StartInfo.UseShellExecute = true; | _process.StartInfo.UseShellExecute = true; | ||||
_process.StartInfo.CreateNoWindow = true; | _process.StartInfo.CreateNoWindow = true; | ||||
_process.Start(); | _process.Start(); | ||||
/* | |||||
* Add this process to job obj associated with this ss process, so that | |||||
* when ss exit unexpectedly, this process will be forced killed by system. | |||||
*/ | |||||
PolipoJob.AddProcess(_process.Handle); | |||||
} | } | ||||
RefreshTrayArea(); | RefreshTrayArea(); | ||||
} | } | ||||
@@ -97,6 +113,45 @@ namespace Shadowsocks.Controller | |||||
} | } | ||||
} | } | ||||
/* | |||||
* We won't like to kill other ss instances' ss_privoxy.exe. | |||||
* This function will check whether the given process is created | |||||
* by this process by checking the module path or command line. | |||||
* | |||||
* Since it's required to put ss in different dirs to run muti instances, | |||||
* different instance will create their unique "privoxy_UID.conf" where | |||||
* UID is hash of ss's location. | |||||
*/ | |||||
private static bool IsChildProcess(Process process) | |||||
{ | |||||
if (Utils.IsPortableMode()) | |||||
{ | |||||
/* | |||||
* Under PortableMode, we could identify it by the path of ss_privoxy.exe. | |||||
*/ | |||||
string path = process.MainModule.FileName; | |||||
return Utils.GetTempPath("ss_privoxy.exe").Equals(path); | |||||
} | |||||
else | |||||
{ | |||||
try | |||||
{ | |||||
var cmd = process.GetCommandLine(); | |||||
return cmd.Contains(UniqueConfigFile); | |||||
} | |||||
catch (Win32Exception ex) | |||||
{ | |||||
if ((uint) ex.ErrorCode != 0x80004005) | |||||
{ | |||||
throw; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
} | |||||
private int GetFreePort() | private int GetFreePort() | ||||
{ | { | ||||
int defaultPort = 8123; | int defaultPort = 8123; | ||||
@@ -0,0 +1,154 @@ | |||||
using System; | |||||
using System.Diagnostics; | |||||
using System.Runtime.InteropServices; | |||||
using Shadowsocks.Controller; | |||||
namespace Shadowsocks.Util.ProcessManagement | |||||
{ | |||||
/* | |||||
* See: | |||||
* http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net | |||||
*/ | |||||
public class Job : IDisposable | |||||
{ | |||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] | |||||
static extern IntPtr CreateJobObject(IntPtr a, string lpName); | |||||
[DllImport("kernel32.dll")] | |||||
static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength); | |||||
[DllImport("kernel32.dll", SetLastError = true)] | |||||
static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); | |||||
[DllImport("kernel32.dll", SetLastError = true)] | |||||
[return: MarshalAs(UnmanagedType.Bool)] | |||||
static extern bool CloseHandle(IntPtr hObject); | |||||
private IntPtr handle; | |||||
private bool disposed; | |||||
public Job() | |||||
{ | |||||
handle = CreateJobObject(IntPtr.Zero, null); | |||||
var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION | |||||
{ | |||||
LimitFlags = 0x2000 | |||||
}; | |||||
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION | |||||
{ | |||||
BasicLimitInformation = info | |||||
}; | |||||
int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); | |||||
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); | |||||
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); | |||||
if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length)) | |||||
throw new Exception(string.Format("Unable to set information. Error: {0}", Marshal.GetLastWin32Error())); | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
Dispose(true); | |||||
GC.SuppressFinalize(this); | |||||
} | |||||
private void Dispose(bool disposing) | |||||
{ | |||||
if (disposed) | |||||
return; | |||||
if (disposing) { } | |||||
Close(); | |||||
disposed = true; | |||||
} | |||||
public void Close() | |||||
{ | |||||
CloseHandle(handle); | |||||
handle = IntPtr.Zero; | |||||
} | |||||
public bool AddProcess(IntPtr processHandle) | |||||
{ | |||||
var succ = AssignProcessToJobObject(handle, processHandle); | |||||
if (!succ) | |||||
{ | |||||
var err = Marshal.GetLastWin32Error(); | |||||
Logging.Error("Failed to call AssignProcessToJobObject! GetLastError=" + err); | |||||
} | |||||
return succ; | |||||
} | |||||
public bool AddProcess(int processId) | |||||
{ | |||||
return AddProcess(Process.GetProcessById(processId).Handle); | |||||
} | |||||
} | |||||
#region Helper classes | |||||
[StructLayout(LayoutKind.Sequential)] | |||||
struct IO_COUNTERS | |||||
{ | |||||
public UInt64 ReadOperationCount; | |||||
public UInt64 WriteOperationCount; | |||||
public UInt64 OtherOperationCount; | |||||
public UInt64 ReadTransferCount; | |||||
public UInt64 WriteTransferCount; | |||||
public UInt64 OtherTransferCount; | |||||
} | |||||
[StructLayout(LayoutKind.Sequential)] | |||||
struct JOBOBJECT_BASIC_LIMIT_INFORMATION | |||||
{ | |||||
public Int64 PerProcessUserTimeLimit; | |||||
public Int64 PerJobUserTimeLimit; | |||||
public UInt32 LimitFlags; | |||||
public UIntPtr MinimumWorkingSetSize; | |||||
public UIntPtr MaximumWorkingSetSize; | |||||
public UInt32 ActiveProcessLimit; | |||||
public UIntPtr Affinity; | |||||
public UInt32 PriorityClass; | |||||
public UInt32 SchedulingClass; | |||||
} | |||||
[StructLayout(LayoutKind.Sequential)] | |||||
public struct SECURITY_ATTRIBUTES | |||||
{ | |||||
public UInt32 nLength; | |||||
public IntPtr lpSecurityDescriptor; | |||||
public Int32 bInheritHandle; | |||||
} | |||||
[StructLayout(LayoutKind.Sequential)] | |||||
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; | |||||
} | |||||
public enum JobObjectInfoType | |||||
{ | |||||
AssociateCompletionPortInformation = 7, | |||||
BasicLimitInformation = 2, | |||||
BasicUIRestrictions = 4, | |||||
EndOfJobTimeInformation = 6, | |||||
ExtendedLimitInformation = 9, | |||||
SecurityLimitInformation = 5, | |||||
GroupInformation = 11 | |||||
} | |||||
#endregion | |||||
} |
@@ -0,0 +1,31 @@ | |||||
using System.Diagnostics; | |||||
using System.Management; | |||||
using System.Text; | |||||
namespace Shadowsocks.Util.ProcessManagement | |||||
{ | |||||
static class ThreadUtil | |||||
{ | |||||
/* | |||||
* See: | |||||
* http://stackoverflow.com/questions/2633628/can-i-get-command-line-arguments-of-other-processes-from-net-c | |||||
*/ | |||||
public static string GetCommandLine(this Process process) | |||||
{ | |||||
var commandLine = new StringBuilder(process.MainModule.FileName); | |||||
commandLine.Append(" "); | |||||
using (var searcher = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId = " + process.Id)) | |||||
{ | |||||
foreach (var @object in searcher.Get()) | |||||
{ | |||||
commandLine.Append(@object["CommandLine"]); | |||||
commandLine.Append(" "); | |||||
} | |||||
} | |||||
return commandLine.ToString(); | |||||
} | |||||
} | |||||
} |
@@ -11,14 +11,25 @@ namespace Shadowsocks.Util | |||||
{ | { | ||||
public class Utils | public class Utils | ||||
{ | { | ||||
private static bool? _portableMode; | |||||
private static string TempPath = null; | private static string TempPath = null; | ||||
public static bool IsPortableMode() | |||||
{ | |||||
if (!_portableMode.HasValue) | |||||
{ | |||||
_portableMode = File.Exists(Path.Combine(Application.StartupPath, "shadowsocks_portable_mode.txt")); | |||||
} | |||||
return _portableMode.Value; | |||||
} | |||||
// return path to store temporary files | // return path to store temporary files | ||||
public static string GetTempPath() | public static string GetTempPath() | ||||
{ | { | ||||
if (TempPath == null) | if (TempPath == null) | ||||
{ | { | ||||
if (File.Exists(Path.Combine(Application.StartupPath, "shadowsocks_portable_mode.txt"))) | |||||
if (IsPortableMode()) | |||||
try | try | ||||
{ | { | ||||
Directory.CreateDirectory(Path.Combine(Application.StartupPath, "temp")); | Directory.CreateDirectory(Path.Combine(Application.StartupPath, "temp")); | ||||
@@ -76,6 +76,7 @@ | |||||
<Reference Include="System" /> | <Reference Include="System" /> | ||||
<Reference Include="System.Data" /> | <Reference Include="System.Data" /> | ||||
<Reference Include="System.Drawing" /> | <Reference Include="System.Drawing" /> | ||||
<Reference Include="System.Management" /> | |||||
<Reference Include="System.Net" /> | <Reference Include="System.Net" /> | ||||
<Reference Include="System.Windows.Forms" /> | <Reference Include="System.Windows.Forms" /> | ||||
<Reference Include="System.Windows.Forms.DataVisualization" /> | <Reference Include="System.Windows.Forms.DataVisualization" /> | ||||
@@ -176,6 +177,8 @@ | |||||
<Compile Include="Controller\Strategy\IStrategy.cs" /> | <Compile Include="Controller\Strategy\IStrategy.cs" /> | ||||
<Compile Include="Proxy\Socks5Proxy.cs" /> | <Compile Include="Proxy\Socks5Proxy.cs" /> | ||||
<Compile Include="StringEx.cs" /> | <Compile Include="StringEx.cs" /> | ||||
<Compile Include="Util\ProcessManagement\Job.cs" /> | |||||
<Compile Include="Util\ProcessManagement\ThreadUtil.cs" /> | |||||
<Compile Include="Util\SocketUtil.cs" /> | <Compile Include="Util\SocketUtil.cs" /> | ||||
<Compile Include="Util\Util.cs" /> | <Compile Include="Util\Util.cs" /> | ||||
<Compile Include="View\ConfigForm.cs"> | <Compile Include="View\ConfigForm.cs"> | ||||