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:
Noisyfox
2016-08-18 19:31:01 +08:00
committed by Syrone Wong
parent e30487207b
commit a1eef6eac1
6 changed files with 259 additions and 8 deletions
-3
View File
@@ -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();
}
}
}
+12 -1
View File
@@ -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">