Browse Source

sip008 resolver, preparation for sip008 support

tags/4.2.1.0
Student Main 4 years ago
parent
commit
50f93d62c1
No known key found for this signature in database GPG Key ID: AA78519C208C8742
5 changed files with 408 additions and 265 deletions
  1. +91
    -0
      shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs
  2. +292
    -264
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  3. +16
    -0
      shadowsocks-csharp/Model/Configuration.cs
  4. +8
    -1
      shadowsocks-csharp/Model/Server.cs
  5. +1
    -0
      shadowsocks-csharp/shadowsocks-csharp.csproj

+ 91
- 0
shadowsocks-csharp/Controller/Service/OnlineConfigResolver.cs View File

@@ -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<List<Server>> 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<Server>();
}
}

public static List<Server> Get(string json)
{
try
{
var t = JToken.Parse(json);
return SearchJToken(t).ToList();
}
catch (Exception e)
{
logger.LogUsefulException(e);
return new List<Server>();
}
}

private static IEnumerable<Server> SearchJArray(JArray a)
{
if (a == null) return Array.Empty<Server>();
return a.SelectMany(SearchJToken).ToList();
}

private static IEnumerable<Server> SearchJObject(JObject o)
{
var l = new List<Server>();
if (o == null) return l;
try
{
return new List<Server> { o.ToObject<Server>() };
}
catch { };
foreach (var kv in o)
{
JToken v = kv.Value;
l.AddRange(SearchJToken(v));
}
return l;
}

private static IEnumerable<Server> SearchJToken(JToken t)
{
switch (t.Type)
{
default:
return Array.Empty<Server>();
case JTokenType.Object:
return SearchJObject(t as JObject);
case JTokenType.Array:
return SearchJArray(t as JArray);
}
}
}
}

+ 292
- 264
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -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<UpdatedEventArgs> 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<Listener.IService> services = new List<Listener.IService>
{
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<IStrategy> 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<Server> 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<char> 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<IStrategy> 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<Listener.IService> services = new List<Listener.IService>
{
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<char> 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()


+ 16
- 0
shadowsocks-csharp/Model/Configuration.cs View File

@@ -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<Server> configs;
public List<string> 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<Server>();
if (config.onlineConfigSource == null)
config.onlineConfigSource = new List<string>();
if (config.configs.Count == 0)
config.configs.Add(GetDefaultServer());
if (config.localPort == 0)


+ 8
- 1
shadowsocks-csharp/Model/Server.cs View File

@@ -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()


+ 1
- 0
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -169,6 +169,7 @@
<Compile Include="Controller\HotkeyReg.cs" />
<Compile Include="Controller\LoggerExtension.cs" />
<Compile Include="Controller\Service\GeositeUpdater.cs" />
<Compile Include="Controller\Service\OnlineConfigResolver.cs" />
<Compile Include="Controller\Service\PACDaemon.cs" />
<Compile Include="Controller\Service\IPCService.cs" />
<Compile Include="Controller\System\ProtocolHandler.cs" />


Loading…
Cancel
Save