@@ -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; | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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<Listener.IService> services = new List<Listener.IService>(); | |||
services.Add(tcpRelay); | |||
services.Add(udpRelay); | |||
services.Add(_pacServer); | |||
services.Add(new PortForwarder(privoxyRunner.RunningPort)); | |||
_listener = new Listener(services); | |||
_listener.Start(_config); | |||
} | |||
@@ -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 |
@@ -111,6 +111,40 @@ namespace Shadowsocks.Properties { | |||
} | |||
} | |||
/// <summary> | |||
/// 查找 System.Byte[] 类型的本地化资源。 | |||
/// </summary> | |||
internal static byte[] mgwz_dll { | |||
get { | |||
object obj = ResourceManager.GetObject("mgwz_dll", resourceCulture); | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// 查找类似 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 | |||
/// 的本地化字符串。 | |||
/// </summary> | |||
internal static string privoxy_conf { | |||
get { | |||
return ResourceManager.GetString("privoxy_conf", resourceCulture); | |||
} | |||
} | |||
/// <summary> | |||
/// 查找 System.Byte[] 类型的本地化资源。 | |||
/// </summary> | |||
internal static byte[] privoxy_exe { | |||
get { | |||
object obj = ResourceManager.GetObject("privoxy_exe", resourceCulture); | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// 查找 System.Byte[] 类型的本地化资源。 | |||
/// </summary> | |||
@@ -213,29 +247,29 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找类似 # 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 [字符串的其余部分被截断]"; 的本地化字符串。 | |||
/// </summary> | |||
internal static string zh_tw { | |||
get { | |||
@@ -127,6 +127,15 @@ | |||
<data name="libsscrypto_dll" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\libsscrypto.dll.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="mgwz_dll" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\mgwz.dll.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="privoxy_conf" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\privoxy_conf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value> | |||
</data> | |||
<data name="privoxy_exe" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\data\privoxy.exe.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="proxy_pac_txt" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\proxy.pac.txt.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
@@ -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 | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -140,7 +140,6 @@ | |||
<Compile Include="3rd\zxing\ResultPoint.cs" /> | |||
<Compile Include="3rd\zxing\ResultPointCallback.cs" /> | |||
<Compile Include="3rd\zxing\WriterException.cs" /> | |||
<Compile Include="Controller\Service\Http2Socks5.cs" /> | |||
<Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" /> | |||
<Compile Include="Model\HotKeyConfig.cs" /> | |||
<Compile Include="Model\ProxyConfig.cs" /> | |||
@@ -161,6 +160,7 @@ | |||
<Compile Include="Controller\I18N.cs" /> | |||
<Compile Include="Controller\Service\Listener.cs" /> | |||
<Compile Include="Controller\Logging.cs" /> | |||
<Compile Include="Controller\Service\PortForwarder.cs" /> | |||
<Compile Include="Controller\Service\UDPRelay.cs" /> | |||
<Compile Include="Controller\Service\UpdateChecker.cs" /> | |||
<Compile Include="Encryption\EncryptorBase.cs" /> | |||
@@ -189,6 +189,8 @@ | |||
<Compile Include="Settings.cs" /> | |||
<Compile Include="StringEx.cs" /> | |||
<Compile Include="Controller\System\Hotkeys\Hotkeys.cs" /> | |||
<Compile Include="Util\ProcessManagement\Job.cs" /> | |||
<Compile Include="Util\ProcessManagement\ThreadUtil.cs" /> | |||
<Compile Include="Encryption\RNG.cs" /> | |||
<Compile Include="Util\Sockets\LineReader.cs" /> | |||
<Compile Include="Util\Sockets\SocketUtil.cs" /> | |||
@@ -204,6 +206,7 @@ | |||
<DependentUpon>ConfigForm.cs</DependentUpon> | |||
</Compile> | |||
<Compile Include="Controller\Service\TCPRelay.cs" /> | |||
<Compile Include="Controller\Service\PrivoxyRunner.cs" /> | |||
<Compile Include="Program.cs" /> | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
<Compile Include="Controller\ShadowsocksController.cs" /> | |||
@@ -281,6 +284,8 @@ | |||
</None> | |||
<None Include="Data\abp.js.gz" /> | |||
<None Include="Data\libsscrypto.dll.gz" /> | |||
<None Include="Data\mgwz.dll.gz" /> | |||
<None Include="Data\privoxy.exe.gz" /> | |||
<None Include="Data\proxy.pac.txt.gz" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -297,6 +302,7 @@ | |||
<None Include="Resources\ss24.png" /> | |||
<None Include="Resources\ssw128.png" /> | |||
<Content Include="Data\cn.txt" /> | |||
<Content Include="Data\privoxy_conf.txt" /> | |||
<Content Include="Data\user-rule.txt" /> | |||
<Content Include="Data\zh_tw.txt" /> | |||
<Content Include="FodyWeavers.xml"> | |||