From 50f93d62c1864528ff35579685f66ea95a4266c4 Mon Sep 17 00:00:00 2001 From: Student Main Date: Thu, 3 Sep 2020 13:39:29 +0800 Subject: [PATCH] sip008 resolver, preparation for sip008 support --- .../Service/OnlineConfigResolver.cs | 91 +++ .../Controller/ShadowsocksController.cs | 556 +++++++++--------- shadowsocks-csharp/Model/Configuration.cs | 16 + shadowsocks-csharp/Model/Server.cs | 9 +- shadowsocks-csharp/shadowsocks-csharp.csproj | 1 + 5 files changed, 408 insertions(+), 265 deletions(-) create mode 100644 shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs diff --git a/shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs b/shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs new file mode 100644 index 00000000..480dc366 --- /dev/null +++ b/shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using NLog; +using Shadowsocks.Model; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Shadowsocks.Controller.Service +{ + public class OnlineConfigResolver + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public static async Task> GetOnline(string url, IWebProxy proxy = null) + { + var httpClientHandler = new HttpClientHandler(); + var httpClient = new HttpClient(httpClientHandler); + + if (proxy != null) + { + httpClientHandler.Proxy = proxy; + } + + try + { + string str = await httpClient.GetStringAsync(url); + return Get(str); + } + catch (Exception e) + { + logger.LogUsefulException(e); + return new List(); + } + } + + public static List Get(string json) + { + try + { + var t = JToken.Parse(json); + return SearchJToken(t).ToList(); + } + catch (Exception e) + { + logger.LogUsefulException(e); + return new List(); + } + } + + private static IEnumerable SearchJArray(JArray a) + { + if (a == null) return Array.Empty(); + return a.SelectMany(SearchJToken).ToList(); + } + + private static IEnumerable SearchJObject(JObject o) + { + var l = new List(); + if (o == null) return l; + try + { + return new List { o.ToObject() }; + } + catch { }; + foreach (var kv in o) + { + JToken v = kv.Value; + l.AddRange(SearchJToken(v)); + } + return l; + } + + private static IEnumerable SearchJToken(JToken t) + { + switch (t.Type) + { + default: + return Array.Empty(); + case JTokenType.Object: + return SearchJObject(t as JObject); + case JTokenType.Array: + return SearchJArray(t as JArray); + } + } + } +} diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index ffa78246..ba7230d3 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -25,7 +25,7 @@ namespace Shadowsocks.Controller // handle user actions // manipulates UI // interacts with low level logic - + #region Members definition private Thread _ramThread; private Thread _trafficThread; @@ -87,6 +87,7 @@ namespace Shadowsocks.Controller // Invoked when controller.Start(); public event EventHandler ProgramUpdated; + #endregion public ShadowsocksController() { @@ -103,6 +104,8 @@ namespace Shadowsocks.Controller }; } + #region Basic + public void Start(bool regHotkeys = true) { if (_config.updated && regHotkeys) @@ -122,6 +125,115 @@ namespace Shadowsocks.Controller } } + public void Stop() + { + if (stopped) + { + return; + } + stopped = true; + if (_listener != null) + { + _listener.Stop(); + } + StopPlugins(); + if (privoxyRunner != null) + { + privoxyRunner.Stop(); + } + if (_config.enabled) + { + SystemProxy.Update(_config, true, null); + } + Encryption.RNG.Close(); + } + + protected void Reload() + { + Encryption.RNG.Reload(); + // some logic in configuration updated the config when saving, we need to read it again + _config = Configuration.Load(); + + NLogConfig.LoadConfiguration(); + + StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); + + privoxyRunner = privoxyRunner ?? new PrivoxyRunner(); + + _pacDaemon = _pacDaemon ?? new PACDaemon(_config); + _pacDaemon.PACFileChanged += PacDaemon_PACFileChanged; + _pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged; + _pacServer = _pacServer ?? new PACServer(_pacDaemon); + _pacServer.UpdatePACURL(_config); // So PACServer works when system proxy disabled. + + GeositeUpdater.ResetEvent(); + GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted; + GeositeUpdater.Error += PacServer_PACUpdateError; + + availabilityStatistics.UpdateConfiguration(this); + _listener?.Stop(); + StopPlugins(); + + // 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(); + strategy?.ReloadServers(); + + StartPlugin(); + privoxyRunner.Start(_config); + + TCPRelay tcpRelay = new TCPRelay(this, _config); + tcpRelay.OnConnected += UpdateLatency; + tcpRelay.OnInbound += UpdateInboundCounter; + tcpRelay.OnOutbound += UpdateOutboundCounter; + tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server); + + UDPRelay udpRelay = new UDPRelay(this); + List services = new List + { + tcpRelay, + udpRelay, + _pacServer, + new PortForwarder(privoxyRunner.RunningPort) + }; + _listener = new Listener(services); + _listener.Start(_config); + } + catch (Exception e) + { + // translate Microsoft language into human language + // i.e. An attempt was made to access a socket in a way forbidden by its access permissions => Port already in use + if (e is SocketException se) + { + if (se.SocketErrorCode == SocketError.AddressAlreadyInUse) + { + e = new Exception(I18N.GetString("Port {0} already in use", _config.localPort), e); + } + else if (se.SocketErrorCode == SocketError.AccessDenied) + { + e = new Exception(I18N.GetString("Port {0} is reserved by system", _config.localPort), e); + } + } + logger.LogUsefulException(e); + ReportError(e); + } + + ConfigChanged?.Invoke(this, new EventArgs()); + UpdateSystemProxy(); + Utils.ReleaseMemory(true); + } + + protected void SaveConfig(Configuration newConfig) + { + Configuration.Save(newConfig); + Reload(); + } + protected void ReportError(Exception e) { Errored?.Invoke(this, new ErrorEventArgs(e)); @@ -144,23 +256,6 @@ namespace Shadowsocks.Controller return _config; } - public IList GetStrategies() - { - return _strategyManager.GetStrategies(); - } - - public IStrategy GetCurrentStrategy() - { - foreach (var strategy in _strategyManager.GetStrategies()) - { - if (strategy.ID == _config.strategy) - { - return strategy; - } - } - return null; - } - public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint) { IStrategy strategy = GetCurrentStrategy(); @@ -175,34 +270,6 @@ namespace Shadowsocks.Controller return GetCurrentServer(); } - public EndPoint GetPluginLocalEndPointIfConfigured(Server server) - { - var plugin = _pluginsByServer.GetOrAdd( - server, - x => Sip003Plugin.CreateIfConfigured(x, _config.showPluginOutput)); - - if (plugin == null) - { - return null; - } - - try - { - if (plugin.StartIfNeeded()) - { - logger.Info( - $"Started SIP003 plugin for {server.Identifier()} on {plugin.LocalEndPoint} - PID: {plugin.ProcessId}"); - } - } - catch (Exception ex) - { - logger.Error("Failed to start SIP003 plugin: " + ex.Message); - throw; - } - - return plugin.LocalEndPoint; - } - public void SaveServers(List servers, int localPort, bool portableMode) { _config.configs = servers; @@ -211,56 +278,24 @@ namespace Shadowsocks.Controller Configuration.Save(_config); } - public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration) + public void SelectServerIndex(int index) { - StatisticsConfiguration = configuration; - StatisticsStrategyConfiguration.Save(configuration); + _config.index = index; + _config.strategy = null; + SaveConfig(_config); } - public bool AskAddServerBySSURL(string ssURL) + public void ToggleShareOverLAN(bool enabled) { - var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo); - if (dr == DialogResult.Yes) - { - if (AddServerBySSURL(ssURL)) - { - MessageBox.Show(I18N.GetString("Successfully imported from {0}", ssURL)); - return true; - } - else - { - MessageBox.Show(I18N.GetString("Failed to import. Please check if the link is valid.")); - } - } - return false; - } - + _config.shareOverLan = enabled; + SaveConfig(_config); - public bool AddServerBySSURL(string ssURL) - { - try - { - if (ssURL.IsNullOrEmpty() || ssURL.IsWhiteSpace()) - return false; + ShareOverLANStatusChanged?.Invoke(this, new EventArgs()); + } - var servers = Server.GetServers(ssURL); - if (servers == null || servers.Count == 0) - return false; + #endregion - foreach (var server in servers) - { - _config.configs.Add(server); - } - _config.index = _config.configs.Count - 1; - SaveConfig(_config); - return true; - } - catch (Exception e) - { - logger.LogUsefulException(e); - return false; - } - } + #region OS Proxy public void ToggleEnable(bool enabled) { @@ -278,81 +313,62 @@ namespace Shadowsocks.Controller EnableGlobalChanged?.Invoke(this, new EventArgs()); } - public void ToggleShareOverLAN(bool enabled) - { - _config.shareOverLan = enabled; - SaveConfig(_config); - - ShareOverLANStatusChanged?.Invoke(this, new EventArgs()); - } - public void SaveProxy(ProxyConfig proxyConfig) { _config.proxy = proxyConfig; SaveConfig(_config); } - public void ToggleVerboseLogging(bool enabled) + private void UpdateSystemProxy() { - _config.isVerboseLogging = enabled; - SaveConfig(_config); - NLogConfig.LoadConfiguration(); // reload nlog - - VerboseLoggingStatusChanged?.Invoke(this, new EventArgs()); + SystemProxy.Update(_config, false, _pacServer); } - public void ToggleShowPluginOutput(bool enabled) + #endregion + + #region PAC + + private void PacDaemon_PACFileChanged(object sender, EventArgs e) { - _config.showPluginOutput = enabled; - SaveConfig(_config); + UpdateSystemProxy(); + } - ShowPluginOutputChanged?.Invoke(this, new EventArgs()); + private void PacServer_PACUpdateCompleted(object sender, GeositeResultEventArgs e) + { + UpdatePACFromGeositeCompleted?.Invoke(this, e); } - public void SelectServerIndex(int index) + private void PacServer_PACUpdateError(object sender, ErrorEventArgs e) { - _config.index = index; - _config.strategy = null; - SaveConfig(_config); + UpdatePACFromGeositeError?.Invoke(this, e); } - public void SelectStrategy(string strategyID) + private static readonly IEnumerable IgnoredLineBegins = new[] { '!', '[' }; + private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e) { - _config.index = -1; - _config.strategy = strategyID; - SaveConfig(_config); + GeositeUpdater.MergeAndWritePACFile(_config.geositeGroup, _config.geositeBlacklistMode); + UpdateSystemProxy(); } - public void Stop() + public void CopyPacUrl() { - if (stopped) - { - return; - } - stopped = true; - if (_listener != null) - { - _listener.Stop(); - } - StopPlugins(); - if (privoxyRunner != null) - { - privoxyRunner.Stop(); - } - if (_config.enabled) - { - SystemProxy.Update(_config, true, null); - } - Encryption.RNG.Close(); + Clipboard.SetDataObject(_pacServer.PacUrl); } - private void StopPlugins() + public void SavePACUrl(string pacUrl) { - foreach (var serverAndPlugin in _pluginsByServer) - { - serverAndPlugin.Value?.Dispose(); - } - _pluginsByServer.Clear(); + _config.pacUrl = pacUrl; + SaveConfig(_config); + + ConfigChanged?.Invoke(this, new EventArgs()); + } + + public void UseOnlinePAC(bool useOnlinePac) + { + _config.useOnlinePac = useOnlinePac; + SaveConfig(_config); + + ConfigChanged?.Invoke(this, new EventArgs()); } public void TouchPACFile() @@ -369,43 +385,78 @@ namespace Shadowsocks.Controller UserRuleFileReadyToOpen?.Invoke(this, new PathEventArgs() { Path = userRuleFilename }); } - public string GetServerURLForCurrentServer() + public void ToggleSecureLocalPac(bool enabled) { - return GetCurrentServer().GetURL(_config.generateLegacyUrl); + _config.secureLocalPac = enabled; + SaveConfig(_config); + + ConfigChanged?.Invoke(this, new EventArgs()); } - public void UpdateStatisticsConfiguration(bool enabled) + #endregion + + #region SIP002 + + public bool AskAddServerBySSURL(string ssURL) { - if (availabilityStatistics != null) + var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo); + if (dr == DialogResult.Yes) { - availabilityStatistics.UpdateConfiguration(this); - _config.availabilityStatistics = enabled; - SaveConfig(_config); + if (AddServerBySSURL(ssURL)) + { + MessageBox.Show(I18N.GetString("Successfully imported from {0}", ssURL)); + return true; + } + else + { + MessageBox.Show(I18N.GetString("Failed to import. Please check if the link is valid.")); + } } + return false; } - public void SavePACUrl(string pacUrl) + public bool AddServerBySSURL(string ssURL) { - _config.pacUrl = pacUrl; - SaveConfig(_config); + try + { + if (ssURL.IsNullOrEmpty() || ssURL.IsWhiteSpace()) + return false; - ConfigChanged?.Invoke(this, new EventArgs()); + var servers = Server.GetServers(ssURL); + if (servers == null || servers.Count == 0) + return false; + + foreach (var server in servers) + { + _config.configs.Add(server); + } + _config.index = _config.configs.Count - 1; + SaveConfig(_config); + return true; + } + catch (Exception e) + { + logger.LogUsefulException(e); + return false; + } } - public void UseOnlinePAC(bool useOnlinePac) + public string GetServerURLForCurrentServer() { - _config.useOnlinePac = useOnlinePac; - SaveConfig(_config); - - ConfigChanged?.Invoke(this, new EventArgs()); + return GetCurrentServer().GetURL(_config.generateLegacyUrl); } - public void ToggleSecureLocalPac(bool enabled) + #endregion + + #region Misc + + public void ToggleVerboseLogging(bool enabled) { - _config.secureLocalPac = enabled; + _config.isVerboseLogging = enabled; SaveConfig(_config); + NLogConfig.LoadConfiguration(); // reload nlog - ConfigChanged?.Invoke(this, new EventArgs()); + VerboseLoggingStatusChanged?.Invoke(this, new EventArgs()); } public void ToggleCheckingUpdate(bool enabled) @@ -439,6 +490,50 @@ namespace Shadowsocks.Controller ConfigChanged?.Invoke(this, new EventArgs()); } + + #endregion + + #region Statistic + + public void SelectStrategy(string strategyID) + { + _config.index = -1; + _config.strategy = strategyID; + SaveConfig(_config); + } + + public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration) + { + StatisticsConfiguration = configuration; + StatisticsStrategyConfiguration.Save(configuration); + } + + public IList GetStrategies() + { + return _strategyManager.GetStrategies(); + } + + public IStrategy GetCurrentStrategy() + { + foreach (var strategy in _strategyManager.GetStrategies()) + { + if (strategy.ID == _config.strategy) + { + return strategy; + } + } + return null; + } + + public void UpdateStatisticsConfiguration(bool enabled) + { + if (availabilityStatistics != null) + { + availabilityStatistics.UpdateConfiguration(this); + _config.availabilityStatistics = enabled; + SaveConfig(_config); + } + } public void UpdateLatency(object sender, SSTCPConnectedEventArgs args) { @@ -469,130 +564,63 @@ namespace Shadowsocks.Controller } } - protected void Reload() - { - Encryption.RNG.Reload(); - // some logic in configuration updated the config when saving, we need to read it again - _config = Configuration.Load(); - - NLogConfig.LoadConfiguration(); - - StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); - - privoxyRunner = privoxyRunner ?? new PrivoxyRunner(); - - _pacDaemon = _pacDaemon ?? new PACDaemon(_config); - _pacDaemon.PACFileChanged += PacDaemon_PACFileChanged; - _pacDaemon.UserRuleFileChanged += PacDaemon_UserRuleFileChanged; - _pacServer = _pacServer ?? new PACServer(_pacDaemon); - _pacServer.UpdatePACURL(_config); // So PACServer works when system proxy disabled. - - GeositeUpdater.ResetEvent(); - GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted; - GeositeUpdater.Error += PacServer_PACUpdateError; - - availabilityStatistics.UpdateConfiguration(this); - _listener?.Stop(); - StopPlugins(); - - // 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(); - strategy?.ReloadServers(); - - StartPlugin(); - privoxyRunner.Start(_config); - - TCPRelay tcpRelay = new TCPRelay(this, _config); - tcpRelay.OnConnected += UpdateLatency; - tcpRelay.OnInbound += UpdateInboundCounter; - tcpRelay.OnOutbound += UpdateOutboundCounter; - tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server); - - UDPRelay udpRelay = new UDPRelay(this); - List services = new List - { - tcpRelay, - udpRelay, - _pacServer, - new PortForwarder(privoxyRunner.RunningPort) - }; - _listener = new Listener(services); - _listener.Start(_config); - } - catch (Exception e) - { - // translate Microsoft language into human language - // i.e. An attempt was made to access a socket in a way forbidden by its access permissions => Port already in use - if (e is SocketException se) - { - if (se.SocketErrorCode == SocketError.AddressAlreadyInUse) - { - e = new Exception(I18N.GetString("Port {0} already in use", _config.localPort), e); - } - else if (se.SocketErrorCode == SocketError.AccessDenied) - { - e = new Exception(I18N.GetString("Port {0} is reserved by system", _config.localPort), e); - } - } - logger.LogUsefulException(e); - ReportError(e); - } - - ConfigChanged?.Invoke(this, new EventArgs()); - UpdateSystemProxy(); - Utils.ReleaseMemory(true); - } + #endregion + #region SIP003 + private void StartPlugin() { var server = _config.GetCurrentServer(); GetPluginLocalEndPointIfConfigured(server); } - protected void SaveConfig(Configuration newConfig) + private void StopPlugins() { - Configuration.Save(newConfig); - Reload(); + foreach (var serverAndPlugin in _pluginsByServer) + { + serverAndPlugin.Value?.Dispose(); + } + _pluginsByServer.Clear(); } - private void UpdateSystemProxy() + public EndPoint GetPluginLocalEndPointIfConfigured(Server server) { - SystemProxy.Update(_config, false, _pacServer); - } + var plugin = _pluginsByServer.GetOrAdd( + server, + x => Sip003Plugin.CreateIfConfigured(x, _config.showPluginOutput)); - private void PacDaemon_PACFileChanged(object sender, EventArgs e) - { - UpdateSystemProxy(); - } + if (plugin == null) + { + return null; + } - private void PacServer_PACUpdateCompleted(object sender, GeositeResultEventArgs e) - { - UpdatePACFromGeositeCompleted?.Invoke(this, e); - } + try + { + if (plugin.StartIfNeeded()) + { + logger.Info( + $"Started SIP003 plugin for {server.Identifier()} on {plugin.LocalEndPoint} - PID: {plugin.ProcessId}"); + } + } + catch (Exception ex) + { + logger.Error("Failed to start SIP003 plugin: " + ex.Message); + throw; + } - private void PacServer_PACUpdateError(object sender, ErrorEventArgs e) - { - UpdatePACFromGeositeError?.Invoke(this, e); + return plugin.LocalEndPoint; } - private static readonly IEnumerable IgnoredLineBegins = new[] { '!', '[' }; - private void PacDaemon_UserRuleFileChanged(object sender, EventArgs e) + public void ToggleShowPluginOutput(bool enabled) { - GeositeUpdater.MergeAndWritePACFile(_config.geositeGroup, _config.geositeBlacklistMode); - UpdateSystemProxy(); - } + _config.showPluginOutput = enabled; + SaveConfig(_config); - public void CopyPacUrl() - { - Clipboard.SetDataObject(_pacServer.PacUrl); + ShowPluginOutputChanged?.Invoke(this, new EventArgs()); } + #endregion + #region Memory Management private void StartReleasingMemory() diff --git a/shadowsocks-csharp/Model/Configuration.cs b/shadowsocks-csharp/Model/Configuration.cs index f712653e..93c33365 100644 --- a/shadowsocks-csharp/Model/Configuration.cs +++ b/shadowsocks-csharp/Model/Configuration.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Net; using Newtonsoft.Json; using NLog; using Shadowsocks.Controller; @@ -17,6 +18,9 @@ namespace Shadowsocks.Model public List configs; + public List onlineConfigSource; + + // when strategy is set, index is ignored public string strategy; public int index; @@ -77,6 +81,15 @@ namespace Shadowsocks.Model return GetDefaultServer(); } + public WebProxy WebProxy => enabled + ? new WebProxy( + isIPv6Enabled + ? $"[{IPAddress.IPv6Loopback}]" + : IPAddress.Loopback.ToString(), + localPort) + : null; + + public static void CheckServer(Server server) { CheckServer(server.server); @@ -113,6 +126,9 @@ namespace Shadowsocks.Model if (config.configs == null) config.configs = new List(); + if (config.onlineConfigSource == null) + config.onlineConfigSource = new List(); + if (config.configs.Count == 0) config.configs.Add(GetDefaultServer()); if (config.localPort == 0) diff --git a/shadowsocks-csharp/Model/Server.cs b/shadowsocks-csharp/Model/Server.cs index e84f27b3..daa26395 100755 --- a/shadowsocks-csharp/Model/Server.cs +++ b/shadowsocks-csharp/Model/Server.cs @@ -29,6 +29,9 @@ namespace Shadowsocks.Model public int server_port; public string password; public string method; + + + // optional fields [DefaultValue("")] @@ -43,7 +46,11 @@ namespace Shadowsocks.Model [DefaultValue("")] [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] public string remarks; - + + [DefaultValue("")] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + public string group; + public int timeout; public override int GetHashCode() diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index a5229a78..c71ab472 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -169,6 +169,7 @@ +