diff --git a/README.md b/README.md
index b4952dd8..b2758e94 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs
index a8b1e2d2..cc4d1b35 100644
--- a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs
+++ b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs
@@ -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;
diff --git a/shadowsocks-csharp/Util/ProcessManagement/Job.cs b/shadowsocks-csharp/Util/ProcessManagement/Job.cs
new file mode 100644
index 00000000..b79444d6
--- /dev/null
+++ b/shadowsocks-csharp/Util/ProcessManagement/Job.cs
@@ -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
+}
diff --git a/shadowsocks-csharp/Util/ProcessManagement/ThreadUtil.cs b/shadowsocks-csharp/Util/ProcessManagement/ThreadUtil.cs
new file mode 100644
index 00000000..9421638f
--- /dev/null
+++ b/shadowsocks-csharp/Util/ProcessManagement/ThreadUtil.cs
@@ -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();
+ }
+ }
+}
diff --git a/shadowsocks-csharp/Util/Util.cs b/shadowsocks-csharp/Util/Util.cs
index fdb8482e..872147b6 100755
--- a/shadowsocks-csharp/Util/Util.cs
+++ b/shadowsocks-csharp/Util/Util.cs
@@ -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"));
diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj
index 10e2c7d2..a9d6cbc3 100644
--- a/shadowsocks-csharp/shadowsocks-csharp.csproj
+++ b/shadowsocks-csharp/shadowsocks-csharp.csproj
@@ -76,6 +76,7 @@
+
@@ -176,6 +177,8 @@
+
+