You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

PrivoxyRunner.cs 6.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Net.Sockets;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using NLog;
  10. using Shadowsocks.Model;
  11. using Shadowsocks.Properties;
  12. using Shadowsocks.Util;
  13. using Shadowsocks.Util.ProcessManagement;
  14. namespace Shadowsocks.Controller
  15. {
  16. class PrivoxyRunner
  17. {
  18. private static Logger logger = LogManager.GetCurrentClassLogger();
  19. private static int _uid;
  20. private static string _uniqueConfigFile;
  21. private static Job _privoxyJob;
  22. private Process _process;
  23. private int _runningPort;
  24. static PrivoxyRunner()
  25. {
  26. try
  27. {
  28. _uid = Program.WorkingDirectory.GetHashCode(); // Currently we use ss's StartupPath to identify different Privoxy instance.
  29. _uniqueConfigFile = $"privoxy_{_uid}.conf";
  30. _privoxyJob = new Job();
  31. FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe);
  32. }
  33. catch (IOException e)
  34. {
  35. logger.LogUsefulException(e);
  36. }
  37. }
  38. public int RunningPort => _runningPort;
  39. public void Start(Configuration configuration)
  40. {
  41. if (_process == null)
  42. {
  43. Process[] existingPrivoxy = Process.GetProcessesByName("ss_privoxy");
  44. foreach (Process p in existingPrivoxy.Where(IsChildProcess))
  45. {
  46. KillProcess(p);
  47. }
  48. string privoxyConfig = Resources.privoxy_conf;
  49. _runningPort = GetFreePort(configuration.isIPv6Enabled);
  50. privoxyConfig = privoxyConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString());
  51. privoxyConfig = privoxyConfig.Replace("__PRIVOXY_BIND_PORT__", _runningPort.ToString());
  52. privoxyConfig = configuration.isIPv6Enabled
  53. ? privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "[::]" : "[::1]")
  54. .Replace("__SOCKS_HOST__", "[::1]")
  55. : privoxyConfig.Replace("__PRIVOXY_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1")
  56. .Replace("__SOCKS_HOST__", "127.0.0.1");
  57. FileManager.ByteArrayToFile(Utils.GetTempPath(_uniqueConfigFile), Encoding.UTF8.GetBytes(privoxyConfig));
  58. _process = new Process
  59. {
  60. // Configure the process using the StartInfo properties.
  61. StartInfo =
  62. {
  63. FileName = "ss_privoxy.exe",
  64. Arguments = _uniqueConfigFile,
  65. WorkingDirectory = Utils.GetTempPath(),
  66. WindowStyle = ProcessWindowStyle.Hidden,
  67. UseShellExecute = true,
  68. CreateNoWindow = true
  69. }
  70. };
  71. _process.Start();
  72. /*
  73. * Add this process to job obj associated with this ss process, so that
  74. * when ss exit unexpectedly, this process will be forced killed by system.
  75. */
  76. _privoxyJob.AddProcess(_process.Handle);
  77. }
  78. }
  79. public void Stop()
  80. {
  81. if (_process != null)
  82. {
  83. KillProcess(_process);
  84. _process.Dispose();
  85. _process = null;
  86. }
  87. }
  88. private static void KillProcess(Process p)
  89. {
  90. try
  91. {
  92. p.CloseMainWindow();
  93. p.WaitForExit(100);
  94. if (!p.HasExited)
  95. {
  96. p.Kill();
  97. p.WaitForExit();
  98. }
  99. }
  100. catch (Exception e)
  101. {
  102. logger.LogUsefulException(e);
  103. }
  104. }
  105. /*
  106. * We won't like to kill other ss instances' ss_privoxy.exe.
  107. * This function will check whether the given process is created
  108. * by this process by checking the module path or command line.
  109. *
  110. * Since it's required to put ss in different dirs to run muti instances,
  111. * different instance will create their unique "privoxy_UID.conf" where
  112. * UID is hash of ss's location.
  113. */
  114. private static bool IsChildProcess(Process process)
  115. {
  116. try
  117. {
  118. /*
  119. * Under PortableMode, we could identify it by the path of ss_privoxy.exe.
  120. */
  121. var path = process.MainModule.FileName;
  122. return Utils.GetTempPath("ss_privoxy.exe").Equals(path);
  123. }
  124. catch (Exception ex)
  125. {
  126. /*
  127. * Sometimes Process.GetProcessesByName will return some processes that
  128. * are already dead, and that will cause exceptions here.
  129. * We could simply ignore those exceptions.
  130. */
  131. logger.LogUsefulException(ex);
  132. return false;
  133. }
  134. }
  135. private int GetFreePort(bool isIPv6 = false)
  136. {
  137. int defaultPort = 8123;
  138. try
  139. {
  140. // TCP stack please do me a favor
  141. TcpListener l = new TcpListener(isIPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback, 0);
  142. l.Start();
  143. var port = ((IPEndPoint)l.LocalEndpoint).Port;
  144. l.Stop();
  145. return port;
  146. }
  147. catch (Exception e)
  148. {
  149. // in case access denied
  150. logger.LogUsefulException(e);
  151. return defaultPort;
  152. }
  153. }
  154. }
  155. }