diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f5c970bb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto + +# geosite database +*.dat binary \ No newline at end of file diff --git a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs index b9fda44d..f6d9a3d6 100644 --- a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs +++ b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs @@ -1,534 +1,534 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; using NLog; -using Shadowsocks.Model; -using Shadowsocks.Util; - -namespace Shadowsocks.Controller -{ - using Statistics = Dictionary>; - - public sealed class AvailabilityStatistics : IDisposable - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - public const string DateTimePattern = "yyyy-MM-dd HH:mm:ss"; - private const string StatisticsFilesName = "shadowsocks.availability.json"; - public static string AvailabilityStatisticsFile; - //static constructor to initialize every public static fields before refereced - static AvailabilityStatistics() - { - AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName); - } - - //arguments for ICMP tests - private int Repeat => Config.RepeatTimesNum; - public const int TimeoutMilliseconds = 500; - - //records cache for current server in {_monitorInterval} minutes - private readonly ConcurrentDictionary> _latencyRecords = new ConcurrentDictionary>(); - //speed in KiB/s - private readonly ConcurrentDictionary> _inboundSpeedRecords = new ConcurrentDictionary>(); - private readonly ConcurrentDictionary> _outboundSpeedRecords = new ConcurrentDictionary>(); - private readonly ConcurrentDictionary _inOutBoundRecords = new ConcurrentDictionary(); - - private class InOutBoundRecord - { - private long _inbound; - private long _lastInbound; - private long _outbound; - private long _lastOutbound; - - public void UpdateInbound(long delta) - { - Interlocked.Add(ref _inbound, delta); - } - - public void UpdateOutbound(long delta) - { - Interlocked.Add(ref _outbound, delta); - } - - public void GetDelta(out long inboundDelta, out long outboundDelta) - { - var i = Interlocked.Read(ref _inbound); - var il = Interlocked.Exchange(ref _lastInbound, i); - inboundDelta = i - il; - - - var o = Interlocked.Read(ref _outbound); - var ol = Interlocked.Exchange(ref _lastOutbound, o); - outboundDelta = o - ol; - } - } - - //tasks - private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1); - private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(2); - private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes); - private Timer _perSecondTimer; //analyze and save cached records to RawStatistics and filter records - private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1); - //private Timer _writer; //write RawStatistics to file - //private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1); - - private ShadowsocksController _controller; - private StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration; - - // Static Singleton Initialization - public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics(); - public Statistics RawStatistics { get; private set; } - public Statistics FilteredStatistics { get; private set; } - - private AvailabilityStatistics() - { - RawStatistics = new Statistics(); - } - - internal void UpdateConfiguration(ShadowsocksController controller) - { - _controller = controller; - Reset(); - try - { - if (Config.StatisticsEnabled) - { - LoadRawStatistics(); - if (_perSecondTimer == null) - { - _perSecondTimer = new Timer(OperationsPerSecond, new Counter(), _delayBeforeStart, TimeSpan.FromSeconds(1)); - } - } - else - { - _perSecondTimer?.Dispose(); - } - } - catch (Exception e) - { - logger.LogUsefulException(e); - } - } - - private void OperationsPerSecond(object state) - { - lock(state) - { - var counter = state as Counter; - if (counter.count % _monitorInterval.TotalSeconds == 0) - { - UpdateSpeed(); - } - - if (counter.count % RecordingInterval.TotalSeconds == 0) - { - Run(); - } - - counter.count++; - } - } - - private void UpdateSpeed() - { - foreach (var kv in _inOutBoundRecords) - { - var id = kv.Key; - var record = kv.Value; - - long inboundDelta, outboundDelta; - - record.GetDelta(out inboundDelta, out outboundDelta); - - var inboundSpeed = GetSpeedInKiBPerSecond(inboundDelta, _monitorInterval.TotalSeconds); - var outboundSpeed = GetSpeedInKiBPerSecond(outboundDelta, _monitorInterval.TotalSeconds); - - // not thread safe - var inR = _inboundSpeedRecords.GetOrAdd(id, (k) => new List()); - var outR = _outboundSpeedRecords.GetOrAdd(id, (k) => new List()); - - inR.Add(inboundSpeed); - outR.Add(outboundSpeed); - - logger.Debug( - $"{id}: current/max inbound {inboundSpeed}/{inR.Max()} KiB/s, current/max outbound {outboundSpeed}/{outR.Max()} KiB/s"); - } - } - - private void Reset() - { - _inboundSpeedRecords.Clear(); - _outboundSpeedRecords.Clear(); - _latencyRecords.Clear(); - } - - private void Run() - { - UpdateRecords(); - Reset(); - } - - private void UpdateRecords() - { - var records = new Dictionary(); - UpdateRecordsState state = new UpdateRecordsState(); - int serverCount = _controller.GetCurrentConfiguration().configs.Count; - state.counter = serverCount; - bool isPing = Config.Ping; - for (int i = 0; i < serverCount; i++) - { - try - { - var server = _controller.GetCurrentConfiguration().configs[i]; - var id = server.Identifier(); - List inboundSpeedRecords = null; - List outboundSpeedRecords = null; - List latencyRecords = null; - _inboundSpeedRecords.TryGetValue(id, out inboundSpeedRecords); - _outboundSpeedRecords.TryGetValue(id, out outboundSpeedRecords); - _latencyRecords.TryGetValue(id, out latencyRecords); - StatisticsRecord record = new StatisticsRecord(id, inboundSpeedRecords, outboundSpeedRecords, latencyRecords); - /* duplicate server identifier */ - if (records.ContainsKey(id)) - records[id] = record; - else - records.Add(id, record); - if (isPing) - { - // FIXME: on ping completed, every thing could be asynchrously changed. - // focus on: Config/ RawStatistics - MyPing ping = new MyPing(server, Repeat); - ping.Completed += ping_Completed; - ping.Start(new PingState { state = state, record = record }); - } - else if (!record.IsEmptyData()) - { - AppendRecord(id, record); - } - } - catch (Exception e) - { - logger.Debug(e,"config changed asynchrously, just ignore this server"); - } - } - - if (!isPing) - { - Save(); - FilterRawStatistics(); - } - } - - private void ping_Completed(object sender, MyPing.CompletedEventArgs e) - { - PingState pingState = (PingState)e.UserState; - UpdateRecordsState state = pingState.state; - Server server = e.Server; - StatisticsRecord record = pingState.record; - record.SetResponse(e.RoundtripTime); - if (!record.IsEmptyData()) - { - AppendRecord(server.Identifier(), record); - } - logger.Debug($"Ping {server.FriendlyName()} {e.RoundtripTime.Count} times, {(100 - record.PackageLoss * 100)}% packages loss, min {record.MinResponse} ms, max {record.MaxResponse} ms, avg {record.AverageResponse} ms"); - if (Interlocked.Decrement(ref state.counter) == 0) - { - Save(); - FilterRawStatistics(); - } - } - - private void AppendRecord(string serverIdentifier, StatisticsRecord record) - { - try - { - List records; - lock (RawStatistics) - { - if (!RawStatistics.TryGetValue(serverIdentifier, out records)) - { - records = new List(); - RawStatistics[serverIdentifier] = records; - } - } - records.Add(record); - } - catch (Exception e) - { - logger.LogUsefulException(e); - } - } - - private void Save() - { - logger.Debug($"save statistics to {AvailabilityStatisticsFile}"); - if (RawStatistics.Count == 0) - { - return; - } - try - { - string content; -#if DEBUG - content = JsonConvert.SerializeObject(RawStatistics, Formatting.Indented); -#else - content = JsonConvert.SerializeObject(RawStatistics, Formatting.None); -#endif - File.WriteAllText(AvailabilityStatisticsFile, content); - } - catch (IOException e) - { - logger.LogUsefulException(e); - } - } - - private bool IsValidRecord(StatisticsRecord record) - { - if (Config.ByHourOfDay) - { - if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false; - } - return true; - } - - private void FilterRawStatistics() - { - try - { - logger.Debug("filter raw statistics"); - if (RawStatistics == null) return; - var filteredStatistics = new Statistics(); - - foreach (var serverAndRecords in RawStatistics) - { - var server = serverAndRecords.Key; - var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord); - filteredStatistics[server] = filteredRecords; - } - - FilteredStatistics = filteredStatistics; - } - catch (Exception e) - { - logger.LogUsefulException(e); - } - } - - private void LoadRawStatistics() - { - try - { - var path = AvailabilityStatisticsFile; - logger.Debug($"loading statistics from {path}"); - if (!File.Exists(path)) - { - using (File.Create(path)) - { - //do nothing - } - } - var content = File.ReadAllText(path); - RawStatistics = JsonConvert.DeserializeObject(content) ?? RawStatistics; - } - catch (Exception e) - { - logger.LogUsefulException(e); - Console.WriteLine($"failed to load statistics; use runtime statistics, some data may be lost"); - } - } - - private static int GetSpeedInKiBPerSecond(long bytes, double seconds) - { - var result = (int)(bytes / seconds) / 1024; - return result; - } - - public void Dispose() - { - _perSecondTimer.Dispose(); - } - - public void UpdateLatency(Server server, int latency) - { - _latencyRecords.GetOrAdd(server.Identifier(), (k) => - { - List records = new List(); - records.Add(latency); - return records; - }); - } - - public void UpdateInboundCounter(Server server, long n) - { - _inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) => - { - var r = new InOutBoundRecord(); - r.UpdateInbound(n); - - return r; - }, (k, v) => - { - v.UpdateInbound(n); - return v; - }); - } - - public void UpdateOutboundCounter(Server server, long n) - { - _inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) => - { - var r = new InOutBoundRecord(); - r.UpdateOutbound(n); - - return r; - }, (k, v) => - { - v.UpdateOutbound(n); - return v; - }); - } - - private class Counter - { - public int count = 0; - } - - class UpdateRecordsState - { - public int counter; - } - - class PingState - { - public UpdateRecordsState state; - public StatisticsRecord record; - } - - class MyPing - { - private static Logger logger = LogManager.GetCurrentClassLogger(); - - //arguments for ICMP tests - public const int TimeoutMilliseconds = 500; - - public EventHandler Completed; - private Server server; - - private int repeat; - private IPAddress ip; - private Ping ping; - private List RoundtripTime; - - public MyPing(Server server, int repeat) - { - this.server = server; - this.repeat = repeat; - RoundtripTime = new List(repeat); - ping = new Ping(); - ping.PingCompleted += Ping_PingCompleted; - } - - public void Start(object userstate) - { - if (server.server == "") - { - FireCompleted(new Exception("Invalid Server"), userstate); - return; - } - new Task(() => ICMPTest(0, userstate)).Start(); - } - - private void ICMPTest(int delay, object userstate) - { - try - { - logger.Debug($"Ping {server.FriendlyName()}"); - if (ip == null) - { - ip = Dns.GetHostAddresses(server.server) - .First( - ip => - ip.AddressFamily == AddressFamily.InterNetwork || - ip.AddressFamily == AddressFamily.InterNetworkV6); - } - repeat--; - if (delay > 0) - Thread.Sleep(delay); - ping.SendAsync(ip, TimeoutMilliseconds, userstate); - } - catch (Exception e) - { - logger.Error($"An exception occured while eveluating {server.FriendlyName()}"); - logger.LogUsefulException(e); - FireCompleted(e, userstate); - } - } - - private void Ping_PingCompleted(object sender, PingCompletedEventArgs e) - { - try - { - if (e.Reply.Status == IPStatus.Success) - { - logger.Debug($"Ping {server.FriendlyName()} {e.Reply.RoundtripTime} ms"); - RoundtripTime.Add((int?)e.Reply.RoundtripTime); - } - else - { - logger.Debug($"Ping {server.FriendlyName()} timeout"); - RoundtripTime.Add(null); - } - TestNext(e.UserState); - } - catch (Exception ex) - { - logger.Error($"An exception occured while eveluating {server.FriendlyName()}"); - logger.LogUsefulException(ex); - FireCompleted(ex, e.UserState); - } - } - - private void TestNext(object userstate) - { - if (repeat > 0) - { - //Do ICMPTest in a random frequency - int delay = TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds; - new Task(() => ICMPTest(delay, userstate)).Start(); - } - else - { - FireCompleted(null, userstate); - } - } - - private void FireCompleted(Exception error, object userstate) - { - Completed?.Invoke(this, new CompletedEventArgs - { - Error = error, - Server = server, - RoundtripTime = RoundtripTime, - UserState = userstate - }); - } - - public class CompletedEventArgs : EventArgs - { - public Exception Error; - public Server Server; - public List RoundtripTime; - public object UserState; - } - } - - } -} +using Shadowsocks.Model; +using Shadowsocks.Util; + +namespace Shadowsocks.Controller +{ + using Statistics = Dictionary>; + + public sealed class AvailabilityStatistics : IDisposable + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public const string DateTimePattern = "yyyy-MM-dd HH:mm:ss"; + private const string StatisticsFilesName = "shadowsocks.availability.json"; + public static string AvailabilityStatisticsFile; + //static constructor to initialize every public static fields before refereced + static AvailabilityStatistics() + { + AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName); + } + + //arguments for ICMP tests + private int Repeat => Config.RepeatTimesNum; + public const int TimeoutMilliseconds = 500; + + //records cache for current server in {_monitorInterval} minutes + private readonly ConcurrentDictionary> _latencyRecords = new ConcurrentDictionary>(); + //speed in KiB/s + private readonly ConcurrentDictionary> _inboundSpeedRecords = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _outboundSpeedRecords = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary _inOutBoundRecords = new ConcurrentDictionary(); + + private class InOutBoundRecord + { + private long _inbound; + private long _lastInbound; + private long _outbound; + private long _lastOutbound; + + public void UpdateInbound(long delta) + { + Interlocked.Add(ref _inbound, delta); + } + + public void UpdateOutbound(long delta) + { + Interlocked.Add(ref _outbound, delta); + } + + public void GetDelta(out long inboundDelta, out long outboundDelta) + { + var i = Interlocked.Read(ref _inbound); + var il = Interlocked.Exchange(ref _lastInbound, i); + inboundDelta = i - il; + + + var o = Interlocked.Read(ref _outbound); + var ol = Interlocked.Exchange(ref _lastOutbound, o); + outboundDelta = o - ol; + } + } + + //tasks + private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1); + private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(2); + private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes); + private Timer _perSecondTimer; //analyze and save cached records to RawStatistics and filter records + private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1); + //private Timer _writer; //write RawStatistics to file + //private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1); + + private ShadowsocksController _controller; + private StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration; + + // Static Singleton Initialization + public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics(); + public Statistics RawStatistics { get; private set; } + public Statistics FilteredStatistics { get; private set; } + + private AvailabilityStatistics() + { + RawStatistics = new Statistics(); + } + + internal void UpdateConfiguration(ShadowsocksController controller) + { + _controller = controller; + Reset(); + try + { + if (Config.StatisticsEnabled) + { + LoadRawStatistics(); + if (_perSecondTimer == null) + { + _perSecondTimer = new Timer(OperationsPerSecond, new Counter(), _delayBeforeStart, TimeSpan.FromSeconds(1)); + } + } + else + { + _perSecondTimer?.Dispose(); + } + } + catch (Exception e) + { + logger.LogUsefulException(e); + } + } + + private void OperationsPerSecond(object state) + { + lock(state) + { + var counter = state as Counter; + if (counter.count % _monitorInterval.TotalSeconds == 0) + { + UpdateSpeed(); + } + + if (counter.count % RecordingInterval.TotalSeconds == 0) + { + Run(); + } + + counter.count++; + } + } + + private void UpdateSpeed() + { + foreach (var kv in _inOutBoundRecords) + { + var id = kv.Key; + var record = kv.Value; + + long inboundDelta, outboundDelta; + + record.GetDelta(out inboundDelta, out outboundDelta); + + var inboundSpeed = GetSpeedInKiBPerSecond(inboundDelta, _monitorInterval.TotalSeconds); + var outboundSpeed = GetSpeedInKiBPerSecond(outboundDelta, _monitorInterval.TotalSeconds); + + // not thread safe + var inR = _inboundSpeedRecords.GetOrAdd(id, (k) => new List()); + var outR = _outboundSpeedRecords.GetOrAdd(id, (k) => new List()); + + inR.Add(inboundSpeed); + outR.Add(outboundSpeed); + + logger.Debug( + $"{id}: current/max inbound {inboundSpeed}/{inR.Max()} KiB/s, current/max outbound {outboundSpeed}/{outR.Max()} KiB/s"); + } + } + + private void Reset() + { + _inboundSpeedRecords.Clear(); + _outboundSpeedRecords.Clear(); + _latencyRecords.Clear(); + } + + private void Run() + { + UpdateRecords(); + Reset(); + } + + private void UpdateRecords() + { + var records = new Dictionary(); + UpdateRecordsState state = new UpdateRecordsState(); + int serverCount = _controller.GetCurrentConfiguration().configs.Count; + state.counter = serverCount; + bool isPing = Config.Ping; + for (int i = 0; i < serverCount; i++) + { + try + { + var server = _controller.GetCurrentConfiguration().configs[i]; + var id = server.Identifier(); + List inboundSpeedRecords = null; + List outboundSpeedRecords = null; + List latencyRecords = null; + _inboundSpeedRecords.TryGetValue(id, out inboundSpeedRecords); + _outboundSpeedRecords.TryGetValue(id, out outboundSpeedRecords); + _latencyRecords.TryGetValue(id, out latencyRecords); + StatisticsRecord record = new StatisticsRecord(id, inboundSpeedRecords, outboundSpeedRecords, latencyRecords); + /* duplicate server identifier */ + if (records.ContainsKey(id)) + records[id] = record; + else + records.Add(id, record); + if (isPing) + { + // FIXME: on ping completed, every thing could be asynchrously changed. + // focus on: Config/ RawStatistics + MyPing ping = new MyPing(server, Repeat); + ping.Completed += ping_Completed; + ping.Start(new PingState { state = state, record = record }); + } + else if (!record.IsEmptyData()) + { + AppendRecord(id, record); + } + } + catch (Exception e) + { + logger.Debug("config changed asynchrously, just ignore this server"); + } + } + + if (!isPing) + { + Save(); + FilterRawStatistics(); + } + } + + private void ping_Completed(object sender, MyPing.CompletedEventArgs e) + { + PingState pingState = (PingState)e.UserState; + UpdateRecordsState state = pingState.state; + Server server = e.Server; + StatisticsRecord record = pingState.record; + record.SetResponse(e.RoundtripTime); + if (!record.IsEmptyData()) + { + AppendRecord(server.Identifier(), record); + } + logger.Debug($"Ping {server} {e.RoundtripTime.Count} times, {(100 - record.PackageLoss * 100)}% packages loss, min {record.MinResponse} ms, max {record.MaxResponse} ms, avg {record.AverageResponse} ms"); + if (Interlocked.Decrement(ref state.counter) == 0) + { + Save(); + FilterRawStatistics(); + } + } + + private void AppendRecord(string serverIdentifier, StatisticsRecord record) + { + try + { + List records; + lock (RawStatistics) + { + if (!RawStatistics.TryGetValue(serverIdentifier, out records)) + { + records = new List(); + RawStatistics[serverIdentifier] = records; + } + } + records.Add(record); + } + catch (Exception e) + { + logger.LogUsefulException(e); + } + } + + private void Save() + { + logger.Debug($"save statistics to {AvailabilityStatisticsFile}"); + if (RawStatistics.Count == 0) + { + return; + } + try + { + string content; +#if DEBUG + content = JsonConvert.SerializeObject(RawStatistics, Formatting.Indented); +#else + content = JsonConvert.SerializeObject(RawStatistics, Formatting.None); +#endif + File.WriteAllText(AvailabilityStatisticsFile, content); + } + catch (IOException e) + { + logger.LogUsefulException(e); + } + } + + private bool IsValidRecord(StatisticsRecord record) + { + if (Config.ByHourOfDay) + { + if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false; + } + return true; + } + + private void FilterRawStatistics() + { + try + { + logger.Debug("filter raw statistics"); + if (RawStatistics == null) return; + var filteredStatistics = new Statistics(); + + foreach (var serverAndRecords in RawStatistics) + { + var server = serverAndRecords.Key; + var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord); + filteredStatistics[server] = filteredRecords; + } + + FilteredStatistics = filteredStatistics; + } + catch (Exception e) + { + logger.LogUsefulException(e); + } + } + + private void LoadRawStatistics() + { + try + { + var path = AvailabilityStatisticsFile; + logger.Debug($"loading statistics from {path}"); + if (!File.Exists(path)) + { + using (File.Create(path)) + { + //do nothing + } + } + var content = File.ReadAllText(path); + RawStatistics = JsonConvert.DeserializeObject(content) ?? RawStatistics; + } + catch (Exception e) + { + logger.LogUsefulException(e); + Console.WriteLine($"failed to load statistics; use runtime statistics, some data may be lost"); + } + } + + private static int GetSpeedInKiBPerSecond(long bytes, double seconds) + { + var result = (int)(bytes / seconds) / 1024; + return result; + } + + public void Dispose() + { + _perSecondTimer.Dispose(); + } + + public void UpdateLatency(Server server, int latency) + { + _latencyRecords.GetOrAdd(server.Identifier(), (k) => + { + List records = new List(); + records.Add(latency); + return records; + }); + } + + public void UpdateInboundCounter(Server server, long n) + { + _inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) => + { + var r = new InOutBoundRecord(); + r.UpdateInbound(n); + + return r; + }, (k, v) => + { + v.UpdateInbound(n); + return v; + }); + } + + public void UpdateOutboundCounter(Server server, long n) + { + _inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) => + { + var r = new InOutBoundRecord(); + r.UpdateOutbound(n); + + return r; + }, (k, v) => + { + v.UpdateOutbound(n); + return v; + }); + } + + private class Counter + { + public int count = 0; + } + + class UpdateRecordsState + { + public int counter; + } + + class PingState + { + public UpdateRecordsState state; + public StatisticsRecord record; + } + + class MyPing + { + private static Logger logger = LogManager.GetCurrentClassLogger(); + + //arguments for ICMP tests + public const int TimeoutMilliseconds = 500; + + public EventHandler Completed; + private Server server; + + private int repeat; + private IPAddress ip; + private Ping ping; + private List RoundtripTime; + + public MyPing(Server server, int repeat) + { + this.server = server; + this.repeat = repeat; + RoundtripTime = new List(repeat); + ping = new Ping(); + ping.PingCompleted += Ping_PingCompleted; + } + + public void Start(object userstate) + { + if (server.server == "") + { + FireCompleted(new Exception("Invalid Server"), userstate); + return; + } + new Task(() => ICMPTest(0, userstate)).Start(); + } + + private void ICMPTest(int delay, object userstate) + { + try + { + logger.Debug($"Ping {server}"); + if (ip == null) + { + ip = Dns.GetHostAddresses(server.server) + .First( + ip => + ip.AddressFamily == AddressFamily.InterNetwork || + ip.AddressFamily == AddressFamily.InterNetworkV6); + } + repeat--; + if (delay > 0) + Thread.Sleep(delay); + ping.SendAsync(ip, TimeoutMilliseconds, userstate); + } + catch (Exception e) + { + logger.Error($"An exception occured while eveluating {server}"); + logger.LogUsefulException(e); + FireCompleted(e, userstate); + } + } + + private void Ping_PingCompleted(object sender, PingCompletedEventArgs e) + { + try + { + if (e.Reply.Status == IPStatus.Success) + { + logger.Debug($"Ping {server} {e.Reply.RoundtripTime} ms"); + RoundtripTime.Add((int?)e.Reply.RoundtripTime); + } + else + { + logger.Debug($"Ping {server} timeout"); + RoundtripTime.Add(null); + } + TestNext(e.UserState); + } + catch (Exception ex) + { + logger.Error($"An exception occured while eveluating {server}"); + logger.LogUsefulException(ex); + FireCompleted(ex, e.UserState); + } + } + + private void TestNext(object userstate) + { + if (repeat > 0) + { + //Do ICMPTest in a random frequency + int delay = TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds; + new Task(() => ICMPTest(delay, userstate)).Start(); + } + else + { + FireCompleted(null, userstate); + } + } + + private void FireCompleted(Exception error, object userstate) + { + Completed?.Invoke(this, new CompletedEventArgs + { + Error = error, + Server = server, + RoundtripTime = RoundtripTime, + UserState = userstate + }); + } + + public class CompletedEventArgs : EventArgs + { + public Exception Error; + public Server Server; + public List RoundtripTime; + public object UserState; + } + } + + } +} diff --git a/shadowsocks-csharp/Controller/Service/Sip003Plugin.cs b/shadowsocks-csharp/Controller/Service/Sip003Plugin.cs index e295ae72..33f5acdb 100644 --- a/shadowsocks-csharp/Controller/Service/Sip003Plugin.cs +++ b/shadowsocks-csharp/Controller/Service/Sip003Plugin.cs @@ -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; + } + } + } } \ No newline at end of file diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/shadowsocks-csharp/Controller/Service/TCPRelay.cs index b8bf9d21..9eb00630 100644 --- a/shadowsocks-csharp/Controller/Service/TCPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/TCPRelay.cs @@ -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; diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 1d0ecce5..a4e8e4ae 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -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) diff --git a/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs b/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs index 61356543..d19ea24a 100644 --- a/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs +++ b/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs @@ -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)) diff --git a/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs b/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs index 57f37989..a2d9bfc7 100644 --- a/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs +++ b/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs @@ -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) diff --git a/shadowsocks-csharp/Model/Configuration.cs b/shadowsocks-csharp/Model/Configuration.cs index 0485ff3b..f712653e 100644 --- a/shadowsocks-csharp/Model/Configuration.cs +++ b/shadowsocks-csharp/Model/Configuration.cs @@ -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) diff --git a/shadowsocks-csharp/Model/Server.cs b/shadowsocks-csharp/Model/Server.cs index 6e08f50e..3e8ea2e7 100755 --- a/shadowsocks-csharp/Model/Server.cs +++ b/shadowsocks-csharp/Model/Server.cs @@ -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://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase), - DetailsParser = new Regex(@"^((?.+?):(?.*)@(?.+?):(?\d+?))$", RegexOptions.IgnoreCase); + private static readonly Regex UrlFinder = new Regex(@"ss://(?[A-Za-z0-9+-/=_]+)(?:#(?\S+))?", RegexOptions.IgnoreCase); + private static readonly Regex DetailsParser = new Regex(@"^((?.+?):(?.*)@(?.+?):(?\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 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 servers = new List(); - 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 GetServers(string ssURL) + { + return ssURL + .Split('\r', '\n', ' ') + .Select(u => ParseURL(u)) + .Where(s => s != null) + .ToList(); } public string Identifier() diff --git a/shadowsocks-csharp/Program.cs b/shadowsocks-csharp/Program.cs index 872e2f2a..fac9c845 100755 --- a/shadowsocks-csharp/Program.cs +++ b/shadowsocks-csharp/Program.cs @@ -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 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 diff --git a/shadowsocks-csharp/Proxy/HttpProxy.cs b/shadowsocks-csharp/Proxy/HttpProxy.cs index c1fb63b1..36827c8c 100644 --- a/shadowsocks-csharp/Proxy/HttpProxy.cs +++ b/shadowsocks-csharp/Proxy/HttpProxy.cs @@ -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; + } + } +} diff --git a/shadowsocks-csharp/Util/ProcessManagement/Job.cs b/shadowsocks-csharp/Util/ProcessManagement/Job.cs index bb05dccb..0dadbdbc 100644 --- a/shadowsocks-csharp/Util/ProcessManagement/Job.cs +++ b/shadowsocks-csharp/Util/ProcessManagement/Job.cs @@ -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 +} diff --git a/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs b/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs index 0ef07912..c5308c90 100644 --- a/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs +++ b/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs @@ -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); + } + } +} diff --git a/shadowsocks-csharp/View/ConfigForm.Designer.cs b/shadowsocks-csharp/View/ConfigForm.Designer.cs index bc21915a..3651a33c 100755 --- a/shadowsocks-csharp/View/ConfigForm.Designer.cs +++ b/shadowsocks-csharp/View/ConfigForm.Designer.cs @@ -1,33 +1,33 @@ -namespace Shadowsocks.View -{ - partial class ConfigForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { +namespace Shadowsocks.View +{ + partial class ConfigForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} + diff --git a/shadowsocks-csharp/View/ConfigForm.cs b/shadowsocks-csharp/View/ConfigForm.cs index 6846b5fe..2bfdb25b 100755 --- a/shadowsocks-csharp/View/ConfigForm.cs +++ b/shadowsocks-csharp/View/ConfigForm.cs @@ -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(); diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index 5083cd1d..0107f94c 100644 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -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().Any(ts => ts.Text == name)) { ToolStripMenuItem item = new ToolStripMenuItem(name); diff --git a/shadowsocks-csharp/View/ProxyForm.Designer.cs b/shadowsocks-csharp/View/ProxyForm.Designer.cs index 9730861b..abbf6527 100644 --- a/shadowsocks-csharp/View/ProxyForm.Designer.cs +++ b/shadowsocks-csharp/View/ProxyForm.Designer.cs @@ -1,33 +1,33 @@ -namespace Shadowsocks.View -{ - partial class ProxyForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { +namespace Shadowsocks.View +{ + partial class ProxyForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; - } + } } \ No newline at end of file diff --git a/shadowsocks-csharp/View/QRCodeForm.cs b/shadowsocks-csharp/View/QRCodeForm.cs index 3275f378..d8fc8c60 100755 --- a/shadowsocks-csharp/View/QRCodeForm.cs +++ b/shadowsocks-csharp/View/QRCodeForm.cs @@ -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> serverDatas = config.configs.Select( server => - new KeyValuePair(ShadowsocksController.GetServerURL(server), server.FriendlyName()) + new KeyValuePair(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); } diff --git a/shadowsocks-windows.sln b/shadowsocks-windows.sln index 65f53fa7..3aeb98c8 100644 --- a/shadowsocks-windows.sln +++ b/shadowsocks-windows.sln @@ -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 diff --git a/test/UrlTest.cs b/test/UrlTest.cs index 1080bf93..77f0626c 100644 --- a/test/UrlTest.cs +++ b/test/UrlTest.cs @@ -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); } }