diff --git a/shadowsocks-csharp/Controller/Service/Http2Socks5.cs b/shadowsocks-csharp/Controller/Service/Http2Socks5.cs deleted file mode 100644 index 9c557246..00000000 --- a/shadowsocks-csharp/Controller/Service/Http2Socks5.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Threading.Tasks; -using Shadowsocks.Util.Sockets; - -namespace Shadowsocks.Controller.Service -{ - class Http2Socks5 : Listener.Service - { - public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) - { - if (socket.ProtocolType != ProtocolType.Tcp) - { - return false; - } - - return true; - } - } -} diff --git a/shadowsocks-csharp/Controller/Service/PortForwarder.cs b/shadowsocks-csharp/Controller/Service/PortForwarder.cs new file mode 100644 index 00000000..5429b053 --- /dev/null +++ b/shadowsocks-csharp/Controller/Service/PortForwarder.cs @@ -0,0 +1,261 @@ +using System; +using System.Net; +using System.Net.Sockets; +using Shadowsocks.Util.Sockets; + +namespace Shadowsocks.Controller +{ + class PortForwarder : Listener.Service + { + int _targetPort; + + public PortForwarder(int targetPort) + { + this._targetPort = targetPort; + } + + public override bool Handle(byte[] firstPacket, int length, Socket socket, object state) + { + if (socket.ProtocolType != ProtocolType.Tcp) + { + return false; + } + new Handler().Start(firstPacket, length, socket, this._targetPort); + return true; + } + + class Handler + { + private byte[] _firstPacket; + private int _firstPacketLength; + private Socket _local; + private WrappedSocket _remote; + private bool _closed = false; + private bool _localShutdown = false; + private bool _remoteShutdown = false; + public const int RecvSize = 16384; + // remote receive buffer + private byte[] remoteRecvBuffer = new byte[RecvSize]; + // connection receive buffer + private byte[] connetionRecvBuffer = new byte[RecvSize]; + + // instance-based lock + private readonly object _Lock = new object(); + + public void Start(byte[] firstPacket, int length, Socket socket, int targetPort) + { + this._firstPacket = firstPacket; + this._firstPacketLength = length; + this._local = socket; + try + { + EndPoint remoteEP = SocketUtil.GetEndPoint("127.0.0.1", targetPort); + + // Connect to the remote endpoint. + _remote = new WrappedSocket(); + _remote.BeginConnect(remoteEP, ConnectCallback, null); + } + catch (Exception e) + { + Logging.LogUsefulException(e); + this.Close(); + } + } + + private void ConnectCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + _remote.EndConnect(ar); + _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); + HandshakeReceive(); + } + catch (Exception e) + { + Logging.LogUsefulException(e); + this.Close(); + } + } + + private void HandshakeReceive() + { + if (_closed) + { + return; + } + try + { + _remote.BeginSend(_firstPacket, 0, _firstPacketLength, 0, new AsyncCallback(StartPipe), null); + } + catch (Exception e) + { + Logging.LogUsefulException(e); + this.Close(); + } + } + + private void StartPipe(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + _remote.EndSend(ar); + _remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, + new AsyncCallback(PipeRemoteReceiveCallback), null); + _local.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, + new AsyncCallback(PipeConnectionReceiveCallback), null); + } + catch (Exception e) + { + Logging.LogUsefulException(e); + this.Close(); + } + } + + private void PipeRemoteReceiveCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + int bytesRead = _remote.EndReceive(ar); + + if (bytesRead > 0) + { + _local.BeginSend(remoteRecvBuffer, 0, bytesRead, 0, new AsyncCallback(PipeConnectionSendCallback), null); + } + else + { + _local.Shutdown(SocketShutdown.Send); + _localShutdown = true; + CheckClose(); + } + } + catch (Exception e) + { + Logging.LogUsefulException(e); + this.Close(); + } + } + + private void PipeConnectionReceiveCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + int bytesRead = _local.EndReceive(ar); + + if (bytesRead > 0) + { + _remote.BeginSend(connetionRecvBuffer, 0, bytesRead, 0, new AsyncCallback(PipeRemoteSendCallback), null); + } + else + { + _remote.Shutdown(SocketShutdown.Send); + _remoteShutdown = true; + CheckClose(); + } + } + catch (Exception e) + { + Logging.LogUsefulException(e); + this.Close(); + } + } + + private void PipeRemoteSendCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + _remote.EndSend(ar); + _local.BeginReceive(this.connetionRecvBuffer, 0, RecvSize, 0, + new AsyncCallback(PipeConnectionReceiveCallback), null); + } + catch (Exception e) + { + Logging.LogUsefulException(e); + this.Close(); + } + } + + private void PipeConnectionSendCallback(IAsyncResult ar) + { + if (_closed) + { + return; + } + try + { + _local.EndSend(ar); + _remote.BeginReceive(this.remoteRecvBuffer, 0, RecvSize, 0, + new AsyncCallback(PipeRemoteReceiveCallback), null); + } + catch (Exception e) + { + Logging.LogUsefulException(e); + this.Close(); + } + } + + private void CheckClose() + { + if (_localShutdown && _remoteShutdown) + { + this.Close(); + } + } + + public void Close() + { + lock (_Lock) + { + if (_closed) + { + return; + } + _closed = true; + } + if (_local != null) + { + try + { + _local.Shutdown(SocketShutdown.Both); + _local.Close(); + } + catch (Exception e) + { + Logging.LogUsefulException(e); + } + } + if (_remote != null) + { + try + { + _remote.Shutdown(SocketShutdown.Both); + _remote.Dispose(); + } + catch (SocketException e) + { + Logging.LogUsefulException(e); + } + } + } + } + } +} diff --git a/shadowsocks-csharp/Controller/Service/PrivoxyRunner.cs b/shadowsocks-csharp/Controller/Service/PrivoxyRunner.cs new file mode 100644 index 00000000..1dff5add --- /dev/null +++ b/shadowsocks-csharp/Controller/Service/PrivoxyRunner.cs @@ -0,0 +1,216 @@ +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.Net.Sockets; +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 PrivoxyRunner + { + private static int Uid; + private static string UniqueConfigFile; + private static Job PrivoxyJob; + private Process _process; + private int _runningPort; + + static PrivoxyRunner() + { + try + { + Uid = Application.StartupPath.GetHashCode(); // Currently we use ss's StartupPath to identify different Privoxy instance. + UniqueConfigFile = $"privoxy_{Uid}.conf"; + PrivoxyJob = new Job(); + + FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe); + FileManager.UncompressFile(Utils.GetTempPath("mgwz.dll"), Resources.mgwz_dll); + } + catch (IOException e) + { + Logging.LogUsefulException(e); + } + } + + public int RunningPort => _runningPort; + + public void Start(Configuration configuration) + { + if (_process == null) + { + Process[] existingPrivoxy = Process.GetProcessesByName("ss_privoxy"); + foreach (Process p in existingPrivoxy.Where(IsChildProcess)) + { + KillProcess(p); + } + string privoxyConfig = Resources.privoxy_conf; + _runningPort = this.GetFreePort(); + privoxyConfig = privoxyConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString()); + privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_PORT__", _runningPort.ToString()); + privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1"); + FileManager.ByteArrayToFile(Utils.GetTempPath(UniqueConfigFile), Encoding.UTF8.GetBytes(privoxyConfig)); + + _process = new Process(); + // Configure the process using the StartInfo properties. + _process.StartInfo.FileName = "ss_privoxy.exe"; + _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. + */ + PrivoxyJob.AddProcess(_process.Handle); + } + RefreshTrayArea(); + } + + public void Stop() + { + if (_process != null) + { + KillProcess(_process); + _process.Dispose(); + _process = null; + } + RefreshTrayArea(); + } + + private static void KillProcess(Process p) + { + try + { + p.CloseMainWindow(); + p.WaitForExit(100); + if (!p.HasExited) + { + p.Kill(); + p.WaitForExit(); + } + } + catch (Exception e) + { + Logging.LogUsefulException(e); + } + } + + /* + * 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) + { + try + { + if (Utils.IsPortableMode()) + { + /* + * Under PortableMode, we could identify it by the path of ss_privoxy.exe. + */ + var path = process.MainModule.FileName; + + return Utils.GetTempPath("ss_privoxy.exe").Equals(path); + } + else + { + var cmd = process.GetCommandLine(); + + return cmd.Contains(UniqueConfigFile); + } + } + catch (Exception ex) + { + /* + * Sometimes Process.GetProcessesByName will return some processes that + * are already dead, and that will cause exceptions here. + * We could simply ignore those exceptions. + */ + Logging.LogUsefulException(ex); + return false; + } + } + + private int GetFreePort() + { + int defaultPort = 8123; + try + { + // TCP stack please do me a favor + TcpListener l = new TcpListener(IPAddress.Loopback, 0); + l.Start(); + var port = ((IPEndPoint)l.LocalEndpoint).Port; + l.Stop(); + return port; + } + catch (Exception e) + { + // in case access denied + Logging.LogUsefulException(e); + return defaultPort; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + } + [DllImport("user32.dll")] + public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + [DllImport("user32.dll")] + public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); + [DllImport("user32.dll")] + public static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); + [DllImport("user32.dll")] + public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam); + + public void RefreshTrayArea() + { + IntPtr systemTrayContainerHandle = FindWindow("Shell_TrayWnd", null); + IntPtr systemTrayHandle = FindWindowEx(systemTrayContainerHandle, IntPtr.Zero, "TrayNotifyWnd", null); + IntPtr sysPagerHandle = FindWindowEx(systemTrayHandle, IntPtr.Zero, "SysPager", null); + IntPtr notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "Notification Area"); + if (notificationAreaHandle == IntPtr.Zero) + { + notificationAreaHandle = FindWindowEx(sysPagerHandle, IntPtr.Zero, "ToolbarWindow32", "User Promoted Notification Area"); + IntPtr notifyIconOverflowWindowHandle = FindWindow("NotifyIconOverflowWindow", null); + IntPtr overflowNotificationAreaHandle = FindWindowEx(notifyIconOverflowWindowHandle, IntPtr.Zero, "ToolbarWindow32", "Overflow Notification Area"); + RefreshTrayArea(overflowNotificationAreaHandle); + } + RefreshTrayArea(notificationAreaHandle); + } + + private static void RefreshTrayArea(IntPtr windowHandle) + { + const uint wmMousemove = 0x0200; + RECT rect; + GetClientRect(windowHandle, out rect); + for (var x = 0; x < rect.right; x += 5) + for (var y = 0; y < rect.bottom; y += 5) + SendMessage(windowHandle, wmMousemove, 0, (y << 16) + x); + } + } +} diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index c424b232..c287bd29 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -30,6 +30,7 @@ namespace Shadowsocks.Controller private PACServer _pacServer; private Configuration _config; private StrategyManager _strategyManager; + private PrivoxyRunner privoxyRunner; private GFWListUpdater gfwListUpdater; public AvailabilityStatistics availabilityStatistics = AvailabilityStatistics.Instance; public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; } @@ -264,6 +265,10 @@ namespace Shadowsocks.Controller { _listener.Stop(); } + if (privoxyRunner != null) + { + privoxyRunner.Stop(); + } if (_config.enabled) { SystemProxy.Update(_config, true, null); @@ -427,7 +432,11 @@ namespace Shadowsocks.Controller // some logic in configuration updated the config when saving, we need to read it again _config = Configuration.Load(); StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); - + + if (privoxyRunner == null) + { + privoxyRunner = new PrivoxyRunner(); + } if (_pacServer == null) { _pacServer = new PACServer(); @@ -448,7 +457,11 @@ namespace Shadowsocks.Controller { _listener.Stop(); } - + // don't put PrivoxyRunner.Start() before pacServer.Stop() + // or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1 + // though UseShellExecute is set to true now + // http://stackoverflow.com/questions/10235093/socket-doesnt-close-after-application-exits-if-a-launched-process-is-open + privoxyRunner.Stop(); try { var strategy = GetCurrentStrategy(); @@ -457,12 +470,15 @@ namespace Shadowsocks.Controller strategy.ReloadServers(); } + privoxyRunner.Start(_config); + TCPRelay tcpRelay = new TCPRelay(this, _config); UDPRelay udpRelay = new UDPRelay(this); List services = new List(); services.Add(tcpRelay); services.Add(udpRelay); services.Add(_pacServer); + services.Add(new PortForwarder(privoxyRunner.RunningPort)); _listener = new Listener(services); _listener.Start(_config); } diff --git a/shadowsocks-csharp/Data/mgwz.dll.gz b/shadowsocks-csharp/Data/mgwz.dll.gz new file mode 100644 index 00000000..40a33e5d Binary files /dev/null and b/shadowsocks-csharp/Data/mgwz.dll.gz differ diff --git a/shadowsocks-csharp/Data/privoxy.exe.gz b/shadowsocks-csharp/Data/privoxy.exe.gz new file mode 100644 index 00000000..f42c68d7 Binary files /dev/null and b/shadowsocks-csharp/Data/privoxy.exe.gz differ diff --git a/shadowsocks-csharp/Data/privoxy_conf.txt b/shadowsocks-csharp/Data/privoxy_conf.txt new file mode 100644 index 00000000..5d274c4c --- /dev/null +++ b/shadowsocks-csharp/Data/privoxy_conf.txt @@ -0,0 +1,5 @@ +listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__ +show-on-task-bar 0 +activity-animation 0 +forward-socks5 / 127.0.0.1:__SOCKS_PORT__ . +hide-console diff --git a/shadowsocks-csharp/Properties/Resources.Designer.cs b/shadowsocks-csharp/Properties/Resources.Designer.cs index 120ddd38..88757c3a 100644 --- a/shadowsocks-csharp/Properties/Resources.Designer.cs +++ b/shadowsocks-csharp/Properties/Resources.Designer.cs @@ -111,6 +111,40 @@ namespace Shadowsocks.Properties { } } + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] mgwz_dll { + get { + object obj = ResourceManager.GetObject("mgwz_dll", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// 查找类似 listen-address __PRIVOXY_BIND_IP__:__PRIVOXY_BIND_PORT__ + ///show-on-task-bar 0 + ///activity-animation 0 + ///forward-socks5 / 127.0.0.1:__SOCKS_PORT__ . + ///hide-console + /// 的本地化字符串。 + /// + internal static string privoxy_conf { + get { + return ResourceManager.GetString("privoxy_conf", resourceCulture); + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] privoxy_exe { + get { + object obj = ResourceManager.GetObject("privoxy_exe", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// 查找 System.Byte[] 类型的本地化资源。 /// @@ -213,29 +247,29 @@ namespace Shadowsocks.Properties { } /// - /// 查找类似 # translation for Traditional Chinese - /// - ///Shadowsocks=Shadowsocks - /// - ///# Menu items - /// - ///Enable System Proxy=啟用系統代理 - ///Mode=系統代理模式 - ///PAC=PAC 模式 - ///Global=全局模式 - ///Servers=伺服器 - ///Edit Servers...=編輯伺服器... - ///Statistics Config...=統計配置... - ///Start on Boot=開機啟動 - ///Forward Proxy...=正向代理設置... - ///Allow Clients from LAN=允許來自區域網路的連接 - ///Local PAC=使用本地 PAC - ///Online PAC=使用在線 PAC - ///Edit Local PAC File...=編輯本地 PAC 文件... - ///Update Local PAC from GFWList=從 GFWList 更新本地 PAC - ///Edit User Rule for GFWList...=編輯 GFWList 的用戶規則... - ///Show QRCode...=顯示 QR 碼... - ///S [字符串的其余部分被截断]"; 的本地化字符串。 + /// 查找类似 # translation for Traditional Chinese + /// + ///Shadowsocks=Shadowsocks + /// + ///# Menu items + /// + ///Enable System Proxy=啟用系統代理 + ///Mode=系統代理模式 + ///PAC=PAC 模式 + ///Global=全局模式 + ///Servers=伺服器 + ///Edit Servers...=編輯伺服器... + ///Statistics Config...=統計配置... + ///Start on Boot=開機啟動 + ///Forward Proxy...=正向代理設置... + ///Allow Clients from LAN=允許來自區域網路的連接 + ///Local PAC=使用本地 PAC + ///Online PAC=使用在線 PAC + ///Edit Local PAC File...=編輯本地 PAC 文件... + ///Update Local PAC from GFWList=從 GFWList 更新本地 PAC + ///Edit User Rule for GFWList...=編輯 GFWList 的用戶規則... + ///Show QRCode...=顯示 QR 碼... + ///Scan QRCode from Screen [字符串的其余部分被截断]"; 的本地化字符串。 /// internal static string zh_tw { get { diff --git a/shadowsocks-csharp/Properties/Resources.resx b/shadowsocks-csharp/Properties/Resources.resx index 74807cab..eaa37bd3 100755 --- a/shadowsocks-csharp/Properties/Resources.resx +++ b/shadowsocks-csharp/Properties/Resources.resx @@ -127,6 +127,15 @@ ..\data\libsscrypto.dll.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\data\mgwz.dll.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\data\privoxy_conf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + + ..\data\privoxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Data\proxy.pac.txt.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/shadowsocks-csharp/Util/ProcessManagement/Job.cs b/shadowsocks-csharp/Util/ProcessManagement/Job.cs new file mode 100644 index 00000000..dd82134d --- /dev/null +++ b/shadowsocks-csharp/Util/ProcessManagement/Job.cs @@ -0,0 +1,179 @@ +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 + { + private IntPtr handle = IntPtr.Zero; + + public Job() + { + handle = CreateJobObject(IntPtr.Zero, null); + var extendedInfoPtr = IntPtr.Zero; + var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION + { + LimitFlags = 0x2000 + }; + + var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION + { + BasicLimitInformation = info + }; + + try + { + int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + 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())); + } + finally + { + if (extendedInfoPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(extendedInfoPtr); + extendedInfoPtr = IntPtr.Zero; + } + } + } + + public bool AddProcess(IntPtr processHandle) + { + var succ = AssignProcessToJobObject(handle, processHandle); + + if (!succ) + { + Logging.Error("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error()); + } + + return succ; + } + + public bool AddProcess(int processId) + { + return AddProcess(Process.GetProcessById(processId).Handle); + } + + #region IDisposable + + private bool disposed; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposed) return; + disposed = true; + + if (disposing) + { + // no managed objects to free + } + + if (handle != IntPtr.Zero) + { + CloseHandle(handle); + handle = IntPtr.Zero; + } + } + + ~Job() + { + Dispose(false); + } + + #endregion + + #region Interop + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern IntPtr CreateJobObject(IntPtr a, string lpName); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(IntPtr hObject); + + #endregion + } + + #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/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 623cecbc..b97f29c4 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -140,7 +140,6 @@ - @@ -161,6 +160,7 @@ + @@ -189,6 +189,8 @@ + + @@ -204,6 +206,7 @@ ConfigForm.cs + @@ -281,6 +284,8 @@ + + @@ -297,6 +302,7 @@ +