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,
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.


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

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


+ 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
{
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"));


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

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


Loading…
Cancel
Save