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.

ShadowsocksController.cs 21 kB

7 years ago
11 years ago
7 years ago
11 years ago
11 years ago
11 years ago
7 years ago
7 years ago
11 years ago
11 years ago
11 years ago
7 years ago
7 years ago
7 years ago
11 years ago
7 years ago
11 years ago
11 years ago
7 years ago
11 years ago
7 years ago
7 years ago
7 years ago
11 years ago
11 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
10 years ago
10 years ago
7 years ago
10 years ago
7 years ago
11 years ago
10 years ago
7 years ago
7 years ago
7 years ago
7 years ago
11 years ago
11 years ago
11 years ago
7 years ago
7 years ago
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Net.Sockets;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Web;
  11. using System.Windows.Forms;
  12. using NLog;
  13. using Shadowsocks.Controller.Service;
  14. using Shadowsocks.Controller.Strategy;
  15. using Shadowsocks.Model;
  16. using Shadowsocks.Util;
  17. namespace Shadowsocks.Controller
  18. {
  19. public class ShadowsocksController
  20. {
  21. private static Logger logger = LogManager.GetCurrentClassLogger();
  22. // controller:
  23. // handle user actions
  24. // manipulates UI
  25. // interacts with low level logic
  26. private Thread _trafficThread;
  27. private TCPListener _tcpListener;
  28. private UDPListener _udpListener;
  29. private PACDaemon _pacDaemon;
  30. private PACServer _pacServer;
  31. private Configuration _config;
  32. private StrategyManager _strategyManager;
  33. private PrivoxyRunner privoxyRunner;
  34. private readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer;
  35. public AvailabilityStatistics availabilityStatistics = AvailabilityStatistics.Instance;
  36. public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; }
  37. private long _inboundCounter = 0;
  38. private long _outboundCounter = 0;
  39. public long InboundCounter => Interlocked.Read(ref _inboundCounter);
  40. public long OutboundCounter => Interlocked.Read(ref _outboundCounter);
  41. public Queue<TrafficPerSecond> trafficPerSecondQueue;
  42. private bool stopped = false;
  43. public class PathEventArgs : EventArgs
  44. {
  45. public string Path;
  46. }
  47. public class UpdatedEventArgs : EventArgs
  48. {
  49. public string OldVersion;
  50. public string NewVersion;
  51. }
  52. public class TrafficPerSecond
  53. {
  54. public long inboundCounter;
  55. public long outboundCounter;
  56. public long inboundIncreasement;
  57. public long outboundIncreasement;
  58. }
  59. public event EventHandler ConfigChanged;
  60. public event EventHandler EnableStatusChanged;
  61. public event EventHandler EnableGlobalChanged;
  62. public event EventHandler ShareOverLANStatusChanged;
  63. public event EventHandler VerboseLoggingStatusChanged;
  64. public event EventHandler ShowPluginOutputChanged;
  65. public event EventHandler TrafficChanged;
  66. // when user clicked Edit PAC, and PAC file has already created
  67. public event EventHandler<PathEventArgs> PACFileReadyToOpen;
  68. public event EventHandler<PathEventArgs> UserRuleFileReadyToOpen;
  69. public event EventHandler<GeositeResultEventArgs> UpdatePACFromGeositeCompleted;
  70. public event ErrorEventHandler UpdatePACFromGeositeError;
  71. public event ErrorEventHandler Errored;
  72. // Invoked when controller.Start();
  73. public event EventHandler<UpdatedEventArgs> ProgramUpdated;
  74. public ShadowsocksController()
  75. {
  76. _config = Configuration.Load();
  77. StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
  78. _strategyManager = new StrategyManager(this);
  79. _pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>();
  80. StartTrafficStatistics(61);
  81. ProgramUpdated += (o, e) =>
  82. {
  83. logger.Info($"Updated from {e.OldVersion} to {e.NewVersion}");
  84. };
  85. }
  86. public void Start(bool regHotkeys = true)
  87. {
  88. if (_config.updated && regHotkeys)
  89. {
  90. _config.updated = false;
  91. ProgramUpdated.Invoke(this, new UpdatedEventArgs()
  92. {
  93. OldVersion = _config.version,
  94. NewVersion = UpdateChecker.Version,
  95. });
  96. Configuration.Save(_config);
  97. }
  98. Reload();
  99. if (regHotkeys)
  100. {
  101. HotkeyReg.RegAllHotkeys();
  102. }
  103. }
  104. protected void ReportError(Exception e)
  105. {
  106. Errored?.Invoke(this, new ErrorEventArgs(e));
  107. }
  108. public Server GetCurrentServer()
  109. {
  110. return _config.GetCurrentServer();
  111. }
  112. // always return copy
  113. public Configuration GetConfigurationCopy()
  114. {
  115. return Configuration.Load();
  116. }
  117. // always return current instance
  118. public Configuration GetCurrentConfiguration()
  119. {
  120. return _config;
  121. }
  122. public IList<IStrategy> GetStrategies()
  123. {
  124. return _strategyManager.GetStrategies();
  125. }
  126. public IStrategy GetCurrentStrategy()
  127. {
  128. foreach (var strategy in _strategyManager.GetStrategies())
  129. {
  130. if (strategy.ID == _config.strategy)
  131. {
  132. return strategy;
  133. }
  134. }
  135. return null;
  136. }
  137. public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint)
  138. {
  139. IStrategy strategy = GetCurrentStrategy();
  140. if (strategy != null)
  141. {
  142. return strategy.GetAServer(type, localIPEndPoint, destEndPoint);
  143. }
  144. if (_config.index < 0)
  145. {
  146. _config.index = 0;
  147. }
  148. return GetCurrentServer();
  149. }
  150. public EndPoint GetPluginLocalEndPointIfConfigured(Server server)
  151. {
  152. var plugin = _pluginsByServer.GetOrAdd(
  153. server,
  154. x => Sip003Plugin.CreateIfConfigured(x, _config.showPluginOutput));
  155. if (plugin == null)
  156. {
  157. return null;
  158. }
  159. try
  160. {
  161. if (plugin.StartIfNeeded())
  162. {
  163. logger.Info(
  164. $"Started SIP003 plugin for {server.Identifier()} on {plugin.LocalEndPoint} - PID: {plugin.ProcessId}");
  165. }
  166. }
  167. catch (Exception ex)
  168. {
  169. logger.Error("Failed to start SIP003 plugin: " + ex.Message);
  170. throw;
  171. }
  172. return plugin.LocalEndPoint;
  173. }
  174. public void SaveServers(List<Server> servers, int localPort, bool portableMode)
  175. {
  176. _config.configs = servers;
  177. _config.localPort = localPort;
  178. _config.portableMode = portableMode;
  179. Configuration.Save(_config);
  180. }
  181. public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration)
  182. {
  183. StatisticsConfiguration = configuration;
  184. StatisticsStrategyConfiguration.Save(configuration);
  185. }
  186. public bool AskAddServerBySSURL(string ssURL)
  187. {
  188. var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo);
  189. if (dr == DialogResult.Yes)
  190. {
  191. if (AddServerBySSURL(ssURL))
  192. {
  193. MessageBox.Show(I18N.GetString("Successfully imported from {0}", ssURL));
  194. return true;
  195. }
  196. else
  197. {
  198. MessageBox.Show(I18N.GetString("Failed to import. Please check if the link is valid."));
  199. }
  200. }
  201. return false;
  202. }
  203. public bool AddServerBySSURL(string ssURL)
  204. {
  205. try
  206. {
  207. if (ssURL.IsNullOrEmpty() || ssURL.IsWhiteSpace())
  208. return false;
  209. var servers = Server.GetServers(ssURL);
  210. if (servers == null || servers.Count == 0)
  211. return false;
  212. foreach (var server in servers)
  213. {
  214. _config.configs.Add(server);
  215. }
  216. _config.index = _config.configs.Count - 1;
  217. SaveConfig(_config);
  218. return true;
  219. }
  220. catch (Exception e)
  221. {
  222. logger.LogUsefulException(e);
  223. return false;
  224. }
  225. }
  226. public void ToggleEnable(bool enabled)
  227. {
  228. _config.enabled = enabled;
  229. SaveConfig(_config);
  230. EnableStatusChanged?.Invoke(this, new EventArgs());
  231. }
  232. public void ToggleGlobal(bool global)
  233. {
  234. _config.global = global;
  235. SaveConfig(_config);
  236. EnableGlobalChanged?.Invoke(this, new EventArgs());
  237. }
  238. public void ToggleShareOverLAN(bool enabled)
  239. {
  240. _config.shareOverLan = enabled;
  241. SaveConfig(_config);
  242. ShareOverLANStatusChanged?.Invoke(this, new EventArgs());
  243. }
  244. public void SaveProxy(ProxyConfig proxyConfig)
  245. {
  246. _config.proxy = proxyConfig;
  247. SaveConfig(_config);
  248. }
  249. public void ToggleVerboseLogging(bool enabled)
  250. {
  251. _config.isVerboseLogging = enabled;
  252. SaveConfig(_config);
  253. NLogConfig.LoadConfiguration(); // reload nlog
  254. VerboseLoggingStatusChanged?.Invoke(this, new EventArgs());
  255. }
  256. public void ToggleShowPluginOutput(bool enabled)
  257. {
  258. _config.showPluginOutput = enabled;
  259. SaveConfig(_config);
  260. ShowPluginOutputChanged?.Invoke(this, new EventArgs());
  261. }
  262. public void SelectServerIndex(int index)
  263. {
  264. _config.index = index;
  265. _config.strategy = null;
  266. SaveConfig(_config);
  267. }
  268. public void SelectStrategy(string strategyID)
  269. {
  270. _config.index = -1;
  271. _config.strategy = strategyID;
  272. SaveConfig(_config);
  273. }
  274. public void Stop()
  275. {
  276. if (stopped)
  277. {
  278. return;
  279. }
  280. stopped = true;
  281. _tcpListener?.Stop();
  282. _udpListener?.Stop();
  283. StopPlugins();
  284. if (privoxyRunner != null)
  285. {
  286. privoxyRunner.Stop();
  287. }
  288. if (_config.enabled)
  289. {
  290. SystemProxy.Update(_config, true, null);
  291. }
  292. }
  293. private void StopPlugins()
  294. {
  295. foreach (var serverAndPlugin in _pluginsByServer)
  296. {
  297. serverAndPlugin.Value?.Dispose();
  298. }
  299. _pluginsByServer.Clear();
  300. }
  301. public void TouchPACFile()
  302. {
  303. string pacFilename = _pacDaemon.TouchPACFile();
  304. PACFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = pacFilename });
  305. }
  306. public void TouchUserRuleFile()
  307. {
  308. string userRuleFilename = _pacDaemon.TouchUserRuleFile();
  309. UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename });
  310. }
  311. public string GetServerURLForCurrentServer()
  312. {
  313. return GetCurrentServer().GetURL(_config.generateLegacyUrl);
  314. }
  315. public void UpdateStatisticsConfiguration(bool enabled)
  316. {
  317. if (availabilityStatistics != null)
  318. {
  319. availabilityStatistics.UpdateConfiguration(this);
  320. _config.availabilityStatistics = enabled;
  321. SaveConfig(_config);
  322. }
  323. }
  324. public void SavePACUrl(string pacUrl)
  325. {
  326. _config.pacUrl = pacUrl;
  327. SaveConfig(_config);
  328. ConfigChanged?.Invoke(this, new EventArgs());
  329. }
  330. public void UseOnlinePAC(bool useOnlinePac)
  331. {
  332. _config.useOnlinePac = useOnlinePac;
  333. SaveConfig(_config);
  334. ConfigChanged?.Invoke(this, new EventArgs());
  335. }
  336. public void ToggleSecureLocalPac(bool enabled)
  337. {
  338. _config.secureLocalPac = enabled;
  339. SaveConfig(_config);
  340. ConfigChanged?.Invoke(this, new EventArgs());
  341. }
  342. public void ToggleCheckingUpdate(bool enabled)
  343. {
  344. _config.autoCheckUpdate = enabled;
  345. Configuration.Save(_config);
  346. ConfigChanged?.Invoke(this, new EventArgs());
  347. }
  348. public void ToggleCheckingPreRelease(bool enabled)
  349. {
  350. _config.checkPreRelease = enabled;
  351. Configuration.Save(_config);
  352. ConfigChanged?.Invoke(this, new EventArgs());
  353. }
  354. public void SaveLogViewerConfig(LogViewerConfig newConfig)
  355. {
  356. _config.logViewer = newConfig;
  357. newConfig.SaveSize();
  358. Configuration.Save(_config);
  359. ConfigChanged?.Invoke(this, new EventArgs());
  360. }
  361. public void SaveHotkeyConfig(HotkeyConfig newConfig)
  362. {
  363. _config.hotkey = newConfig;
  364. SaveConfig(_config);
  365. ConfigChanged?.Invoke(this, new EventArgs());
  366. }
  367. public void UpdateLatency(object sender, SSTCPConnectedEventArgs args)
  368. {
  369. GetCurrentStrategy()?.UpdateLatency(args.server, args.latency);
  370. if (_config.availabilityStatistics)
  371. {
  372. availabilityStatistics.UpdateLatency(args.server, (int)args.latency.TotalMilliseconds);
  373. }
  374. }
  375. public void UpdateInboundCounter(object sender, SSTransmitEventArgs args)
  376. {
  377. GetCurrentStrategy()?.UpdateLastRead(args.server);
  378. Interlocked.Add(ref _inboundCounter, args.length);
  379. if (_config.availabilityStatistics)
  380. {
  381. availabilityStatistics.UpdateInboundCounter(args.server, args.length);
  382. }
  383. }
  384. public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args)
  385. {
  386. GetCurrentStrategy()?.UpdateLastWrite(args.server);
  387. Interlocked.Add(ref _outboundCounter, args.length);
  388. if (_config.availabilityStatistics)
  389. {
  390. availabilityStatistics.UpdateOutboundCounter(args.server, args.length);
  391. }
  392. }
  393. protected void Reload()
  394. {
  395. Encryption.RNG.Reload();
  396. // some logic in configuration updated the config when saving, we need to read it again
  397. _config = Configuration.Load();
  398. NLogConfig.LoadConfiguration();
  399. StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
  400. privoxyRunner = privoxyRunner ?? new PrivoxyRunner();
  401. _pacDaemon = _pacDaemon ?? new PACDaemon(_config);
  402. _pacDaemon.PACFileChanged += PacDaemon_PACFileChanged;
  403. _pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged;
  404. _pacServer = _pacServer ?? new PACServer(_pacDaemon);
  405. _pacServer.UpdatePACURL(_config); // So PACServer works when system proxy disabled.
  406. GeositeUpdater.ResetEvent();
  407. GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted;
  408. GeositeUpdater.Error += PacServer_PACUpdateError;
  409. availabilityStatistics.UpdateConfiguration(this);
  410. _tcpListener?.Stop();
  411. _udpListener?.Stop();
  412. StopPlugins();
  413. // don't put PrivoxyRunner.Start() before pacServer.Stop()
  414. // or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1
  415. // though UseShellExecute is set to true now
  416. // http://stackoverflow.com/questions/10235093/socket-doesnt-close-after-application-exits-if-a-launched-process-is-open
  417. privoxyRunner.Stop();
  418. try
  419. {
  420. var strategy = GetCurrentStrategy();
  421. strategy?.ReloadServers();
  422. StartPlugin();
  423. privoxyRunner.Start(_config);
  424. TCPRelay tcpRelay = new TCPRelay(this, _config);
  425. tcpRelay.OnConnected += UpdateLatency;
  426. tcpRelay.OnInbound += UpdateInboundCounter;
  427. tcpRelay.OnOutbound += UpdateOutboundCounter;
  428. tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server);
  429. UDPRelay udpRelay = new UDPRelay(this);
  430. _tcpListener = new TCPListener(_config, new List<IStreamService>
  431. {
  432. tcpRelay,
  433. _pacServer,
  434. new PortForwarder(privoxyRunner.RunningPort),
  435. });
  436. _tcpListener.Start();
  437. _udpListener = new UDPListener(_config, new List<IDatagramService>
  438. {
  439. udpRelay,
  440. });
  441. _udpListener.Start();
  442. }
  443. catch (Exception e)
  444. {
  445. // translate Microsoft language into human language
  446. // i.e. An attempt was made to access a socket in a way forbidden by its access permissions => Port already in use
  447. if (e is SocketException se)
  448. {
  449. if (se.SocketErrorCode == SocketError.AddressAlreadyInUse)
  450. {
  451. e = new Exception(I18N.GetString("Port {0} already in use", _config.localPort), e);
  452. }
  453. else if (se.SocketErrorCode == SocketError.AccessDenied)
  454. {
  455. e = new Exception(I18N.GetString("Port {0} is reserved by system", _config.localPort), e);
  456. }
  457. }
  458. logger.LogUsefulException(e);
  459. ReportError(e);
  460. }
  461. ConfigChanged?.Invoke(this, new EventArgs());
  462. UpdateSystemProxy();
  463. }
  464. private void StartPlugin()
  465. {
  466. var server = _config.GetCurrentServer();
  467. GetPluginLocalEndPointIfConfigured(server);
  468. }
  469. protected void SaveConfig(Configuration newConfig)
  470. {
  471. Configuration.Save(newConfig);
  472. Reload();
  473. }
  474. private void UpdateSystemProxy()
  475. {
  476. SystemProxy.Update(_config, false, _pacServer);
  477. }
  478. private void PacDaemon_PACFileChanged(object sender, EventArgs e)
  479. {
  480. UpdateSystemProxy();
  481. }
  482. private void PacServer_PACUpdateCompleted(object sender, GeositeResultEventArgs e)
  483. {
  484. UpdatePACFromGeositeCompleted?.Invoke(this, e);
  485. }
  486. private void PacServer_PACUpdateError(object sender, ErrorEventArgs e)
  487. {
  488. UpdatePACFromGeositeError?.Invoke(this, e);
  489. }
  490. private static readonly IEnumerable<char> IgnoredLineBegins = new[] { '!', '[' };
  491. private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e)
  492. {
  493. GeositeUpdater.MergeAndWritePACFile(_config.geositeGroup, _config.geositeBlacklistMode);
  494. UpdateSystemProxy();
  495. }
  496. public void CopyPacUrl()
  497. {
  498. Clipboard.SetDataObject(_pacServer.PacUrl);
  499. }
  500. #region Traffic Statistics
  501. private void StartTrafficStatistics(int queueMaxSize)
  502. {
  503. trafficPerSecondQueue = new Queue<TrafficPerSecond>();
  504. for (int i = 0; i < queueMaxSize; i++)
  505. {
  506. trafficPerSecondQueue.Enqueue(new TrafficPerSecond());
  507. }
  508. _trafficThread = new Thread(new ThreadStart(() => TrafficStatistics(queueMaxSize)))
  509. {
  510. IsBackground = true
  511. };
  512. _trafficThread.Start();
  513. }
  514. private void TrafficStatistics(int queueMaxSize)
  515. {
  516. TrafficPerSecond previous, current;
  517. while (true)
  518. {
  519. previous = trafficPerSecondQueue.Last();
  520. current = new TrafficPerSecond
  521. {
  522. inboundCounter = InboundCounter,
  523. outboundCounter = OutboundCounter
  524. };
  525. current.inboundIncreasement = current.inboundCounter - previous.inboundCounter;
  526. current.outboundIncreasement = current.outboundCounter - previous.outboundCounter;
  527. trafficPerSecondQueue.Enqueue(current);
  528. if (trafficPerSecondQueue.Count > queueMaxSize)
  529. trafficPerSecondQueue.Dequeue();
  530. TrafficChanged?.Invoke(this, new EventArgs());
  531. Thread.Sleep(1000);
  532. }
  533. }
  534. #endregion
  535. }
  536. }