Browse Source

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>
tags/3.3
Noisyfox Syrone Wong 8 years ago
parent
commit
a1eef6eac1
6 changed files with 259 additions and 8 deletions
  1. +0
    -3
      README.md
  2. +59
    -4
      shadowsocks-csharp/Controller/Service/PolipoRunner.cs
  3. +154
    -0
      shadowsocks-csharp/Util/ProcessManagement/Job.cs
  4. +31
    -0
      shadowsocks-csharp/Util/ProcessManagement/ThreadUtil.cs
  5. +12
    -1
      shadowsocks-csharp/Util/Util.cs
  6. +3
    -0
      shadowsocks-csharp/shadowsocks-csharp.csproj

+ 0
- 3
README.md 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, 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.


+ 59
- 4
shadowsocks-csharp/Controller/Service/PolipoRunner.cs View File

@@ -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;


+ 154
- 0
shadowsocks-csharp/Util/ProcessManagement/Job.cs View File

@@ -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
}

+ 31
- 0
shadowsocks-csharp/Util/ProcessManagement/ThreadUtil.cs View File

@@ -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
shadowsocks-csharp/Util/Util.cs View File

@@ -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"));


+ 3
- 0
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -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">


Loading…
Cancel
Save