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.

PACServer.cs 11 kB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. using Shadowsocks.Model;
  2. using Shadowsocks.Properties;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.IO.Compression;
  8. using System.Net;
  9. using System.Net.Sockets;
  10. using System.Text;
  11. using System.Text.RegularExpressions;
  12. namespace Shadowsocks.Controller
  13. {
  14. class PACServer
  15. {
  16. private static int PORT = 8093;
  17. private static string PAC_FILE = "pac.txt";
  18. private static Configuration config;
  19. Socket _listener;
  20. FileSystemWatcher watcher;
  21. GfwListUpdater gfwlistUpdater;
  22. public event EventHandler PACFileChanged;
  23. public void Start(Configuration configuration)
  24. {
  25. try
  26. {
  27. config = configuration;
  28. // Create a TCP/IP socket.
  29. _listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  30. _listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
  31. IPEndPoint localEndPoint = null;
  32. if (configuration.shareOverLan)
  33. {
  34. localEndPoint = new IPEndPoint(IPAddress.Any, PORT);
  35. }
  36. else
  37. {
  38. localEndPoint = new IPEndPoint(IPAddress.Loopback, PORT);
  39. }
  40. // Bind the socket to the local endpoint and listen for incoming connections.
  41. _listener.Bind(localEndPoint);
  42. _listener.Listen(100);
  43. _listener.BeginAccept(
  44. new AsyncCallback(AcceptCallback),
  45. _listener);
  46. WatchPacFile();
  47. StartGfwListUpdater();
  48. }
  49. catch (SocketException)
  50. {
  51. _listener.Close();
  52. throw;
  53. }
  54. }
  55. public void Stop()
  56. {
  57. if (gfwlistUpdater != null)
  58. {
  59. gfwlistUpdater.Stop();
  60. gfwlistUpdater = null;
  61. }
  62. if (_listener != null)
  63. {
  64. _listener.Close();
  65. _listener = null;
  66. }
  67. }
  68. public string TouchPACFile()
  69. {
  70. if (File.Exists(PAC_FILE))
  71. {
  72. return PAC_FILE;
  73. }
  74. else
  75. {
  76. FileManager.UncompressFile(PAC_FILE, Resources.proxy_pac_txt);
  77. return PAC_FILE;
  78. }
  79. }
  80. // we don't even use it
  81. static byte[] requestBuf = new byte[2048];
  82. public void AcceptCallback(IAsyncResult ar)
  83. {
  84. Socket listener = (Socket)ar.AsyncState;
  85. try
  86. {
  87. Socket conn = listener.EndAccept(ar);
  88. object[] state = new object[] {
  89. conn,
  90. requestBuf
  91. };
  92. conn.BeginReceive(requestBuf, 0, requestBuf.Length, 0,
  93. new AsyncCallback(ReceiveCallback), state);
  94. }
  95. catch (ObjectDisposedException)
  96. {
  97. }
  98. catch (Exception e)
  99. {
  100. Console.WriteLine(e);
  101. }
  102. finally
  103. {
  104. try
  105. {
  106. listener.BeginAccept(
  107. new AsyncCallback(AcceptCallback),
  108. listener);
  109. }
  110. catch (ObjectDisposedException)
  111. {
  112. // do nothing
  113. }
  114. catch (Exception e)
  115. {
  116. Logging.LogUsefulException(e);
  117. }
  118. }
  119. }
  120. private string GetPACContent()
  121. {
  122. if (File.Exists(PAC_FILE))
  123. {
  124. return File.ReadAllText(PAC_FILE, Encoding.UTF8);
  125. }
  126. else
  127. {
  128. byte[] pacGZ = Resources.proxy_pac_txt;
  129. byte[] buffer = new byte[1024]; // builtin pac gzip size: maximum 100K
  130. MemoryStream sb = new MemoryStream();
  131. int n;
  132. using (GZipStream input = new GZipStream(new MemoryStream(pacGZ),
  133. CompressionMode.Decompress, false))
  134. {
  135. while((n = input.Read(buffer, 0, buffer.Length)) > 0)
  136. {
  137. sb.Write(buffer, 0, n);
  138. }
  139. return System.Text.Encoding.UTF8.GetString(sb.ToArray());
  140. }
  141. }
  142. }
  143. private void ReceiveCallback(IAsyncResult ar)
  144. {
  145. object[] state = (object[])ar.AsyncState;
  146. Socket conn = (Socket)state[0];
  147. byte[] requestBuf = (byte[])state[1];
  148. try
  149. {
  150. int bytesRead = conn.EndReceive(ar);
  151. string pac = GetPACContent();
  152. IPEndPoint localEndPoint = (IPEndPoint)conn.LocalEndPoint;
  153. string proxy = GetPACAddress(requestBuf, localEndPoint);
  154. pac = pac.Replace("__PROXY__", proxy);
  155. if (bytesRead > 0)
  156. {
  157. string text = String.Format(@"HTTP/1.1 200 OK
  158. Server: Shadowsocks
  159. Content-Type: application/x-ns-proxy-autoconfig
  160. Content-Length: {0}
  161. Connection: Close
  162. ", System.Text.Encoding.UTF8.GetBytes(pac).Length) + pac;
  163. byte[] response = System.Text.Encoding.UTF8.GetBytes(text);
  164. conn.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), conn);
  165. Util.Util.ReleaseMemory();
  166. }
  167. else
  168. {
  169. conn.Close();
  170. }
  171. }
  172. catch (Exception e)
  173. {
  174. Console.WriteLine(e);
  175. conn.Close();
  176. }
  177. }
  178. private void SendCallback(IAsyncResult ar)
  179. {
  180. Socket conn = (Socket)ar.AsyncState;
  181. try
  182. {
  183. conn.Shutdown(SocketShutdown.Send);
  184. }
  185. catch
  186. { }
  187. }
  188. private void WatchPacFile()
  189. {
  190. if (watcher != null)
  191. {
  192. watcher.Dispose();
  193. }
  194. watcher = new FileSystemWatcher(Directory.GetCurrentDirectory());
  195. watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
  196. watcher.Filter = PAC_FILE;
  197. watcher.Changed += Watcher_Changed;
  198. watcher.Created += Watcher_Changed;
  199. watcher.Deleted += Watcher_Changed;
  200. watcher.Renamed += Watcher_Changed;
  201. watcher.EnableRaisingEvents = true;
  202. }
  203. private void Watcher_Changed(object sender, FileSystemEventArgs e)
  204. {
  205. if (PACFileChanged != null)
  206. {
  207. PACFileChanged(this, new EventArgs());
  208. }
  209. }
  210. private string GetPACAddress(byte[] requestBuf, IPEndPoint localEndPoint)
  211. {
  212. string proxy = "PROXY " + localEndPoint.Address + ":8123;";
  213. //try
  214. //{
  215. // string requestString = Encoding.UTF8.GetString(requestBuf);
  216. // if (requestString.IndexOf("AppleWebKit") >= 0)
  217. // {
  218. // string address = "" + localEndPoint.Address + ":" + config.GetCurrentServer().local_port;
  219. // proxy = "SOCKS5 " + address + "; SOCKS " + address + ";";
  220. // }
  221. //}
  222. //catch (Exception e)
  223. //{
  224. // Console.WriteLine(e);
  225. //}
  226. return proxy;
  227. }
  228. private void StartGfwListUpdater()
  229. {
  230. if (gfwlistUpdater != null)
  231. {
  232. gfwlistUpdater.Stop();
  233. gfwlistUpdater = null;
  234. }
  235. gfwlistUpdater = new GfwListUpdater();
  236. gfwlistUpdater.GfwListChanged += gfwlistUpdater_GfwListChanged;
  237. IPEndPoint localEndPoint = (IPEndPoint)_listener.LocalEndPoint;
  238. gfwlistUpdater.proxy = new WebProxy(localEndPoint.Address.ToString(), 8123);
  239. gfwlistUpdater.useSystemProxy = false;
  240. /* Delay 30 seconds, wait proxy start up. */
  241. gfwlistUpdater.ScheduleUpdateTime(30);
  242. gfwlistUpdater.Start();
  243. }
  244. private void gfwlistUpdater_GfwListChanged(object sender, GfwListUpdater.GfwListChangedArgs e)
  245. {
  246. if (e.GfwList == null || e.GfwList.Length == 0) return;
  247. string pacfile = TouchPACFile();
  248. string pacContent = File.ReadAllText(pacfile);
  249. string oldDomains;
  250. if (ClearPacContent(ref pacContent, out oldDomains))
  251. {
  252. StringBuilder sb = new StringBuilder();
  253. sb.AppendLine("{");
  254. for (int i = 0; i < e.GfwList.Length; i++)
  255. {
  256. if (i == e.GfwList.Length - 1)
  257. sb.AppendFormat("\t\"{0}\": {1}\r\n", e.GfwList[i], 1);
  258. else
  259. sb.AppendFormat("\t\"{0}\": {1},\r\n", e.GfwList[i], 1);
  260. }
  261. sb.Append("}");
  262. string newDomains = sb.ToString();
  263. if (!string.Equals(oldDomains, newDomains))
  264. {
  265. pacContent = pacContent.Replace("__LAST_MODIFIED__", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  266. pacContent = pacContent.Replace("__DOMAINS__", newDomains);
  267. File.WriteAllText(pacfile, pacContent);
  268. Console.WriteLine("gfwlist updated - " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  269. }
  270. }
  271. else
  272. {
  273. Console.WriteLine("Broken pac file.");
  274. }
  275. }
  276. private bool ClearPacContent(ref string pacContent, out string oldDomains)
  277. {
  278. Regex regex = new Regex("(/\\*.*?\\*/\\s*)?var\\s+domains\\s*=\\s*(\\{(\\s*\"[^\"]*\"\\s*:\\s*\\d+\\s*,)*\\s*(\\s*\"[^\"]*\"\\s*:\\s*\\d+\\s*)\\})", RegexOptions.Singleline);
  279. Match m = regex.Match(pacContent);
  280. if (m.Success)
  281. {
  282. oldDomains = m.Result("$2");
  283. pacContent = regex.Replace(pacContent, "/* Last Modified: __LAST_MODIFIED__ */\r\nvar domains = __DOMAINS__");
  284. return true;
  285. }
  286. oldDomains = null;
  287. return false;
  288. }
  289. }
  290. }