Multiple instances of ss_privoxy (#665)
* 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>
This commit is contained in:
@@ -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,
|
||||
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
|
||||
|
||||
Please visit [Servers] for more information.
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using System.Windows.Forms;
|
||||
using Shadowsocks.Model;
|
||||
using Shadowsocks.Properties;
|
||||
using Shadowsocks.Util;
|
||||
using Shadowsocks.Util.ProcessManagement;
|
||||
|
||||
namespace Shadowsocks.Controller
|
||||
{
|
||||
class PolipoRunner
|
||||
{
|
||||
private static int Uid;
|
||||
private static string UniqueConfigFile;
|
||||
private static Job PolipoJob;
|
||||
private Process _process;
|
||||
private int _runningPort;
|
||||
|
||||
@@ -22,6 +28,10 @@ namespace Shadowsocks.Controller
|
||||
{
|
||||
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("mgwz.dll"), Resources.mgwz_dll);
|
||||
}
|
||||
@@ -45,7 +55,7 @@ namespace Shadowsocks.Controller
|
||||
if (_process == null)
|
||||
{
|
||||
Process[] existingPolipo = Process.GetProcessesByName("ss_privoxy");
|
||||
foreach (Process p in existingPolipo)
|
||||
foreach (Process p in existingPolipo.Where(IsChildProcess))
|
||||
{
|
||||
KillProcess(p);
|
||||
}
|
||||
@@ -54,17 +64,23 @@ namespace Shadowsocks.Controller
|
||||
polipoConfig = polipoConfig.Replace("__SOCKS_PORT__", configuration.localPort.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");
|
||||
FileManager.ByteArrayToFile(Utils.GetTempPath("privoxy.conf"), Encoding.UTF8.GetBytes(polipoConfig));
|
||||
FileManager.ByteArrayToFile(Utils.GetTempPath(UniqueConfigFile), Encoding.UTF8.GetBytes(polipoConfig));
|
||||
|
||||
_process = new Process();
|
||||
// Configure the process using the StartInfo properties.
|
||||
_process.StartInfo.FileName = "ss_privoxy.exe";
|
||||
_process.StartInfo.Arguments = "privoxy.conf";
|
||||
_process.StartInfo.Arguments = UniqueConfigFile;
|
||||
_process.StartInfo.WorkingDirectory = Utils.GetTempPath();
|
||||
_process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
_process.StartInfo.UseShellExecute = true;
|
||||
_process.StartInfo.CreateNoWindow = true;
|
||||
_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();
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
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
|
||||
{
|
||||
private static bool? _portableMode;
|
||||
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
|
||||
public static string GetTempPath()
|
||||
{
|
||||
if (TempPath == null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(Application.StartupPath, "shadowsocks_portable_mode.txt")))
|
||||
if (IsPortableMode())
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(Application.StartupPath, "temp"));
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Windows.Forms.DataVisualization" />
|
||||
@@ -176,6 +177,8 @@
|
||||
<Compile Include="Controller\Strategy\IStrategy.cs" />
|
||||
<Compile Include="Proxy\Socks5Proxy.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\Util.cs" />
|
||||
<Compile Include="View\ConfigForm.cs">
|
||||
|
||||
Reference in New Issue
Block a user