| @@ -0,0 +1,4 @@ | |||
| * text=auto | |||
| # geosite database | |||
| *.dat binary | |||
| @@ -1,110 +1,110 @@ | |||
| using System; | |||
| using System.Collections.Specialized; | |||
| using System.Diagnostics; | |||
| using System.IO; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using System.Reflection; | |||
| using Shadowsocks.Model; | |||
| using Shadowsocks.Util.ProcessManagement; | |||
| namespace Shadowsocks.Controller.Service | |||
| { | |||
| // https://github.com/shadowsocks/shadowsocks-org/wiki/Plugin | |||
| public sealed class Sip003Plugin : IDisposable | |||
| { | |||
| public IPEndPoint LocalEndPoint { get; private set; } | |||
| public int ProcessId => _started ? _pluginProcess.Id : 0; | |||
| private readonly object _startProcessLock = new object(); | |||
| private readonly Job _pluginJob; | |||
| private readonly Process _pluginProcess; | |||
| private bool _started; | |||
| private bool _disposed; | |||
| public static Sip003Plugin CreateIfConfigured(Server server, bool showPluginOutput) | |||
| { | |||
| if (server == null) | |||
| { | |||
| throw new ArgumentNullException(nameof(server)); | |||
| } | |||
| if (string.IsNullOrWhiteSpace(server.plugin)) | |||
| { | |||
| return null; | |||
| } | |||
| return new Sip003Plugin( | |||
| server.plugin, | |||
| server.plugin_opts, | |||
| server.plugin_args, | |||
| server.server, | |||
| server.server_port, | |||
| showPluginOutput); | |||
| } | |||
| private Sip003Plugin(string plugin, string pluginOpts, string pluginArgs, string serverAddress, int serverPort, bool showPluginOutput) | |||
| { | |||
| if (plugin == null) throw new ArgumentNullException(nameof(plugin)); | |||
| if (string.IsNullOrWhiteSpace(serverAddress)) | |||
| { | |||
| throw new ArgumentException("Value cannot be null or whitespace.", nameof(serverAddress)); | |||
| } | |||
| if (serverPort <= 0 || serverPort > 65535) | |||
| { | |||
| throw new ArgumentOutOfRangeException("serverPort"); | |||
| } | |||
| var appPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase).LocalPath); | |||
| _pluginProcess = new Process | |||
| { | |||
| StartInfo = new ProcessStartInfo | |||
| { | |||
| FileName = plugin, | |||
| Arguments = pluginArgs, | |||
| UseShellExecute = false, | |||
| CreateNoWindow = !showPluginOutput, | |||
| ErrorDialog = false, | |||
| WindowStyle = ProcessWindowStyle.Hidden, | |||
| WorkingDirectory = appPath ?? Environment.CurrentDirectory, | |||
| Environment = | |||
| { | |||
| ["SS_REMOTE_HOST"] = serverAddress, | |||
| ["SS_REMOTE_PORT"] = serverPort.ToString(), | |||
| ["SS_PLUGIN_OPTIONS"] = pluginOpts | |||
| } | |||
| } | |||
| }; | |||
| _pluginJob = new Job(); | |||
| } | |||
| public bool StartIfNeeded() | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| lock (_startProcessLock) | |||
| { | |||
| if (_started && !_pluginProcess.HasExited) | |||
| { | |||
| return false; | |||
| } | |||
| var localPort = GetNextFreeTcpPort(); | |||
| LocalEndPoint = new IPEndPoint(IPAddress.Loopback, localPort); | |||
| _pluginProcess.StartInfo.Environment["SS_LOCAL_HOST"] = LocalEndPoint.Address.ToString(); | |||
| _pluginProcess.StartInfo.Environment["SS_LOCAL_PORT"] = LocalEndPoint.Port.ToString(); | |||
| _pluginProcess.StartInfo.Arguments = ExpandEnvironmentVariables(_pluginProcess.StartInfo.Arguments, _pluginProcess.StartInfo.EnvironmentVariables); | |||
| using System; | |||
| using System.Collections.Specialized; | |||
| using System.Diagnostics; | |||
| using System.IO; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using System.Reflection; | |||
| using Shadowsocks.Model; | |||
| using Shadowsocks.Util.ProcessManagement; | |||
| namespace Shadowsocks.Controller.Service | |||
| { | |||
| // https://github.com/shadowsocks/shadowsocks-org/wiki/Plugin | |||
| public sealed class Sip003Plugin : IDisposable | |||
| { | |||
| public IPEndPoint LocalEndPoint { get; private set; } | |||
| public int ProcessId => _started ? _pluginProcess.Id : 0; | |||
| private readonly object _startProcessLock = new object(); | |||
| private readonly Job _pluginJob; | |||
| private readonly Process _pluginProcess; | |||
| private bool _started; | |||
| private bool _disposed; | |||
| public static Sip003Plugin CreateIfConfigured(Server server, bool showPluginOutput) | |||
| { | |||
| if (server == null) | |||
| { | |||
| throw new ArgumentNullException(nameof(server)); | |||
| } | |||
| if (string.IsNullOrWhiteSpace(server.plugin)) | |||
| { | |||
| return null; | |||
| } | |||
| return new Sip003Plugin( | |||
| server.plugin, | |||
| server.plugin_opts, | |||
| server.plugin_args, | |||
| server.server, | |||
| server.server_port, | |||
| showPluginOutput); | |||
| } | |||
| private Sip003Plugin(string plugin, string pluginOpts, string pluginArgs, string serverAddress, int serverPort, bool showPluginOutput) | |||
| { | |||
| if (plugin == null) throw new ArgumentNullException(nameof(plugin)); | |||
| if (string.IsNullOrWhiteSpace(serverAddress)) | |||
| { | |||
| throw new ArgumentException("Value cannot be null or whitespace.", nameof(serverAddress)); | |||
| } | |||
| if (serverPort <= 0 || serverPort > 65535) | |||
| { | |||
| throw new ArgumentOutOfRangeException("serverPort"); | |||
| } | |||
| var appPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase).LocalPath); | |||
| _pluginProcess = new Process | |||
| { | |||
| StartInfo = new ProcessStartInfo | |||
| { | |||
| FileName = plugin, | |||
| Arguments = pluginArgs, | |||
| UseShellExecute = false, | |||
| CreateNoWindow = !showPluginOutput, | |||
| ErrorDialog = false, | |||
| WindowStyle = ProcessWindowStyle.Hidden, | |||
| WorkingDirectory = appPath ?? Environment.CurrentDirectory, | |||
| Environment = | |||
| { | |||
| ["SS_REMOTE_HOST"] = serverAddress, | |||
| ["SS_REMOTE_PORT"] = serverPort.ToString(), | |||
| ["SS_PLUGIN_OPTIONS"] = pluginOpts | |||
| } | |||
| } | |||
| }; | |||
| _pluginJob = new Job(); | |||
| } | |||
| public bool StartIfNeeded() | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| lock (_startProcessLock) | |||
| { | |||
| if (_started && !_pluginProcess.HasExited) | |||
| { | |||
| return false; | |||
| } | |||
| var localPort = GetNextFreeTcpPort(); | |||
| LocalEndPoint = new IPEndPoint(IPAddress.Loopback, localPort); | |||
| _pluginProcess.StartInfo.Environment["SS_LOCAL_HOST"] = LocalEndPoint.Address.ToString(); | |||
| _pluginProcess.StartInfo.Environment["SS_LOCAL_PORT"] = LocalEndPoint.Port.ToString(); | |||
| _pluginProcess.StartInfo.Arguments = ExpandEnvironmentVariables(_pluginProcess.StartInfo.Arguments, _pluginProcess.StartInfo.EnvironmentVariables); | |||
| try | |||
| { | |||
| _pluginProcess.Start(); | |||
| } | |||
| catch (System.ComponentModel.Win32Exception ex) | |||
| } | |||
| catch (System.ComponentModel.Win32Exception ex) | |||
| { | |||
| // do not use File.Exists(...), it can not handle the scenarios when the plugin file is in system environment path. | |||
| // https://docs.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values | |||
| @@ -115,65 +115,65 @@ namespace Shadowsocks.Controller.Service | |||
| throw new FileNotFoundException(I18N.GetString("Cannot find the plugin program file"), _pluginProcess.StartInfo.FileName, ex); | |||
| } | |||
| throw new ApplicationException(I18N.GetString("Plugin Program"), ex); | |||
| } | |||
| _pluginJob.AddProcess(_pluginProcess.Handle); | |||
| _started = true; | |||
| } | |||
| return true; | |||
| } | |||
| public string ExpandEnvironmentVariables(string name, StringDictionary environmentVariables = null) | |||
| { | |||
| // Expand the environment variables from the new process itself | |||
| if (environmentVariables != null) | |||
| { | |||
| foreach(string key in environmentVariables.Keys) | |||
| { | |||
| name = name.Replace($"%{key}%", environmentVariables[key], StringComparison.OrdinalIgnoreCase); | |||
| } | |||
| } | |||
| // Also expand the environment variables from current main process (system) | |||
| name = Environment.ExpandEnvironmentVariables(name); | |||
| return name; | |||
| } | |||
| static int GetNextFreeTcpPort() | |||
| { | |||
| var l = new TcpListener(IPAddress.Loopback, 0); | |||
| l.Start(); | |||
| int port = ((IPEndPoint)l.LocalEndpoint).Port; | |||
| l.Stop(); | |||
| return port; | |||
| } | |||
| public void Dispose() | |||
| { | |||
| if (_disposed) | |||
| { | |||
| return; | |||
| } | |||
| try | |||
| { | |||
| if (!_pluginProcess.HasExited) | |||
| { | |||
| _pluginProcess.Kill(); | |||
| _pluginProcess.WaitForExit(); | |||
| } | |||
| } | |||
| catch (Exception) { } | |||
| finally | |||
| { | |||
| try | |||
| { | |||
| _pluginProcess.Dispose(); | |||
| _pluginJob.Dispose(); | |||
| } | |||
| catch (Exception) { } | |||
| _disposed = true; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| _pluginJob.AddProcess(_pluginProcess.Handle); | |||
| _started = true; | |||
| } | |||
| return true; | |||
| } | |||
| public string ExpandEnvironmentVariables(string name, StringDictionary environmentVariables = null) | |||
| { | |||
| // Expand the environment variables from the new process itself | |||
| if (environmentVariables != null) | |||
| { | |||
| foreach(string key in environmentVariables.Keys) | |||
| { | |||
| name = name.Replace($"%{key}%", environmentVariables[key], StringComparison.OrdinalIgnoreCase); | |||
| } | |||
| } | |||
| // Also expand the environment variables from current main process (system) | |||
| name = Environment.ExpandEnvironmentVariables(name); | |||
| return name; | |||
| } | |||
| static int GetNextFreeTcpPort() | |||
| { | |||
| var l = new TcpListener(IPAddress.Loopback, 0); | |||
| l.Start(); | |||
| int port = ((IPEndPoint)l.LocalEndpoint).Port; | |||
| l.Stop(); | |||
| return port; | |||
| } | |||
| public void Dispose() | |||
| { | |||
| if (_disposed) | |||
| { | |||
| return; | |||
| } | |||
| try | |||
| { | |||
| if (!_pluginProcess.HasExited) | |||
| { | |||
| _pluginProcess.Kill(); | |||
| _pluginProcess.WaitForExit(); | |||
| } | |||
| } | |||
| catch (Exception) { } | |||
| finally | |||
| { | |||
| try | |||
| { | |||
| _pluginProcess.Dispose(); | |||
| _pluginJob.Dispose(); | |||
| } | |||
| catch (Exception) { } | |||
| _disposed = true; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -846,7 +846,7 @@ namespace Shadowsocks.Controller | |||
| AsyncSession session = timer.Session; | |||
| Server server = timer.Server; | |||
| OnFailed?.Invoke(this, new SSRelayEventArgs(_server)); | |||
| Logger.Info($"{server.FriendlyName()} timed out"); | |||
| Logger.Info($"{server.ToString()} timed out"); | |||
| session.Remote.Close(); | |||
| Close(); | |||
| } | |||
| @@ -873,7 +873,7 @@ namespace Shadowsocks.Controller | |||
| _destConnected = true; | |||
| Logger.Debug($"Socket connected to ss server: {_server.FriendlyName()}"); | |||
| Logger.Debug($"Socket connected to ss server: {_server.ToString()}"); | |||
| TimeSpan latency = DateTime.Now - _startConnectTime; | |||
| @@ -367,48 +367,7 @@ namespace Shadowsocks.Controller | |||
| public string GetServerURLForCurrentServer() | |||
| { | |||
| Server server = GetCurrentServer(); | |||
| return GetServerURL(server); | |||
| } | |||
| public static string GetServerURL(Server server) | |||
| { | |||
| string tag = string.Empty; | |||
| string url = string.Empty; | |||
| if (string.IsNullOrWhiteSpace(server.plugin)) | |||
| { | |||
| // For backwards compatiblity, if no plugin, use old url format | |||
| string parts = $"{server.method}:{server.password}@{server.server}:{server.server_port}"; | |||
| string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||
| url = base64; | |||
| } | |||
| else | |||
| { | |||
| // SIP002 | |||
| string parts = $"{server.method}:{server.password}"; | |||
| string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||
| string websafeBase64 = base64.Replace('+', '-').Replace('/', '_').TrimEnd('='); | |||
| string pluginPart = server.plugin; | |||
| if (!string.IsNullOrWhiteSpace(server.plugin_opts)) | |||
| { | |||
| pluginPart += ";" + server.plugin_opts; | |||
| } | |||
| url = string.Format( | |||
| "{0}@{1}:{2}/?plugin={3}", | |||
| websafeBase64, | |||
| server.FormatHostName(server.server), | |||
| server.server_port, | |||
| HttpUtility.UrlEncode(pluginPart, Encoding.UTF8)); | |||
| } | |||
| if (!server.remarks.IsNullOrEmpty()) | |||
| { | |||
| tag = $"#{HttpUtility.UrlEncode(server.remarks, Encoding.UTF8)}"; | |||
| } | |||
| return $"ss://{url}{tag}"; | |||
| return GetCurrentServer().GetURL(_config.generateLegacyUrl); | |||
| } | |||
| public void UpdateStatisticsConfiguration(bool enabled) | |||
| @@ -114,7 +114,7 @@ namespace Shadowsocks.Controller.Strategy | |||
| 100 * 1000 * Math.Min(5 * 60, (now - status.lastFailure).TotalSeconds) | |||
| -2 * 5 * (Math.Min(2000, status.latency.TotalMilliseconds) / (1 + (now - status.lastTimeDetectLatency).TotalSeconds / 30 / 10) + | |||
| -0.5 * 200 * Math.Min(5, (status.lastRead - status.lastWrite).TotalSeconds)); | |||
| logger.Debug(String.Format("server: {0} latency:{1} score: {2}", status.server.FriendlyName(), status.latency, status.score)); | |||
| logger.Debug(String.Format("server: {0} latency:{1} score: {2}", status.server.ToString(), status.latency, status.score)); | |||
| } | |||
| ServerStatus max = null; | |||
| foreach (var status in servers) | |||
| @@ -136,14 +136,14 @@ namespace Shadowsocks.Controller.Strategy | |||
| if (_currentServer == null || max.score - _currentServer.score > 200) | |||
| { | |||
| _currentServer = max; | |||
| logger.Info($"HA switching to server: {_currentServer.server.FriendlyName()}"); | |||
| logger.Info($"HA switching to server: {_currentServer.server.ToString()}"); | |||
| } | |||
| } | |||
| } | |||
| public void UpdateLatency(Model.Server server, TimeSpan latency) | |||
| { | |||
| logger.Debug($"latency: {server.FriendlyName()} {latency}"); | |||
| logger.Debug($"latency: {server.ToString()} {latency}"); | |||
| ServerStatus status; | |||
| if (_serverStatus.TryGetValue(server, out status)) | |||
| @@ -155,7 +155,7 @@ namespace Shadowsocks.Controller.Strategy | |||
| public void UpdateLastRead(Model.Server server) | |||
| { | |||
| logger.Debug($"last read: {server.FriendlyName()}"); | |||
| logger.Debug($"last read: {server.ToString()}"); | |||
| ServerStatus status; | |||
| if (_serverStatus.TryGetValue(server, out status)) | |||
| @@ -166,7 +166,7 @@ namespace Shadowsocks.Controller.Strategy | |||
| public void UpdateLastWrite(Model.Server server) | |||
| { | |||
| logger.Debug($"last write: {server.FriendlyName()}"); | |||
| logger.Debug($"last write: {server.ToString()}"); | |||
| ServerStatus status; | |||
| if (_serverStatus.TryGetValue(server, out status)) | |||
| @@ -177,7 +177,7 @@ namespace Shadowsocks.Controller.Strategy | |||
| public void SetFailure(Model.Server server) | |||
| { | |||
| logger.Debug($"failure: {server.FriendlyName()}"); | |||
| logger.Debug($"failure: {server.ToString()}"); | |||
| ServerStatus status; | |||
| if (_serverStatus.TryGetValue(server, out status)) | |||
| @@ -109,7 +109,7 @@ namespace Shadowsocks.Controller.Strategy | |||
| var bestResult = serversWithStatistics | |||
| .Aggregate((server1, server2) => server1.score > server2.score ? server1 : server2); | |||
| LogWhenEnabled($"Switch to server: {bestResult.server.FriendlyName()} by statistics: score {bestResult.score}"); | |||
| LogWhenEnabled($"Switch to server: {bestResult.server.ToString()} by statistics: score {bestResult.score}"); | |||
| _currentServer = bestResult.server; | |||
| } | |||
| catch (Exception e) | |||
| @@ -147,7 +147,7 @@ namespace Shadowsocks.Controller.Strategy | |||
| public void SetFailure(Server server) | |||
| { | |||
| logger.Debug($"failure: {server.FriendlyName()}"); | |||
| logger.Debug($"failure: {server.ToString()}"); | |||
| } | |||
| public void UpdateLastRead(Server server) | |||
| @@ -11,7 +11,7 @@ namespace Shadowsocks.Model | |||
| public class Configuration | |||
| { | |||
| [JsonIgnore] | |||
| private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
| private static readonly Logger logger = LogManager.GetCurrentClassLogger(); | |||
| public string version; | |||
| @@ -24,14 +24,10 @@ namespace Shadowsocks.Model | |||
| public bool enabled; | |||
| public bool shareOverLan; | |||
| public bool isDefault; | |||
| public bool isIPv6Enabled = false; | |||
| public int localPort; | |||
| public bool portableMode = true; | |||
| public bool showPluginOutput; | |||
| public string pacUrl; | |||
| public string geositeUrl; | |||
| public string geositeGroup = "geolocation-!cn"; | |||
| public bool geositeBlacklistMode = true; | |||
| public bool useOnlinePac; | |||
| public bool secureLocalPac = true; | |||
| @@ -39,6 +35,15 @@ namespace Shadowsocks.Model | |||
| public bool autoCheckUpdate; | |||
| public bool checkPreRelease; | |||
| public bool isVerboseLogging; | |||
| // hidden options | |||
| public bool isIPv6Enabled = false; // for experimental ipv6 support | |||
| public bool generateLegacyUrl = false; // for pre-sip002 url compatibility | |||
| public string geositeUrl; // for custom geosite source (and rule group) | |||
| public string geositeGroup = "geolocation-!cn"; | |||
| public bool geositeBlacklistMode = true; | |||
| //public NLogConfig.LogLevel logLevel; | |||
| public LogViewerConfig logViewer; | |||
| public ProxyConfig proxy; | |||
| @@ -48,11 +53,10 @@ namespace Shadowsocks.Model | |||
| NLogConfig nLogConfig; | |||
| private static readonly string CONFIG_FILE = "gui-config.json"; | |||
| private static readonly NLogConfig.LogLevel verboseLogLevel = | |||
| #if DEBUG | |||
| NLogConfig.LogLevel.Trace; | |||
| private static readonly NLogConfig.LogLevel verboseLogLevel = NLogConfig.LogLevel.Trace; | |||
| #else | |||
| NLogConfig.LogLevel.Debug; | |||
| private static readonly NLogConfig.LogLevel verboseLogLevel = NLogConfig.LogLevel.Debug; | |||
| #endif | |||
| @@ -194,9 +198,9 @@ namespace Shadowsocks.Model | |||
| sw.Flush(); | |||
| } | |||
| try | |||
| { | |||
| // apply changs to NLog.config | |||
| config.nLogConfig.SetLogLevel(config.isVerboseLogging? verboseLogLevel : NLogConfig.LogLevel.Info); | |||
| { | |||
| // apply changes to NLog.config | |||
| config.nLogConfig.SetLogLevel(config.isVerboseLogging ? verboseLogLevel : NLogConfig.LogLevel.Info); | |||
| NLogConfig.SaveXML(config.nLogConfig); | |||
| } | |||
| catch (Exception e) | |||
| @@ -212,7 +216,7 @@ namespace Shadowsocks.Model | |||
| public static Server AddDefaultServerOrServer(Configuration config, Server server = null, int? index = null) | |||
| { | |||
| if (config != null && config.configs != null) | |||
| if (config?.configs != null) | |||
| { | |||
| server = (server ?? GetDefaultServer()); | |||
| @@ -231,12 +235,6 @@ namespace Shadowsocks.Model | |||
| return new Server(); | |||
| } | |||
| private static void Assert(bool condition) | |||
| { | |||
| if (!condition) | |||
| throw new Exception(I18N.GetString("assertion failure")); | |||
| } | |||
| public static void CheckPort(int port) | |||
| { | |||
| if (port <= 0 || port > 65535) | |||
| @@ -5,6 +5,7 @@ using System.Text; | |||
| using System.Web; | |||
| using Shadowsocks.Controller; | |||
| using System.Text.RegularExpressions; | |||
| using System.Linq; | |||
| namespace Shadowsocks.Model | |||
| { | |||
| @@ -15,9 +16,8 @@ namespace Shadowsocks.Model | |||
| public const int DefaultPort = 8388; | |||
| #region ParseLegacyURL | |||
| public static readonly Regex | |||
| UrlFinder = new Regex(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase), | |||
| DetailsParser = new Regex(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase); | |||
| private static readonly Regex UrlFinder = new Regex(@"ss://(?<base64>[A-Za-z0-9+-/=_]+)(?:#(?<tag>\S+))?", RegexOptions.IgnoreCase); | |||
| private static readonly Regex DetailsParser = new Regex(@"^((?<method>.+?):(?<password>.*)@(?<hostname>.+?):(?<port>\d+?))$", RegexOptions.IgnoreCase); | |||
| #endregion ParseLegacyURL | |||
| private const int DefaultServerTimeoutSec = 5; | |||
| @@ -44,28 +44,77 @@ namespace Shadowsocks.Model | |||
| return server == o2.server && server_port == o2.server_port; | |||
| } | |||
| public string FriendlyName() | |||
| public override string ToString() | |||
| { | |||
| if (server.IsNullOrEmpty()) | |||
| { | |||
| return I18N.GetString("New server"); | |||
| } | |||
| string serverStr = $"{FormatHostName(server)}:{server_port}"; | |||
| string serverStr = $"{FormalHostName}:{server_port}"; | |||
| return remarks.IsNullOrEmpty() | |||
| ? serverStr | |||
| : $"{remarks} ({serverStr})"; | |||
| } | |||
| public string FormatHostName(string hostName) | |||
| public string GetURL(bool legacyUrl = false) | |||
| { | |||
| // CheckHostName() won't do a real DNS lookup | |||
| switch (Uri.CheckHostName(hostName)) | |||
| string tag = string.Empty; | |||
| string url = string.Empty; | |||
| if (legacyUrl && string.IsNullOrWhiteSpace(plugin)) | |||
| { | |||
| // For backwards compatiblity, if no plugin, use old url format | |||
| string parts = $"{method}:{password}@{server}:{server_port}"; | |||
| string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||
| url = base64; | |||
| } | |||
| else | |||
| { | |||
| // SIP002 | |||
| string parts = $"{method}:{password}"; | |||
| string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||
| string websafeBase64 = base64.Replace('+', '-').Replace('/', '_').TrimEnd('='); | |||
| url = string.Format( | |||
| "{0}@{1}:{2}/", | |||
| websafeBase64, | |||
| FormalHostName, | |||
| server_port | |||
| ); | |||
| if (!plugin.IsNullOrWhiteSpace()) | |||
| { | |||
| string pluginPart = plugin; | |||
| if (!string.IsNullOrWhiteSpace(plugin_opts)) | |||
| { | |||
| pluginPart += ";" + plugin_opts; | |||
| } | |||
| string pluginQuery = "?plugin=" + HttpUtility.UrlEncode(pluginPart, Encoding.UTF8); | |||
| url += pluginQuery; | |||
| } | |||
| } | |||
| if (!remarks.IsNullOrEmpty()) | |||
| { | |||
| tag = $"#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}"; | |||
| } | |||
| return $"ss://{url}{tag}"; | |||
| } | |||
| public string FormalHostName | |||
| { | |||
| get | |||
| { | |||
| case UriHostNameType.IPv6: // Add square bracket when IPv6 (RFC3986) | |||
| return $"[{hostName}]"; | |||
| default: // IPv4 or domain name | |||
| return hostName; | |||
| // CheckHostName() won't do a real DNS lookup | |||
| switch (Uri.CheckHostName(server)) | |||
| { | |||
| case UriHostNameType.IPv6: // Add square bracket when IPv6 (RFC3986) | |||
| return $"[{server}]"; | |||
| default: // IPv4 or domain name | |||
| return server; | |||
| } | |||
| } | |||
| } | |||
| @@ -114,79 +163,81 @@ namespace Shadowsocks.Model | |||
| return server; | |||
| } | |||
| public static List<Server> GetServers(string ssURL) | |||
| public static Server ParseURL(string serverUrl) | |||
| { | |||
| var serverUrls = ssURL.Split('\r', '\n', ' '); | |||
| string _serverUrl = serverUrl.Trim(); | |||
| if (!_serverUrl.BeginWith("ss://", StringComparison.InvariantCultureIgnoreCase)) | |||
| { | |||
| return null; | |||
| } | |||
| List<Server> servers = new List<Server>(); | |||
| foreach (string serverUrl in serverUrls) | |||
| Server legacyServer = ParseLegacyURL(serverUrl); | |||
| if (legacyServer != null) //legacy | |||
| { | |||
| return legacyServer; | |||
| } | |||
| else //SIP002 | |||
| { | |||
| string _serverUrl = serverUrl.Trim(); | |||
| if (!_serverUrl.BeginWith("ss://", StringComparison.InvariantCultureIgnoreCase)) | |||
| Uri parsedUrl; | |||
| try | |||
| { | |||
| continue; | |||
| parsedUrl = new Uri(serverUrl); | |||
| } | |||
| Server legacyServer = ParseLegacyURL(serverUrl); | |||
| if (legacyServer != null) //legacy | |||
| catch (UriFormatException) | |||
| { | |||
| servers.Add(legacyServer); | |||
| return null; | |||
| } | |||
| else //SIP002 | |||
| Server server = new Server | |||
| { | |||
| Uri parsedUrl; | |||
| try | |||
| { | |||
| parsedUrl = new Uri(serverUrl); | |||
| } | |||
| catch (UriFormatException) | |||
| { | |||
| continue; | |||
| } | |||
| Server server = new Server | |||
| { | |||
| remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), | |||
| server = parsedUrl.IdnHost, | |||
| server_port = parsedUrl.Port, | |||
| }; | |||
| // parse base64 UserInfo | |||
| string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped); | |||
| string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64 | |||
| string userInfo = ""; | |||
| try | |||
| { | |||
| userInfo = Encoding.UTF8.GetString(Convert.FromBase64String( | |||
| base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))); | |||
| } | |||
| catch (FormatException) | |||
| { | |||
| continue; | |||
| } | |||
| string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2); | |||
| if (userInfoParts.Length != 2) | |||
| { | |||
| continue; | |||
| } | |||
| server.method = userInfoParts[0]; | |||
| server.password = userInfoParts[1]; | |||
| NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query); | |||
| string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2); | |||
| if (pluginParts.Length > 0) | |||
| { | |||
| server.plugin = pluginParts[0] ?? ""; | |||
| } | |||
| remarks = parsedUrl.GetComponents(UriComponents.Fragment, UriFormat.Unescaped), | |||
| server = parsedUrl.IdnHost, | |||
| server_port = parsedUrl.Port, | |||
| }; | |||
| // parse base64 UserInfo | |||
| string rawUserInfo = parsedUrl.GetComponents(UriComponents.UserInfo, UriFormat.Unescaped); | |||
| string base64 = rawUserInfo.Replace('-', '+').Replace('_', '/'); // Web-safe base64 to normal base64 | |||
| string userInfo = ""; | |||
| try | |||
| { | |||
| userInfo = Encoding.UTF8.GetString(Convert.FromBase64String( | |||
| base64.PadRight(base64.Length + (4 - base64.Length % 4) % 4, '='))); | |||
| } | |||
| catch (FormatException) | |||
| { | |||
| return null; | |||
| } | |||
| string[] userInfoParts = userInfo.Split(new char[] { ':' }, 2); | |||
| if (userInfoParts.Length != 2) | |||
| { | |||
| return null; | |||
| } | |||
| server.method = userInfoParts[0]; | |||
| server.password = userInfoParts[1]; | |||
| if (pluginParts.Length > 1) | |||
| { | |||
| server.plugin_opts = pluginParts[1] ?? ""; | |||
| } | |||
| NameValueCollection queryParameters = HttpUtility.ParseQueryString(parsedUrl.Query); | |||
| string[] pluginParts = (queryParameters["plugin"] ?? "").Split(new[] { ';' }, 2); | |||
| if (pluginParts.Length > 0) | |||
| { | |||
| server.plugin = pluginParts[0] ?? ""; | |||
| } | |||
| servers.Add(server); | |||
| if (pluginParts.Length > 1) | |||
| { | |||
| server.plugin_opts = pluginParts[1] ?? ""; | |||
| } | |||
| return server; | |||
| } | |||
| return servers; | |||
| } | |||
| public static List<Server> GetServers(string ssURL) | |||
| { | |||
| return ssURL | |||
| .Split('\r', '\n', ' ') | |||
| .Select(u => ParseURL(u)) | |||
| .Where(s => s != null) | |||
| .ToList(); | |||
| } | |||
| public string Identifier() | |||
| @@ -1,8 +1,4 @@ | |||
| using System; | |||
| using System.Diagnostics; | |||
| using System.IO; | |||
| using System.Threading; | |||
| using System.Windows.Forms; | |||
| using Microsoft.Win32; | |||
| using NLog; | |||
| using Microsoft.Win32; | |||
| @@ -10,18 +6,23 @@ using Shadowsocks.Controller; | |||
| using Shadowsocks.Controller.Hotkeys; | |||
| using Shadowsocks.Util; | |||
| using Shadowsocks.View; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Diagnostics; | |||
| using System.IO; | |||
| using System.IO.Pipes; | |||
| using System.Text; | |||
| using System.Net; | |||
| using System.Linq; | |||
| using System.Collections.Generic; | |||
| using System.Net; | |||
| using System.Text; | |||
| using System.Threading; | |||
| using System.Threading.Tasks; | |||
| using System.Windows.Forms; | |||
| namespace Shadowsocks | |||
| { | |||
| static class Program | |||
| internal static class Program | |||
| { | |||
| private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
| private static readonly Logger logger = LogManager.GetCurrentClassLogger(); | |||
| public static ShadowsocksController MainController { get; private set; } | |||
| public static MenuViewController MenuController { get; private set; } | |||
| public static string[] Args { get; private set; } | |||
| @@ -53,7 +54,6 @@ namespace Shadowsocks | |||
| { | |||
| pipeExist = false; | |||
| } | |||
| // TODO: switch to better argv parser when it's getting complicate | |||
| List<string> alist = Args.ToList(); | |||
| // check --open-url param | |||
| @@ -133,7 +133,6 @@ namespace Shadowsocks | |||
| { | |||
| MainController.AskAddServerBySSURL(addedUrl); | |||
| } | |||
| Application.Run(); | |||
| } | |||
| @@ -172,7 +171,7 @@ namespace Shadowsocks | |||
| logger.Info("os wake up"); | |||
| if (MainController != null) | |||
| { | |||
| System.Threading.Tasks.Task.Factory.StartNew(() => | |||
| Task.Factory.StartNew(() => | |||
| { | |||
| Thread.Sleep(10 * 1000); | |||
| try | |||
| @@ -1,212 +1,212 @@ | |||
| using System; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using System.Text; | |||
| using System.Text.RegularExpressions; | |||
| using System.Threading; | |||
| using System; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using System.Text; | |||
| using System.Text.RegularExpressions; | |||
| using System.Threading; | |||
| using NLog; | |||
| using Shadowsocks.Controller; | |||
| using Shadowsocks.Util.Sockets; | |||
| namespace Shadowsocks.Proxy | |||
| { | |||
| public class HttpProxy : IProxy | |||
| { | |||
| private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
| private class FakeAsyncResult : IAsyncResult | |||
| { | |||
| public readonly HttpState innerState; | |||
| private readonly IAsyncResult r; | |||
| public FakeAsyncResult(IAsyncResult orig, HttpState state) | |||
| { | |||
| r = orig; | |||
| innerState = state; | |||
| } | |||
| public bool IsCompleted => r.IsCompleted; | |||
| public WaitHandle AsyncWaitHandle => r.AsyncWaitHandle; | |||
| public object AsyncState => innerState.AsyncState; | |||
| public bool CompletedSynchronously => r.CompletedSynchronously; | |||
| } | |||
| private class HttpState | |||
| { | |||
| public AsyncCallback Callback { get; set; } | |||
| public object AsyncState { get; set; } | |||
| public int BytesToRead; | |||
| public Exception ex { get; set; } | |||
| } | |||
| public EndPoint LocalEndPoint => _remote.LocalEndPoint; | |||
| public EndPoint ProxyEndPoint { get; private set; } | |||
| public EndPoint DestEndPoint { get; private set; } | |||
| private readonly WrappedSocket _remote = new WrappedSocket(); | |||
| public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state) | |||
| { | |||
| ProxyEndPoint = remoteEP; | |||
| _remote.BeginConnect(remoteEP, callback, state); | |||
| } | |||
| public void EndConnectProxy(IAsyncResult asyncResult) | |||
| { | |||
| _remote.EndConnect(asyncResult); | |||
| _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
| } | |||
| private const string HTTP_CRLF = "\r\n"; | |||
| private const string HTTP_CONNECT_TEMPLATE = | |||
| "CONNECT {0} HTTP/1.1" + HTTP_CRLF + | |||
| "Host: {0}" + HTTP_CRLF + | |||
| "Proxy-Connection: keep-alive" + HTTP_CRLF + | |||
| "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + HTTP_CRLF + | |||
| "{1}" + // Proxy-Authorization if any | |||
| "" + HTTP_CRLF; // End with an empty line | |||
| private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF; | |||
| public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null) | |||
| { | |||
| DestEndPoint = destEndPoint; | |||
| using Shadowsocks.Controller; | |||
| using Shadowsocks.Util.Sockets; | |||
| namespace Shadowsocks.Proxy | |||
| { | |||
| public class HttpProxy : IProxy | |||
| { | |||
| private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
| private class FakeAsyncResult : IAsyncResult | |||
| { | |||
| public readonly HttpState innerState; | |||
| private readonly IAsyncResult r; | |||
| public FakeAsyncResult(IAsyncResult orig, HttpState state) | |||
| { | |||
| r = orig; | |||
| innerState = state; | |||
| } | |||
| public bool IsCompleted => r.IsCompleted; | |||
| public WaitHandle AsyncWaitHandle => r.AsyncWaitHandle; | |||
| public object AsyncState => innerState.AsyncState; | |||
| public bool CompletedSynchronously => r.CompletedSynchronously; | |||
| } | |||
| private class HttpState | |||
| { | |||
| public AsyncCallback Callback { get; set; } | |||
| public object AsyncState { get; set; } | |||
| public int BytesToRead; | |||
| public Exception ex { get; set; } | |||
| } | |||
| public EndPoint LocalEndPoint => _remote.LocalEndPoint; | |||
| public EndPoint ProxyEndPoint { get; private set; } | |||
| public EndPoint DestEndPoint { get; private set; } | |||
| private readonly WrappedSocket _remote = new WrappedSocket(); | |||
| public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state) | |||
| { | |||
| ProxyEndPoint = remoteEP; | |||
| _remote.BeginConnect(remoteEP, callback, state); | |||
| } | |||
| public void EndConnectProxy(IAsyncResult asyncResult) | |||
| { | |||
| _remote.EndConnect(asyncResult); | |||
| _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
| } | |||
| private const string HTTP_CRLF = "\r\n"; | |||
| private const string HTTP_CONNECT_TEMPLATE = | |||
| "CONNECT {0} HTTP/1.1" + HTTP_CRLF + | |||
| "Host: {0}" + HTTP_CRLF + | |||
| "Proxy-Connection: keep-alive" + HTTP_CRLF + | |||
| "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" + HTTP_CRLF + | |||
| "{1}" + // Proxy-Authorization if any | |||
| "" + HTTP_CRLF; // End with an empty line | |||
| private const string PROXY_AUTH_TEMPLATE = "Proxy-Authorization: Basic {0}" + HTTP_CRLF; | |||
| public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null) | |||
| { | |||
| DestEndPoint = destEndPoint; | |||
| String authInfo = ""; | |||
| if (auth != null) | |||
| { | |||
| string authKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(auth.UserName + ":" + auth.Password)); | |||
| authInfo = string.Format(PROXY_AUTH_TEMPLATE, authKey); | |||
| } | |||
| string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo); | |||
| var b = Encoding.UTF8.GetBytes(request); | |||
| var st = new HttpState(); | |||
| st.Callback = callback; | |||
| st.AsyncState = state; | |||
| _remote.BeginSend(b, 0, b.Length, 0, HttpRequestSendCallback, st); | |||
| } | |||
| public void EndConnectDest(IAsyncResult asyncResult) | |||
| { | |||
| var state = ((FakeAsyncResult)asyncResult).innerState; | |||
| if (state.ex != null) | |||
| { | |||
| throw state.ex; | |||
| } | |||
| } | |||
| public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, | |||
| object state) | |||
| { | |||
| _remote.BeginSend(buffer, offset, size, socketFlags, callback, state); | |||
| } | |||
| public int EndSend(IAsyncResult asyncResult) | |||
| { | |||
| return _remote.EndSend(asyncResult); | |||
| } | |||
| public void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, | |||
| object state) | |||
| { | |||
| _remote.BeginReceive(buffer, offset, size, socketFlags, callback, state); | |||
| } | |||
| public int EndReceive(IAsyncResult asyncResult) | |||
| { | |||
| return _remote.EndReceive(asyncResult); | |||
| } | |||
| public void Shutdown(SocketShutdown how) | |||
| { | |||
| _remote.Shutdown(how); | |||
| } | |||
| public void Close() | |||
| { | |||
| _remote.Dispose(); | |||
| } | |||
| private void HttpRequestSendCallback(IAsyncResult ar) | |||
| { | |||
| var state = (HttpState) ar.AsyncState; | |||
| try | |||
| { | |||
| _remote.EndSend(ar); | |||
| // start line read | |||
| new LineReader(_remote, OnLineRead, OnException, OnFinish, Encoding.UTF8, HTTP_CRLF, 1024, new FakeAsyncResult(ar, state)); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| state.ex = ex; | |||
| state.Callback?.Invoke(new FakeAsyncResult(ar, state)); | |||
| } | |||
| } | |||
| private void OnFinish(byte[] lastBytes, int index, int length, object state) | |||
| { | |||
| var st = (FakeAsyncResult)state; | |||
| if (st.innerState.ex == null) | |||
| { | |||
| if (!_established) | |||
| { | |||
| st.innerState.ex = new Exception(I18N.GetString("Proxy request failed")); | |||
| } | |||
| // TODO: save last bytes | |||
| } | |||
| st.innerState.Callback?.Invoke(st); | |||
| } | |||
| private void OnException(Exception ex, object state) | |||
| { | |||
| var st = (FakeAsyncResult) state; | |||
| st.innerState.ex = ex; | |||
| } | |||
| private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled); | |||
| private int _respondLineCount = 0; | |||
| private bool _established = false; | |||
| private bool OnLineRead(string line, object state) | |||
| { | |||
| logger.Trace(line); | |||
| if (_respondLineCount == 0) | |||
| { | |||
| var m = HttpRespondHeaderRegex.Match(line); | |||
| if (m.Success) | |||
| { | |||
| var resultCode = m.Groups[2].Value; | |||
| if ("200" != resultCode) | |||
| { | |||
| return true; | |||
| } | |||
| _established = true; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if (line.IsNullOrEmpty()) | |||
| { | |||
| return true; | |||
| } | |||
| } | |||
| _respondLineCount++; | |||
| return false; | |||
| } | |||
| } | |||
| } | |||
| string request = string.Format(HTTP_CONNECT_TEMPLATE, destEndPoint, authInfo); | |||
| var b = Encoding.UTF8.GetBytes(request); | |||
| var st = new HttpState(); | |||
| st.Callback = callback; | |||
| st.AsyncState = state; | |||
| _remote.BeginSend(b, 0, b.Length, 0, HttpRequestSendCallback, st); | |||
| } | |||
| public void EndConnectDest(IAsyncResult asyncResult) | |||
| { | |||
| var state = ((FakeAsyncResult)asyncResult).innerState; | |||
| if (state.ex != null) | |||
| { | |||
| throw state.ex; | |||
| } | |||
| } | |||
| public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, | |||
| object state) | |||
| { | |||
| _remote.BeginSend(buffer, offset, size, socketFlags, callback, state); | |||
| } | |||
| public int EndSend(IAsyncResult asyncResult) | |||
| { | |||
| return _remote.EndSend(asyncResult); | |||
| } | |||
| public void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, | |||
| object state) | |||
| { | |||
| _remote.BeginReceive(buffer, offset, size, socketFlags, callback, state); | |||
| } | |||
| public int EndReceive(IAsyncResult asyncResult) | |||
| { | |||
| return _remote.EndReceive(asyncResult); | |||
| } | |||
| public void Shutdown(SocketShutdown how) | |||
| { | |||
| _remote.Shutdown(how); | |||
| } | |||
| public void Close() | |||
| { | |||
| _remote.Dispose(); | |||
| } | |||
| private void HttpRequestSendCallback(IAsyncResult ar) | |||
| { | |||
| var state = (HttpState) ar.AsyncState; | |||
| try | |||
| { | |||
| _remote.EndSend(ar); | |||
| // start line read | |||
| new LineReader(_remote, OnLineRead, OnException, OnFinish, Encoding.UTF8, HTTP_CRLF, 1024, new FakeAsyncResult(ar, state)); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| state.ex = ex; | |||
| state.Callback?.Invoke(new FakeAsyncResult(ar, state)); | |||
| } | |||
| } | |||
| private void OnFinish(byte[] lastBytes, int index, int length, object state) | |||
| { | |||
| var st = (FakeAsyncResult)state; | |||
| if (st.innerState.ex == null) | |||
| { | |||
| if (!_established) | |||
| { | |||
| st.innerState.ex = new Exception(I18N.GetString("Proxy request failed")); | |||
| } | |||
| // TODO: save last bytes | |||
| } | |||
| st.innerState.Callback?.Invoke(st); | |||
| } | |||
| private void OnException(Exception ex, object state) | |||
| { | |||
| var st = (FakeAsyncResult) state; | |||
| st.innerState.ex = ex; | |||
| } | |||
| private static readonly Regex HttpRespondHeaderRegex = new Regex(@"^(HTTP/1\.\d) (\d{3}) (.+)$", RegexOptions.Compiled); | |||
| private int _respondLineCount = 0; | |||
| private bool _established = false; | |||
| private bool OnLineRead(string line, object state) | |||
| { | |||
| logger.Trace(line); | |||
| if (_respondLineCount == 0) | |||
| { | |||
| var m = HttpRespondHeaderRegex.Match(line); | |||
| if (m.Success) | |||
| { | |||
| var resultCode = m.Groups[2].Value; | |||
| if ("200" != resultCode) | |||
| { | |||
| return true; | |||
| } | |||
| _established = true; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if (line.IsNullOrEmpty()) | |||
| { | |||
| return true; | |||
| } | |||
| } | |||
| _respondLineCount++; | |||
| return false; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,182 +1,182 @@ | |||
| using System; | |||
| using System.Diagnostics; | |||
| using System.Runtime.InteropServices; | |||
| using System; | |||
| using System.Diagnostics; | |||
| using System.Runtime.InteropServices; | |||
| using NLog; | |||
| using Shadowsocks.Controller; | |||
| namespace Shadowsocks.Util.ProcessManagement | |||
| { | |||
| /* | |||
| * See: | |||
| * http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net | |||
| */ | |||
| public class Job : IDisposable | |||
| { | |||
| private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
| private IntPtr handle = IntPtr.Zero; | |||
| public Job() | |||
| { | |||
| handle = CreateJobObject(IntPtr.Zero, null); | |||
| var extendedInfoPtr = IntPtr.Zero; | |||
| var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION | |||
| { | |||
| LimitFlags = 0x2000 | |||
| }; | |||
| var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION | |||
| { | |||
| BasicLimitInformation = info | |||
| }; | |||
| try | |||
| { | |||
| int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); | |||
| extendedInfoPtr = Marshal.AllocHGlobal(length); | |||
| Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); | |||
| if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, | |||
| (uint) length)) | |||
| throw new Exception(string.Format("Unable to set information. Error: {0}", | |||
| Marshal.GetLastWin32Error())); | |||
| } | |||
| finally | |||
| { | |||
| if (extendedInfoPtr != IntPtr.Zero) | |||
| { | |||
| Marshal.FreeHGlobal(extendedInfoPtr); | |||
| extendedInfoPtr = IntPtr.Zero; | |||
| } | |||
| } | |||
| } | |||
| public bool AddProcess(IntPtr processHandle) | |||
| { | |||
| var succ = AssignProcessToJobObject(handle, processHandle); | |||
| if (!succ) | |||
| { | |||
| logger.Error("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error()); | |||
| } | |||
| return succ; | |||
| } | |||
| public bool AddProcess(int processId) | |||
| { | |||
| return AddProcess(Process.GetProcessById(processId).Handle); | |||
| } | |||
| #region IDisposable | |||
| private bool disposed; | |||
| public void Dispose() | |||
| { | |||
| Dispose(true); | |||
| GC.SuppressFinalize(this); | |||
| } | |||
| protected virtual void Dispose(bool disposing) | |||
| { | |||
| if (disposed) return; | |||
| disposed = true; | |||
| using Shadowsocks.Controller; | |||
| namespace Shadowsocks.Util.ProcessManagement | |||
| { | |||
| /* | |||
| * See: | |||
| * http://stackoverflow.com/questions/6266820/working-example-of-createjobobject-setinformationjobobject-pinvoke-in-net | |||
| */ | |||
| public class Job : IDisposable | |||
| { | |||
| private static Logger logger = LogManager.GetCurrentClassLogger(); | |||
| private IntPtr handle = IntPtr.Zero; | |||
| public Job() | |||
| { | |||
| handle = CreateJobObject(IntPtr.Zero, null); | |||
| var extendedInfoPtr = IntPtr.Zero; | |||
| var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION | |||
| { | |||
| LimitFlags = 0x2000 | |||
| }; | |||
| var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION | |||
| { | |||
| BasicLimitInformation = info | |||
| }; | |||
| try | |||
| { | |||
| int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); | |||
| extendedInfoPtr = Marshal.AllocHGlobal(length); | |||
| Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); | |||
| if (!SetInformationJobObject(handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, | |||
| (uint) length)) | |||
| throw new Exception(string.Format("Unable to set information. Error: {0}", | |||
| Marshal.GetLastWin32Error())); | |||
| } | |||
| finally | |||
| { | |||
| if (extendedInfoPtr != IntPtr.Zero) | |||
| { | |||
| Marshal.FreeHGlobal(extendedInfoPtr); | |||
| extendedInfoPtr = IntPtr.Zero; | |||
| } | |||
| } | |||
| } | |||
| public bool AddProcess(IntPtr processHandle) | |||
| { | |||
| var succ = AssignProcessToJobObject(handle, processHandle); | |||
| if (!succ) | |||
| { | |||
| logger.Error("Failed to call AssignProcessToJobObject! GetLastError=" + Marshal.GetLastWin32Error()); | |||
| } | |||
| return succ; | |||
| } | |||
| public bool AddProcess(int processId) | |||
| { | |||
| return AddProcess(Process.GetProcessById(processId).Handle); | |||
| } | |||
| #region IDisposable | |||
| private bool disposed; | |||
| public void Dispose() | |||
| { | |||
| Dispose(true); | |||
| GC.SuppressFinalize(this); | |||
| } | |||
| protected virtual void Dispose(bool disposing) | |||
| { | |||
| if (disposed) return; | |||
| disposed = true; | |||
| if (disposing) | |||
| { | |||
| // no managed objects to free | |||
| } | |||
| if (handle != IntPtr.Zero) | |||
| { | |||
| CloseHandle(handle); | |||
| handle = IntPtr.Zero; | |||
| } | |||
| } | |||
| ~Job() | |||
| { | |||
| Dispose(false); | |||
| } | |||
| #endregion | |||
| #region Interop | |||
| [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] | |||
| private static extern IntPtr CreateJobObject(IntPtr a, string lpName); | |||
| [DllImport("kernel32.dll", SetLastError = true)] | |||
| private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength); | |||
| [DllImport("kernel32.dll", SetLastError = true)] | |||
| private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); | |||
| [DllImport("kernel32.dll", SetLastError = true)] | |||
| [return: MarshalAs(UnmanagedType.Bool)] | |||
| private static extern bool CloseHandle(IntPtr hObject); | |||
| #endregion | |||
| } | |||
| #region Helper classes | |||
| [StructLayout(LayoutKind.Sequential)] | |||
| struct IO_COUNTERS | |||
| { | |||
| public UInt64 ReadOperationCount; | |||
| public UInt64 WriteOperationCount; | |||
| public UInt64 OtherOperationCount; | |||
| public UInt64 ReadTransferCount; | |||
| public UInt64 WriteTransferCount; | |||
| public UInt64 OtherTransferCount; | |||
| } | |||
| [StructLayout(LayoutKind.Sequential)] | |||
| struct JOBOBJECT_BASIC_LIMIT_INFORMATION | |||
| { | |||
| public Int64 PerProcessUserTimeLimit; | |||
| public Int64 PerJobUserTimeLimit; | |||
| public UInt32 LimitFlags; | |||
| public UIntPtr MinimumWorkingSetSize; | |||
| public UIntPtr MaximumWorkingSetSize; | |||
| public UInt32 ActiveProcessLimit; | |||
| public UIntPtr Affinity; | |||
| public UInt32 PriorityClass; | |||
| public UInt32 SchedulingClass; | |||
| } | |||
| [StructLayout(LayoutKind.Sequential)] | |||
| public struct SECURITY_ATTRIBUTES | |||
| { | |||
| public UInt32 nLength; | |||
| public IntPtr lpSecurityDescriptor; | |||
| public Int32 bInheritHandle; | |||
| } | |||
| [StructLayout(LayoutKind.Sequential)] | |||
| struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION | |||
| { | |||
| public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; | |||
| public IO_COUNTERS IoInfo; | |||
| public UIntPtr ProcessMemoryLimit; | |||
| public UIntPtr JobMemoryLimit; | |||
| public UIntPtr PeakProcessMemoryUsed; | |||
| public UIntPtr PeakJobMemoryUsed; | |||
| } | |||
| public enum JobObjectInfoType | |||
| { | |||
| AssociateCompletionPortInformation = 7, | |||
| BasicLimitInformation = 2, | |||
| BasicUIRestrictions = 4, | |||
| EndOfJobTimeInformation = 6, | |||
| ExtendedLimitInformation = 9, | |||
| SecurityLimitInformation = 5, | |||
| GroupInformation = 11 | |||
| } | |||
| #endregion | |||
| } | |||
| { | |||
| // no managed objects to free | |||
| } | |||
| if (handle != IntPtr.Zero) | |||
| { | |||
| CloseHandle(handle); | |||
| handle = IntPtr.Zero; | |||
| } | |||
| } | |||
| ~Job() | |||
| { | |||
| Dispose(false); | |||
| } | |||
| #endregion | |||
| #region Interop | |||
| [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] | |||
| private static extern IntPtr CreateJobObject(IntPtr a, string lpName); | |||
| [DllImport("kernel32.dll", SetLastError = true)] | |||
| private static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, UInt32 cbJobObjectInfoLength); | |||
| [DllImport("kernel32.dll", SetLastError = true)] | |||
| private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); | |||
| [DllImport("kernel32.dll", SetLastError = true)] | |||
| [return: MarshalAs(UnmanagedType.Bool)] | |||
| private static extern bool CloseHandle(IntPtr hObject); | |||
| #endregion | |||
| } | |||
| #region Helper classes | |||
| [StructLayout(LayoutKind.Sequential)] | |||
| struct IO_COUNTERS | |||
| { | |||
| public UInt64 ReadOperationCount; | |||
| public UInt64 WriteOperationCount; | |||
| public UInt64 OtherOperationCount; | |||
| public UInt64 ReadTransferCount; | |||
| public UInt64 WriteTransferCount; | |||
| public UInt64 OtherTransferCount; | |||
| } | |||
| [StructLayout(LayoutKind.Sequential)] | |||
| struct JOBOBJECT_BASIC_LIMIT_INFORMATION | |||
| { | |||
| public Int64 PerProcessUserTimeLimit; | |||
| public Int64 PerJobUserTimeLimit; | |||
| public UInt32 LimitFlags; | |||
| public UIntPtr MinimumWorkingSetSize; | |||
| public UIntPtr MaximumWorkingSetSize; | |||
| public UInt32 ActiveProcessLimit; | |||
| public UIntPtr Affinity; | |||
| public UInt32 PriorityClass; | |||
| public UInt32 SchedulingClass; | |||
| } | |||
| [StructLayout(LayoutKind.Sequential)] | |||
| public struct SECURITY_ATTRIBUTES | |||
| { | |||
| public UInt32 nLength; | |||
| public IntPtr lpSecurityDescriptor; | |||
| public Int32 bInheritHandle; | |||
| } | |||
| [StructLayout(LayoutKind.Sequential)] | |||
| struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION | |||
| { | |||
| public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; | |||
| public IO_COUNTERS IoInfo; | |||
| public UIntPtr ProcessMemoryLimit; | |||
| public UIntPtr JobMemoryLimit; | |||
| public UIntPtr PeakProcessMemoryUsed; | |||
| public UIntPtr PeakJobMemoryUsed; | |||
| } | |||
| public enum JobObjectInfoType | |||
| { | |||
| AssociateCompletionPortInformation = 7, | |||
| BasicLimitInformation = 2, | |||
| BasicUIRestrictions = 4, | |||
| EndOfJobTimeInformation = 6, | |||
| ExtendedLimitInformation = 9, | |||
| SecurityLimitInformation = 5, | |||
| GroupInformation = 11 | |||
| } | |||
| #endregion | |||
| } | |||
| @@ -1,268 +1,268 @@ | |||
| using System; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using System.Threading; | |||
| namespace Shadowsocks.Util.Sockets | |||
| { | |||
| /* | |||
| * A wrapped socket class which support both ipv4 and ipv6 based on the | |||
| * connected remote endpoint. | |||
| * | |||
| * If the server address is host name, then it may have both ipv4 and ipv6 address | |||
| * after resolving. The main idea is we don't want to resolve and choose the address | |||
| * by ourself. Instead, Socket.ConnectAsync() do handle this thing internally by trying | |||
| * each address and returning an established socket connection. | |||
| */ | |||
| public class WrappedSocket | |||
| { | |||
| public EndPoint LocalEndPoint => _activeSocket?.LocalEndPoint; | |||
| // Only used during connection and close, so it won't cost too much. | |||
| private SpinLock _socketSyncLock = new SpinLock(); | |||
| private bool _disposed; | |||
| private bool Connected => _activeSocket != null; | |||
| private Socket _activeSocket; | |||
| public void BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.IsConnected); | |||
| } | |||
| var arg = new SocketAsyncEventArgs(); | |||
| arg.RemoteEndPoint = remoteEP; | |||
| arg.Completed += OnTcpConnectCompleted; | |||
| arg.UserToken = new TcpUserToken(callback, state); | |||
| using System; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using System.Threading; | |||
| namespace Shadowsocks.Util.Sockets | |||
| { | |||
| /* | |||
| * A wrapped socket class which support both ipv4 and ipv6 based on the | |||
| * connected remote endpoint. | |||
| * | |||
| * If the server address is host name, then it may have both ipv4 and ipv6 address | |||
| * after resolving. The main idea is we don't want to resolve and choose the address | |||
| * by ourself. Instead, Socket.ConnectAsync() do handle this thing internally by trying | |||
| * each address and returning an established socket connection. | |||
| */ | |||
| public class WrappedSocket | |||
| { | |||
| public EndPoint LocalEndPoint => _activeSocket?.LocalEndPoint; | |||
| // Only used during connection and close, so it won't cost too much. | |||
| private SpinLock _socketSyncLock = new SpinLock(); | |||
| private bool _disposed; | |||
| private bool Connected => _activeSocket != null; | |||
| private Socket _activeSocket; | |||
| public void BeginConnect(EndPoint remoteEP, AsyncCallback callback, object state) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.IsConnected); | |||
| } | |||
| var arg = new SocketAsyncEventArgs(); | |||
| arg.RemoteEndPoint = remoteEP; | |||
| arg.Completed += OnTcpConnectCompleted; | |||
| arg.UserToken = new TcpUserToken(callback, state); | |||
| if(!Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, arg)) | |||
| { | |||
| OnTcpConnectCompleted(this, arg); | |||
| } | |||
| } | |||
| private class FakeAsyncResult : IAsyncResult | |||
| { | |||
| public bool IsCompleted { get; } = true; | |||
| public WaitHandle AsyncWaitHandle { get; } = null; | |||
| public object AsyncState { get; set; } | |||
| public bool CompletedSynchronously { get; } = true; | |||
| public Exception InternalException { get; set; } = null; | |||
| } | |||
| private class TcpUserToken | |||
| { | |||
| public AsyncCallback Callback { get; } | |||
| public object AsyncState { get; } | |||
| public TcpUserToken(AsyncCallback callback, object state) | |||
| { | |||
| Callback = callback; | |||
| AsyncState = state; | |||
| } | |||
| } | |||
| private void OnTcpConnectCompleted(object sender, SocketAsyncEventArgs args) | |||
| { | |||
| using (args) | |||
| { | |||
| args.Completed -= OnTcpConnectCompleted; | |||
| var token = (TcpUserToken) args.UserToken; | |||
| if (args.SocketError != SocketError.Success) | |||
| { | |||
| var ex = args.ConnectByNameError ?? new SocketException((int) args.SocketError); | |||
| var r = new FakeAsyncResult() | |||
| { | |||
| AsyncState = token.AsyncState, | |||
| InternalException = ex | |||
| }; | |||
| token.Callback(r); | |||
| } | |||
| else | |||
| { | |||
| var lockTaken = false; | |||
| if (!_socketSyncLock.IsHeldByCurrentThread) | |||
| { | |||
| _socketSyncLock.TryEnter(ref lockTaken); | |||
| } | |||
| try | |||
| { | |||
| if (Connected) | |||
| { | |||
| args.ConnectSocket.FullClose(); | |||
| } | |||
| else | |||
| { | |||
| _activeSocket = args.ConnectSocket; | |||
| if (_disposed) | |||
| { | |||
| _activeSocket.FullClose(); | |||
| } | |||
| var r = new FakeAsyncResult() | |||
| { | |||
| AsyncState = token.AsyncState | |||
| }; | |||
| token.Callback(r); | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| if (lockTaken) | |||
| { | |||
| _socketSyncLock.Exit(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| public void EndConnect(IAsyncResult asyncResult) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| var r = asyncResult as FakeAsyncResult; | |||
| if (r == null) | |||
| { | |||
| throw new ArgumentException("Invalid asyncResult.", nameof(asyncResult)); | |||
| } | |||
| if (r.InternalException != null) | |||
| { | |||
| throw r.InternalException; | |||
| } | |||
| } | |||
| public void Dispose() | |||
| { | |||
| if (_disposed) | |||
| { | |||
| return; | |||
| } | |||
| var lockTaken = false; | |||
| if (!_socketSyncLock.IsHeldByCurrentThread) | |||
| { | |||
| _socketSyncLock.TryEnter(ref lockTaken); | |||
| } | |||
| try | |||
| { | |||
| _disposed = true; | |||
| _activeSocket?.FullClose(); | |||
| } | |||
| finally | |||
| { | |||
| if (lockTaken) | |||
| { | |||
| _socketSyncLock.Exit(); | |||
| } | |||
| } | |||
| } | |||
| public IAsyncResult BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, | |||
| AsyncCallback callback, | |||
| object state) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.NotConnected); | |||
| } | |||
| return _activeSocket.BeginSend(buffer, offset, size, socketFlags, callback, state); | |||
| } | |||
| public int EndSend(IAsyncResult asyncResult) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.NotConnected); | |||
| } | |||
| return _activeSocket.EndSend(asyncResult); | |||
| } | |||
| public IAsyncResult BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, | |||
| AsyncCallback callback, | |||
| object state) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.NotConnected); | |||
| } | |||
| return _activeSocket.BeginReceive(buffer, offset, size, socketFlags, callback, state); | |||
| } | |||
| public int EndReceive(IAsyncResult asyncResult) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.NotConnected); | |||
| } | |||
| return _activeSocket.EndReceive(asyncResult); | |||
| } | |||
| public void Shutdown(SocketShutdown how) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| return; | |||
| } | |||
| _activeSocket.Shutdown(how); | |||
| } | |||
| public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue) | |||
| { | |||
| SetSocketOption(optionLevel, optionName, optionValue ? 1 : 0); | |||
| } | |||
| public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int)SocketError.NotConnected); | |||
| } | |||
| _activeSocket.SetSocketOption(optionLevel, optionName, optionValue); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| private class FakeAsyncResult : IAsyncResult | |||
| { | |||
| public bool IsCompleted { get; } = true; | |||
| public WaitHandle AsyncWaitHandle { get; } = null; | |||
| public object AsyncState { get; set; } | |||
| public bool CompletedSynchronously { get; } = true; | |||
| public Exception InternalException { get; set; } = null; | |||
| } | |||
| private class TcpUserToken | |||
| { | |||
| public AsyncCallback Callback { get; } | |||
| public object AsyncState { get; } | |||
| public TcpUserToken(AsyncCallback callback, object state) | |||
| { | |||
| Callback = callback; | |||
| AsyncState = state; | |||
| } | |||
| } | |||
| private void OnTcpConnectCompleted(object sender, SocketAsyncEventArgs args) | |||
| { | |||
| using (args) | |||
| { | |||
| args.Completed -= OnTcpConnectCompleted; | |||
| var token = (TcpUserToken) args.UserToken; | |||
| if (args.SocketError != SocketError.Success) | |||
| { | |||
| var ex = args.ConnectByNameError ?? new SocketException((int) args.SocketError); | |||
| var r = new FakeAsyncResult() | |||
| { | |||
| AsyncState = token.AsyncState, | |||
| InternalException = ex | |||
| }; | |||
| token.Callback(r); | |||
| } | |||
| else | |||
| { | |||
| var lockTaken = false; | |||
| if (!_socketSyncLock.IsHeldByCurrentThread) | |||
| { | |||
| _socketSyncLock.TryEnter(ref lockTaken); | |||
| } | |||
| try | |||
| { | |||
| if (Connected) | |||
| { | |||
| args.ConnectSocket.FullClose(); | |||
| } | |||
| else | |||
| { | |||
| _activeSocket = args.ConnectSocket; | |||
| if (_disposed) | |||
| { | |||
| _activeSocket.FullClose(); | |||
| } | |||
| var r = new FakeAsyncResult() | |||
| { | |||
| AsyncState = token.AsyncState | |||
| }; | |||
| token.Callback(r); | |||
| } | |||
| } | |||
| finally | |||
| { | |||
| if (lockTaken) | |||
| { | |||
| _socketSyncLock.Exit(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| public void EndConnect(IAsyncResult asyncResult) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| var r = asyncResult as FakeAsyncResult; | |||
| if (r == null) | |||
| { | |||
| throw new ArgumentException("Invalid asyncResult.", nameof(asyncResult)); | |||
| } | |||
| if (r.InternalException != null) | |||
| { | |||
| throw r.InternalException; | |||
| } | |||
| } | |||
| public void Dispose() | |||
| { | |||
| if (_disposed) | |||
| { | |||
| return; | |||
| } | |||
| var lockTaken = false; | |||
| if (!_socketSyncLock.IsHeldByCurrentThread) | |||
| { | |||
| _socketSyncLock.TryEnter(ref lockTaken); | |||
| } | |||
| try | |||
| { | |||
| _disposed = true; | |||
| _activeSocket?.FullClose(); | |||
| } | |||
| finally | |||
| { | |||
| if (lockTaken) | |||
| { | |||
| _socketSyncLock.Exit(); | |||
| } | |||
| } | |||
| } | |||
| public IAsyncResult BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, | |||
| AsyncCallback callback, | |||
| object state) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.NotConnected); | |||
| } | |||
| return _activeSocket.BeginSend(buffer, offset, size, socketFlags, callback, state); | |||
| } | |||
| public int EndSend(IAsyncResult asyncResult) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.NotConnected); | |||
| } | |||
| return _activeSocket.EndSend(asyncResult); | |||
| } | |||
| public IAsyncResult BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, | |||
| AsyncCallback callback, | |||
| object state) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.NotConnected); | |||
| } | |||
| return _activeSocket.BeginReceive(buffer, offset, size, socketFlags, callback, state); | |||
| } | |||
| public int EndReceive(IAsyncResult asyncResult) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int) SocketError.NotConnected); | |||
| } | |||
| return _activeSocket.EndReceive(asyncResult); | |||
| } | |||
| public void Shutdown(SocketShutdown how) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| return; | |||
| } | |||
| _activeSocket.Shutdown(how); | |||
| } | |||
| public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, bool optionValue) | |||
| { | |||
| SetSocketOption(optionLevel, optionName, optionValue ? 1 : 0); | |||
| } | |||
| public void SetSocketOption(SocketOptionLevel optionLevel, SocketOptionName optionName, int optionValue) | |||
| { | |||
| if (_disposed) | |||
| { | |||
| throw new ObjectDisposedException(GetType().FullName); | |||
| } | |||
| if (!Connected) | |||
| { | |||
| throw new SocketException((int)SocketError.NotConnected); | |||
| } | |||
| _activeSocket.SetSocketOption(optionLevel, optionName, optionValue); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,33 +1,33 @@ | |||
| namespace Shadowsocks.View | |||
| { | |||
| partial class ConfigForm | |||
| { | |||
| /// <summary> | |||
| /// Required designer variable. | |||
| /// </summary> | |||
| private System.ComponentModel.IContainer components = null; | |||
| /// <summary> | |||
| /// Clean up any resources being used. | |||
| /// </summary> | |||
| /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||
| protected override void Dispose(bool disposing) | |||
| { | |||
| if (disposing && (components != null)) | |||
| { | |||
| components.Dispose(); | |||
| } | |||
| base.Dispose(disposing); | |||
| } | |||
| #region Windows Form Designer generated code | |||
| /// <summary> | |||
| /// Required method for Designer support - do not modify | |||
| /// the contents of this method with the code editor. | |||
| /// </summary> | |||
| private void InitializeComponent() | |||
| { | |||
| namespace Shadowsocks.View | |||
| { | |||
| partial class ConfigForm | |||
| { | |||
| /// <summary> | |||
| /// Required designer variable. | |||
| /// </summary> | |||
| private System.ComponentModel.IContainer components = null; | |||
| /// <summary> | |||
| /// Clean up any resources being used. | |||
| /// </summary> | |||
| /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||
| protected override void Dispose(bool disposing) | |||
| { | |||
| if (disposing && (components != null)) | |||
| { | |||
| components.Dispose(); | |||
| } | |||
| base.Dispose(disposing); | |||
| } | |||
| #region Windows Form Designer generated code | |||
| /// <summary> | |||
| /// Required method for Designer support - do not modify | |||
| /// the contents of this method with the code editor. | |||
| /// </summary> | |||
| private void InitializeComponent() | |||
| { | |||
| this.components = new System.ComponentModel.Container(); | |||
| this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); | |||
| this.PluginOptionsLabel = new System.Windows.Forms.Label(); | |||
| @@ -672,51 +672,51 @@ | |||
| this.ResumeLayout(false); | |||
| this.PerformLayout(); | |||
| } | |||
| #endregion | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; | |||
| private System.Windows.Forms.Label IPLabel; | |||
| private System.Windows.Forms.Label ServerPortLabel; | |||
| private System.Windows.Forms.Label PasswordLabel; | |||
| private System.Windows.Forms.TextBox IPTextBox; | |||
| private System.Windows.Forms.TextBox ServerPortTextBox; | |||
| private System.Windows.Forms.Label EncryptionLabel; | |||
| private System.Windows.Forms.ComboBox EncryptionSelect; | |||
| private System.Windows.Forms.Panel panel2; | |||
| private System.Windows.Forms.Button OKButton; | |||
| private System.Windows.Forms.Button MyCancelButton; | |||
| private System.Windows.Forms.Button ApplyButton; | |||
| private System.Windows.Forms.Button DeleteButton; | |||
| private System.Windows.Forms.Button AddButton; | |||
| private System.Windows.Forms.GroupBox ServerGroupBox; | |||
| private System.Windows.Forms.ListBox ServersListBox; | |||
| private System.Windows.Forms.TextBox RemarksTextBox; | |||
| private System.Windows.Forms.Label RemarksLabel; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel4; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel5; | |||
| private System.Windows.Forms.TextBox ProxyPortTextBox; | |||
| private System.Windows.Forms.Label ProxyPortLabel; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel6; | |||
| private System.Windows.Forms.Button MoveDownButton; | |||
| private System.Windows.Forms.Button MoveUpButton; | |||
| private System.Windows.Forms.Button DuplicateButton; | |||
| private System.Windows.Forms.Label TimeoutLabel; | |||
| private System.Windows.Forms.TextBox TimeoutTextBox; | |||
| private System.Windows.Forms.Label PluginOptionsLabel; | |||
| private System.Windows.Forms.TextBox PluginTextBox; | |||
| private System.Windows.Forms.Label PluginLabel; | |||
| private System.Windows.Forms.TextBox PluginOptionsTextBox; | |||
| private System.Windows.Forms.CheckBox ShowPasswdCheckBox; | |||
| private System.Windows.Forms.TextBox PasswordTextBox; | |||
| private System.Windows.Forms.TextBox PluginArgumentsTextBox; | |||
| private System.Windows.Forms.Label PluginArgumentsLabel; | |||
| private System.Windows.Forms.ToolTip toolTip1; | |||
| private System.Windows.Forms.CheckBox PortableModeCheckBox; | |||
| private System.Windows.Forms.CheckBox NeedPluginArgCheckBox; | |||
| } | |||
| } | |||
| } | |||
| #endregion | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; | |||
| private System.Windows.Forms.Label IPLabel; | |||
| private System.Windows.Forms.Label ServerPortLabel; | |||
| private System.Windows.Forms.Label PasswordLabel; | |||
| private System.Windows.Forms.TextBox IPTextBox; | |||
| private System.Windows.Forms.TextBox ServerPortTextBox; | |||
| private System.Windows.Forms.Label EncryptionLabel; | |||
| private System.Windows.Forms.ComboBox EncryptionSelect; | |||
| private System.Windows.Forms.Panel panel2; | |||
| private System.Windows.Forms.Button OKButton; | |||
| private System.Windows.Forms.Button MyCancelButton; | |||
| private System.Windows.Forms.Button ApplyButton; | |||
| private System.Windows.Forms.Button DeleteButton; | |||
| private System.Windows.Forms.Button AddButton; | |||
| private System.Windows.Forms.GroupBox ServerGroupBox; | |||
| private System.Windows.Forms.ListBox ServersListBox; | |||
| private System.Windows.Forms.TextBox RemarksTextBox; | |||
| private System.Windows.Forms.Label RemarksLabel; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel4; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel5; | |||
| private System.Windows.Forms.TextBox ProxyPortTextBox; | |||
| private System.Windows.Forms.Label ProxyPortLabel; | |||
| private System.Windows.Forms.TableLayoutPanel tableLayoutPanel6; | |||
| private System.Windows.Forms.Button MoveDownButton; | |||
| private System.Windows.Forms.Button MoveUpButton; | |||
| private System.Windows.Forms.Button DuplicateButton; | |||
| private System.Windows.Forms.Label TimeoutLabel; | |||
| private System.Windows.Forms.TextBox TimeoutTextBox; | |||
| private System.Windows.Forms.Label PluginOptionsLabel; | |||
| private System.Windows.Forms.TextBox PluginTextBox; | |||
| private System.Windows.Forms.Label PluginLabel; | |||
| private System.Windows.Forms.TextBox PluginOptionsTextBox; | |||
| private System.Windows.Forms.CheckBox ShowPasswdCheckBox; | |||
| private System.Windows.Forms.TextBox PasswordTextBox; | |||
| private System.Windows.Forms.TextBox PluginArgumentsTextBox; | |||
| private System.Windows.Forms.Label PluginArgumentsLabel; | |||
| private System.Windows.Forms.ToolTip toolTip1; | |||
| private System.Windows.Forms.CheckBox PortableModeCheckBox; | |||
| private System.Windows.Forms.CheckBox NeedPluginArgCheckBox; | |||
| } | |||
| } | |||
| @@ -346,7 +346,7 @@ namespace Shadowsocks.View | |||
| ServersListBox.Items.Clear(); | |||
| foreach (Server server in configuration.configs) | |||
| { | |||
| ServersListBox.Items.Add(server.FriendlyName()); | |||
| ServersListBox.Items.Add(server.ToString()); | |||
| } | |||
| } | |||
| @@ -421,7 +421,7 @@ namespace Shadowsocks.View | |||
| } | |||
| if (_lastSelectedIndex >= 0 && _lastSelectedIndex < _modifiedConfiguration.configs.Count) | |||
| { | |||
| ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].FriendlyName(); | |||
| ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].ToString(); | |||
| } | |||
| UpdateButtons(); | |||
| LoadSelectedServerDetails(); | |||
| @@ -174,7 +174,7 @@ namespace Shadowsocks.View | |||
| } | |||
| else | |||
| { | |||
| serverInfo = config.GetCurrentServer().FriendlyName(); | |||
| serverInfo = config.GetCurrentServer().ToString(); | |||
| } | |||
| // show more info by hacking the P/Invoke declaration for NOTIFYICONDATA inside Windows Forms | |||
| string text = I18N.GetString("Shadowsocks") + " " + UpdateChecker.Version + "\n" + | |||
| @@ -485,7 +485,7 @@ namespace Shadowsocks.View | |||
| { | |||
| if (Configuration.ChecksServer(server)) | |||
| { | |||
| var name = server.FriendlyName(); | |||
| var name = server.ToString(); | |||
| if (!items.OfType<ToolStripMenuItem>().Any(ts => ts.Text == name)) | |||
| { | |||
| ToolStripMenuItem item = new ToolStripMenuItem(name); | |||
| @@ -1,33 +1,33 @@ | |||
| namespace Shadowsocks.View | |||
| { | |||
| partial class ProxyForm | |||
| { | |||
| /// <summary> | |||
| /// Required designer variable. | |||
| /// </summary> | |||
| private System.ComponentModel.IContainer components = null; | |||
| /// <summary> | |||
| /// Clean up any resources being used. | |||
| /// </summary> | |||
| /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||
| protected override void Dispose(bool disposing) | |||
| { | |||
| if (disposing && (components != null)) | |||
| { | |||
| components.Dispose(); | |||
| } | |||
| base.Dispose(disposing); | |||
| } | |||
| #region Windows Form Designer generated code | |||
| /// <summary> | |||
| /// Required method for Designer support - do not modify | |||
| /// the contents of this method with the code editor. | |||
| /// </summary> | |||
| private void InitializeComponent() | |||
| { | |||
| namespace Shadowsocks.View | |||
| { | |||
| partial class ProxyForm | |||
| { | |||
| /// <summary> | |||
| /// Required designer variable. | |||
| /// </summary> | |||
| private System.ComponentModel.IContainer components = null; | |||
| /// <summary> | |||
| /// Clean up any resources being used. | |||
| /// </summary> | |||
| /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||
| protected override void Dispose(bool disposing) | |||
| { | |||
| if (disposing && (components != null)) | |||
| { | |||
| components.Dispose(); | |||
| } | |||
| base.Dispose(disposing); | |||
| } | |||
| #region Windows Form Designer generated code | |||
| /// <summary> | |||
| /// Required method for Designer support - do not modify | |||
| /// the contents of this method with the code editor. | |||
| /// </summary> | |||
| private void InitializeComponent() | |||
| { | |||
| this.MyCancelButton = new System.Windows.Forms.Button(); | |||
| this.OKButton = new System.Windows.Forms.Button(); | |||
| this.UseProxyCheckBox = new System.Windows.Forms.CheckBox(); | |||
| @@ -307,17 +307,17 @@ | |||
| this.flowLayoutPanel1.ResumeLayout(false); | |||
| this.ResumeLayout(false); | |||
| } | |||
| #endregion | |||
| private System.Windows.Forms.CheckBox UseProxyCheckBox; | |||
| private System.Windows.Forms.Label ProxyAddrLabel; | |||
| private System.Windows.Forms.TextBox ProxyServerTextBox; | |||
| private System.Windows.Forms.Label ProxyPortLabel; | |||
| private System.Windows.Forms.TextBox ProxyPortTextBox; | |||
| private System.Windows.Forms.Button MyCancelButton; | |||
| private System.Windows.Forms.Button OKButton; | |||
| private System.Windows.Forms.Label ProxyTypeLabel; | |||
| } | |||
| #endregion | |||
| private System.Windows.Forms.CheckBox UseProxyCheckBox; | |||
| private System.Windows.Forms.Label ProxyAddrLabel; | |||
| private System.Windows.Forms.TextBox ProxyServerTextBox; | |||
| private System.Windows.Forms.Label ProxyPortLabel; | |||
| private System.Windows.Forms.TextBox ProxyPortTextBox; | |||
| private System.Windows.Forms.Button MyCancelButton; | |||
| private System.Windows.Forms.Button OKButton; | |||
| private System.Windows.Forms.Label ProxyTypeLabel; | |||
| private System.Windows.Forms.ComboBox ProxyTypeComboBox; | |||
| private System.Windows.Forms.TextBox ProxyTimeoutTextBox; | |||
| private System.Windows.Forms.Label ProxyTimeoutLabel; | |||
| @@ -329,5 +329,5 @@ | |||
| private System.Windows.Forms.CheckBox UseAuthCheckBox; | |||
| private System.Windows.Forms.TextBox AuthUserTextBox; | |||
| private System.Windows.Forms.TextBox AuthPwdTextBox; | |||
| } | |||
| } | |||
| } | |||
| @@ -64,14 +64,14 @@ namespace Shadowsocks.View | |||
| private void QRCodeForm_Load(object sender, EventArgs e) | |||
| { | |||
| var servers = Configuration.Load(); | |||
| var serverDatas = servers.configs.Select( | |||
| Configuration config = Configuration.Load(); | |||
| List<KeyValuePair<string, string>> serverDatas = config.configs.Select( | |||
| server => | |||
| new KeyValuePair<string, string>(ShadowsocksController.GetServerURL(server), server.FriendlyName()) | |||
| new KeyValuePair<string, string>(server.GetURL(config.generateLegacyUrl), server.ToString()) | |||
| ).ToList(); | |||
| listBox1.DataSource = serverDatas; | |||
| var selectIndex = serverDatas.FindIndex(serverData => serverData.Key.StartsWith(code)); | |||
| int selectIndex = serverDatas.FindIndex(serverData => serverData.Key.StartsWith(code)); | |||
| if (selectIndex >= 0) listBox1.SetSelected(selectIndex, true); | |||
| } | |||
| @@ -19,7 +19,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution | |||
| CHANGES = CHANGES | |||
| CONTRIBUTING.md = CONTRIBUTING.md | |||
| LICENSE.txt = LICENSE.txt | |||
| OPENSSL-GUIDE = OPENSSL-GUIDE | |||
| README.md = README.md | |||
| EndProjectSection | |||
| EndProject | |||
| @@ -243,7 +243,7 @@ namespace Shadowsocks.Test | |||
| string expected = testCase.Key; | |||
| Server config = testCase.Value; | |||
| var actual = ShadowsocksController.GetServerURL(config); | |||
| var actual = config.GetURL(true); | |||
| Assert.AreEqual(expected, actual); | |||
| } | |||
| } | |||