From 60321826fc3aa69ed9fa0c04d179aab23884e41e Mon Sep 17 00:00:00 2001 From: Student Main Date: Wed, 27 May 2020 01:19:31 +0800 Subject: [PATCH 1/8] add gitattributes, run unix2dos to unify line ending --- .gitattributes | 4 + .../Service/AvailabilityStatistics.cs | 1066 ++++++++--------- .../Controller/Service/Sip003Plugin.cs | 330 ++--- shadowsocks-csharp/Program.cs | 198 +-- shadowsocks-csharp/Proxy/HttpProxy.cs | 410 +++---- .../Util/ProcessManagement/Job.cs | 360 +++--- .../Util/Sockets/WrappedSocket.cs | 530 ++++---- .../View/ConfigForm.Designer.cs | 156 +-- shadowsocks-csharp/View/MenuViewController.cs | 26 +- shadowsocks-csharp/View/ProxyForm.Designer.cs | 84 +- 10 files changed, 1584 insertions(+), 1580 deletions(-) create mode 100644 .gitattributes 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 5d11e1d8..5740954b 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("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.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; + } + } + + } +} 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/Program.cs b/shadowsocks-csharp/Program.cs index ddecfc10..3302da27 100755 --- a/shadowsocks-csharp/Program.cs +++ b/shadowsocks-csharp/Program.cs @@ -1,26 +1,26 @@ -using Microsoft.Win32; +using Microsoft.Win32; using NLog; 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.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Pipes; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; namespace Shadowsocks { - internal static class Program + internal static class Program { - private static readonly 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; } @@ -29,15 +29,15 @@ namespace Shadowsocks /// /// [STAThread] - private static void Main(string[] args) + private static void Main(string[] args) { Directory.SetCurrentDirectory(Application.StartupPath); // todo: initialize the NLog configuartion Model.NLogConfig.TouchAndApplyNLogConfig(); // .NET Framework 4.7.2 on Win7 compatibility - ServicePointManager.SecurityProtocol |= - SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; + ServicePointManager.SecurityProtocol |= + SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; // store args for further use Args = args; @@ -59,54 +59,54 @@ namespace Shadowsocks } return; } - string pipename = $"Shadowsocks\\{Application.StartupPath.GetHashCode()}"; + string pipename = $"Shadowsocks\\{Application.StartupPath.GetHashCode()}"; - string addedUrl = null; - - using (NamedPipeClientStream pipe = new NamedPipeClientStream(pipename)) + string addedUrl = null; + + using (NamedPipeClientStream pipe = new NamedPipeClientStream(pipename)) { - bool pipeExist = false; - try - { - pipe.Connect(10); - pipeExist = true; - } - catch (TimeoutException) - { - pipeExist = false; - } - - // TODO: switch to better argv parser when it's getting complicate - List alist = Args.ToList(); - // check --open-url param - int urlidx = alist.IndexOf("--open-url") + 1; - if (urlidx > 0) - { - if (Args.Length <= urlidx) - { - return; - } - - // --open-url exist, and no other instance, add it later - if (!pipeExist) - { - addedUrl = Args[urlidx]; - } - // has other instance, send url via pipe then exit - else - { - byte[] b = Encoding.UTF8.GetBytes(Args[urlidx]); - byte[] opAddUrl = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(1)); - byte[] blen = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(b.Length)); - pipe.Write(opAddUrl, 0, 4); // opcode addurl - pipe.Write(blen, 0, 4); - pipe.Write(b, 0, b.Length); - pipe.Close(); - return; - } - } - // has another instance, and no need to communicate with it return - else if (pipeExist) + bool pipeExist = false; + try + { + pipe.Connect(10); + pipeExist = true; + } + catch (TimeoutException) + { + pipeExist = false; + } + + // TODO: switch to better argv parser when it's getting complicate + List alist = Args.ToList(); + // check --open-url param + int urlidx = alist.IndexOf("--open-url") + 1; + if (urlidx > 0) + { + if (Args.Length <= urlidx) + { + return; + } + + // --open-url exist, and no other instance, add it later + if (!pipeExist) + { + addedUrl = Args[urlidx]; + } + // has other instance, send url via pipe then exit + else + { + byte[] b = Encoding.UTF8.GetBytes(Args[urlidx]); + byte[] opAddUrl = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(1)); + byte[] blen = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(b.Length)); + pipe.Write(opAddUrl, 0, 4); // opcode addurl + pipe.Write(blen, 0, 4); + pipe.Write(b, 0, b.Length); + pipe.Close(); + return; + } + } + // has another instance, and no need to communicate with it return + else if (pipeExist) { Process[] oldProcesses = Process.GetProcessesByName("Shadowsocks"); if (oldProcesses.Length > 0) @@ -119,43 +119,43 @@ namespace Shadowsocks I18N.GetString("Shadowsocks is already running.")); return; } - } - - Utils.ReleaseMemory(true); - - Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); - // handle UI exceptions - Application.ThreadException += Application_ThreadException; - // handle non-UI exceptions - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - Application.ApplicationExit += Application_ApplicationExit; - SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - AutoStartup.RegisterForRestart(true); - - Directory.SetCurrentDirectory(Application.StartupPath); - + } + + Utils.ReleaseMemory(true); + + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); + // handle UI exceptions + Application.ThreadException += Application_ThreadException; + // handle non-UI exceptions + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + Application.ApplicationExit += Application_ApplicationExit; + SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + AutoStartup.RegisterForRestart(true); + + Directory.SetCurrentDirectory(Application.StartupPath); + #if DEBUG - // truncate privoxy log file while debugging - string privoxyLogFilename = Utils.GetTempPath("privoxy.log"); - if (File.Exists(privoxyLogFilename)) - using (new FileStream(privoxyLogFilename, FileMode.Truncate)) { } + // truncate privoxy log file while debugging + string privoxyLogFilename = Utils.GetTempPath("privoxy.log"); + if (File.Exists(privoxyLogFilename)) + using (new FileStream(privoxyLogFilename, FileMode.Truncate)) { } #endif - MainController = new ShadowsocksController(); - MenuController = new MenuViewController(MainController); - - HotKeys.Init(MainController); - MainController.Start(); - - NamedPipeServer namedPipeServer = new NamedPipeServer(); - Task.Run(() => namedPipeServer.Run(pipename)); - namedPipeServer.AddUrlRequested += (_1, e) => MainController.AskAddServerBySSURL(e.Url); - if (!addedUrl.IsNullOrEmpty()) - { - MainController.AskAddServerBySSURL(addedUrl); - } - Application.Run(); + MainController = new ShadowsocksController(); + MenuController = new MenuViewController(MainController); + + HotKeys.Init(MainController); + MainController.Start(); + + NamedPipeServer namedPipeServer = new NamedPipeServer(); + Task.Run(() => namedPipeServer.Run(pipename)); + namedPipeServer.AddUrlRequested += (_1, e) => MainController.AskAddServerBySSURL(e.Url); + if (!addedUrl.IsNullOrEmpty()) + { + MainController.AskAddServerBySSURL(addedUrl); + } + Application.Run(); } private static int exited = 0; @@ -193,7 +193,7 @@ namespace Shadowsocks logger.Info("os wake up"); if (MainController != null) { - 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/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index 34aaf1a0..89fde38a 100644 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -36,7 +36,7 @@ namespace Shadowsocks.View private ContextMenu contextMenu1; private MenuItem disableItem; private MenuItem AutoStartupItem; - private MenuItem ProtocolHandlerItem; + private MenuItem ProtocolHandlerItem; private MenuItem ShareOverLANItem; private MenuItem SeperatorItem; private MenuItem ConfigItem; @@ -316,7 +316,7 @@ namespace Shadowsocks.View this.proxyItem = CreateMenuItem("Forward Proxy...", new EventHandler(this.proxyItem_Click)), new MenuItem("-"), this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), - this.ProtocolHandlerItem = CreateMenuItem("Associate ss:// Links", new EventHandler(this.ProtocolHandlerItem_Click)), + this.ProtocolHandlerItem = CreateMenuItem("Associate ss:// Links", new EventHandler(this.ProtocolHandlerItem_Click)), this.ShareOverLANItem = CreateMenuItem("Allow other Devices to connect", new EventHandler(this.ShareOverLANItem_Click)), new MenuItem("-"), this.hotKeyItem = CreateMenuItem("Edit Hotkeys...", new EventHandler(this.hotKeyItem_Click)), @@ -444,7 +444,7 @@ namespace Shadowsocks.View VerboseLoggingToggleItem.Checked = config.isVerboseLogging; ShowPluginOutputToggleItem.Checked = config.showPluginOutput; AutoStartupItem.Checked = AutoStartup.Check(); - ProtocolHandlerItem.Checked = ProtocolHandler.Check(); + ProtocolHandlerItem.Checked = ProtocolHandler.Check(); onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; localPACItem.Checked = !onlinePACItem.Checked; secureLocalPacUrlToggleItem.Checked = config.secureLocalPac; @@ -847,16 +847,16 @@ namespace Shadowsocks.View { MessageBox.Show(I18N.GetString("Failed to update registry")); } - LoadCurrentConfiguration(); - } - private void ProtocolHandlerItem_Click(object sender, EventArgs e) - { - ProtocolHandlerItem.Checked = !ProtocolHandlerItem.Checked; - if (!ProtocolHandler.Set(ProtocolHandlerItem.Checked)) - { - MessageBox.Show(I18N.GetString("Failed to update registry")); - } - LoadCurrentConfiguration(); + LoadCurrentConfiguration(); + } + private void ProtocolHandlerItem_Click(object sender, EventArgs e) + { + ProtocolHandlerItem.Checked = !ProtocolHandlerItem.Checked; + if (!ProtocolHandler.Set(ProtocolHandlerItem.Checked)) + { + MessageBox.Show(I18N.GetString("Failed to update registry")); + } + LoadCurrentConfiguration(); } private void LocalPACItem_Click(object sender, EventArgs e) 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 From 1d89e9bc6d7b198a884a3a08d6794c345190b958 Mon Sep 17 00:00:00 2001 From: Student Main Date: Fri, 5 Jun 2020 16:03:48 +0800 Subject: [PATCH 2/8] Server.FriendlyName()->Server.ToString() --- .../Controller/Service/AvailabilityStatistics.cs | 12 ++++++------ shadowsocks-csharp/Controller/Service/TCPRelay.cs | 4 ++-- .../Controller/Strategy/HighAvailabilityStrategy.cs | 12 ++++++------ .../Controller/Strategy/StatisticsStrategy.cs | 4 ++-- shadowsocks-csharp/Model/Server.cs | 7 +++---- shadowsocks-csharp/View/ConfigForm.cs | 4 ++-- shadowsocks-csharp/View/MenuViewController.cs | 4 ++-- shadowsocks-csharp/View/QRCodeForm.cs | 2 +- 8 files changed, 24 insertions(+), 25 deletions(-) diff --git a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs index 5740954b..f6d9a3d6 100644 --- a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs +++ b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs @@ -238,7 +238,7 @@ namespace Shadowsocks.Controller { 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"); + 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(); @@ -450,7 +450,7 @@ namespace Shadowsocks.Controller { try { - logger.Debug($"Ping {server.FriendlyName()}"); + logger.Debug($"Ping {server}"); if (ip == null) { ip = Dns.GetHostAddresses(server.server) @@ -466,7 +466,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - logger.Error($"An exception occured while eveluating {server.FriendlyName()}"); + logger.Error($"An exception occured while eveluating {server}"); logger.LogUsefulException(e); FireCompleted(e, userstate); } @@ -478,19 +478,19 @@ namespace Shadowsocks.Controller { if (e.Reply.Status == IPStatus.Success) { - logger.Debug($"Ping {server.FriendlyName()} {e.Reply.RoundtripTime} ms"); + logger.Debug($"Ping {server} {e.Reply.RoundtripTime} ms"); RoundtripTime.Add((int?)e.Reply.RoundtripTime); } else { - logger.Debug($"Ping {server.FriendlyName()} timeout"); + logger.Debug($"Ping {server} timeout"); RoundtripTime.Add(null); } TestNext(e.UserState); } catch (Exception ex) { - logger.Error($"An exception occured while eveluating {server.FriendlyName()}"); + logger.Error($"An exception occured while eveluating {server}"); logger.LogUsefulException(ex); FireCompleted(ex, e.UserState); } diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/shadowsocks-csharp/Controller/Service/TCPRelay.cs index 0653d8dd..f971c5a2 100644 --- a/shadowsocks-csharp/Controller/Service/TCPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/TCPRelay.cs @@ -803,7 +803,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(); } @@ -830,7 +830,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/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/Server.cs b/shadowsocks-csharp/Model/Server.cs index 6e08f50e..f23cfd41 100755 --- a/shadowsocks-csharp/Model/Server.cs +++ b/shadowsocks-csharp/Model/Server.cs @@ -15,9 +15,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,7 +43,7 @@ namespace Shadowsocks.Model return server == o2.server && server_port == o2.server_port; } - public string FriendlyName() + public override string ToString() { if (server.IsNullOrEmpty()) { diff --git a/shadowsocks-csharp/View/ConfigForm.cs b/shadowsocks-csharp/View/ConfigForm.cs index aa337e6e..47335b05 100755 --- a/shadowsocks-csharp/View/ConfigForm.cs +++ b/shadowsocks-csharp/View/ConfigForm.cs @@ -427,7 +427,7 @@ namespace Shadowsocks.View ServersListBox.Items.Clear(); foreach (Server server in configuration.configs) { - ServersListBox.Items.Add(server.FriendlyName()); + ServersListBox.Items.Add(server.ToString()); } } @@ -502,7 +502,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 89fde38a..64923b4b 100644 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -172,7 +172,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" + @@ -478,7 +478,7 @@ namespace Shadowsocks.View { if (Configuration.ChecksServer(server)) { - MenuItem item = new MenuItem(server.FriendlyName()); + MenuItem item = new MenuItem(server.ToString()); item.Tag = configuration.configs.FindIndex(s => s == server); item.Click += AServerItem_Click; items.Add(strategyCount + serverCount, item); diff --git a/shadowsocks-csharp/View/QRCodeForm.cs b/shadowsocks-csharp/View/QRCodeForm.cs index 3275f378..9d178500 100755 --- a/shadowsocks-csharp/View/QRCodeForm.cs +++ b/shadowsocks-csharp/View/QRCodeForm.cs @@ -67,7 +67,7 @@ namespace Shadowsocks.View var servers = Configuration.Load(); var serverDatas = servers.configs.Select( server => - new KeyValuePair(ShadowsocksController.GetServerURL(server), server.FriendlyName()) + new KeyValuePair(ShadowsocksController.GetServerURL(server), server.ToString()) ).ToList(); listBox1.DataSource = serverDatas; From b1d80279525c73813fabdf786ecb0a004e300bdb Mon Sep 17 00:00:00 2001 From: Student Main Date: Fri, 5 Jun 2020 16:04:31 +0800 Subject: [PATCH 3/8] move url generation into Server class --- .../Controller/ShadowsocksController.cs | 43 +------------ shadowsocks-csharp/Model/Server.cs | 62 ++++++++++++++++--- shadowsocks-csharp/View/QRCodeForm.cs | 2 +- test/UrlTest.cs | 2 +- 4 files changed, 57 insertions(+), 52 deletions(-) diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index ea1382d9..182f1bb3 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -371,48 +371,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().URL; } public void UpdatePACFromGeosite() diff --git a/shadowsocks-csharp/Model/Server.cs b/shadowsocks-csharp/Model/Server.cs index f23cfd41..1f5cf828 100755 --- a/shadowsocks-csharp/Model/Server.cs +++ b/shadowsocks-csharp/Model/Server.cs @@ -50,21 +50,67 @@ namespace Shadowsocks.Model 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 URL { - // CheckHostName() won't do a real DNS lookup - switch (Uri.CheckHostName(hostName)) + get { - case UriHostNameType.IPv6: // Add square bracket when IPv6 (RFC3986) - return $"[{hostName}]"; - default: // IPv4 or domain name - return hostName; + string tag = string.Empty; + string url = string.Empty; + + if (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('='); + + string pluginPart = plugin; + if (!string.IsNullOrWhiteSpace(plugin_opts)) + { + pluginPart += ";" + plugin_opts; + } + + url = string.Format( + "{0}@{1}:{2}/?plugin={3}", + websafeBase64, + FormalHostName, + server_port, + HttpUtility.UrlEncode(pluginPart, Encoding.UTF8)); + } + + if (!remarks.IsNullOrEmpty()) + { + tag = $"#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}"; + } + return $"ss://{url}{tag}"; + } + } + + public string FormalHostName + { + get + { + // 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; + } } } diff --git a/shadowsocks-csharp/View/QRCodeForm.cs b/shadowsocks-csharp/View/QRCodeForm.cs index 9d178500..44483a89 100755 --- a/shadowsocks-csharp/View/QRCodeForm.cs +++ b/shadowsocks-csharp/View/QRCodeForm.cs @@ -67,7 +67,7 @@ namespace Shadowsocks.View var servers = Configuration.Load(); var serverDatas = servers.configs.Select( server => - new KeyValuePair(ShadowsocksController.GetServerURL(server), server.ToString()) + new KeyValuePair(server.URL, server.ToString()) ).ToList(); listBox1.DataSource = serverDatas; diff --git a/test/UrlTest.cs b/test/UrlTest.cs index 1080bf93..2b37bc55 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.URL; Assert.AreEqual(expected, actual); } } From f3c4dfb106be2896789279c91e6e654fc02f3871 Mon Sep 17 00:00:00 2001 From: Student Main Date: Fri, 5 Jun 2020 16:04:58 +0800 Subject: [PATCH 4/8] switch legacy url generation by hidden flag in config --- .../Controller/ShadowsocksController.cs | 2 +- shadowsocks-csharp/Model/Configuration.cs | 6 ++ shadowsocks-csharp/Model/Server.cs | 61 ++++++++++--------- shadowsocks-csharp/View/QRCodeForm.cs | 8 +-- test/UrlTest.cs | 2 +- 5 files changed, 44 insertions(+), 35 deletions(-) diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 182f1bb3..e24ae4d2 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -371,7 +371,7 @@ namespace Shadowsocks.Controller public string GetServerURLForCurrentServer() { - return GetCurrentServer().URL; + return GetCurrentServer().GetURL(_config.generateLegacyUrl); } public void UpdatePACFromGeosite() diff --git a/shadowsocks-csharp/Model/Configuration.cs b/shadowsocks-csharp/Model/Configuration.cs index 0485ff3b..aa393c64 100644 --- a/shadowsocks-csharp/Model/Configuration.cs +++ b/shadowsocks-csharp/Model/Configuration.cs @@ -24,11 +24,13 @@ namespace Shadowsocks.Model public bool enabled; public bool shareOverLan; public bool isDefault; + // hidden public bool isIPv6Enabled = false; public int localPort; public bool portableMode = true; public bool showPluginOutput; public string pacUrl; + // geosite config is hidden public string geositeUrl; public string geositeGroup = "geolocation-!cn"; public bool geositeBlacklistMode = true; @@ -39,6 +41,10 @@ namespace Shadowsocks.Model public bool autoCheckUpdate; public bool checkPreRelease; public bool isVerboseLogging; + + // hidden config + public bool generateLegacyUrl = false; + //public NLogConfig.LogLevel logLevel; public LogViewerConfig logViewer; public ProxyConfig proxy; diff --git a/shadowsocks-csharp/Model/Server.cs b/shadowsocks-csharp/Model/Server.cs index 1f5cf828..40909615 100755 --- a/shadowsocks-csharp/Model/Server.cs +++ b/shadowsocks-csharp/Model/Server.cs @@ -56,47 +56,50 @@ namespace Shadowsocks.Model : $"{remarks} ({serverStr})"; } - public string URL + public string GetURL(bool legacyUrl = false) { - get - { - string tag = string.Empty; - string url = string.Empty; + string tag = string.Empty; + string url = string.Empty; - if (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 + 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()) { - // SIP002 - string parts = $"{method}:{password}"; - string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); - string websafeBase64 = base64.Replace('+', '-').Replace('/', '_').TrimEnd('='); string pluginPart = plugin; if (!string.IsNullOrWhiteSpace(plugin_opts)) { pluginPart += ";" + plugin_opts; } - - url = string.Format( - "{0}@{1}:{2}/?plugin={3}", - websafeBase64, - FormalHostName, - server_port, - HttpUtility.UrlEncode(pluginPart, Encoding.UTF8)); + string pluginQuery = "?plugin=" + HttpUtility.UrlEncode(pluginPart, Encoding.UTF8); + url += pluginQuery; } + } - if (!remarks.IsNullOrEmpty()) - { - tag = $"#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}"; - } - return $"ss://{url}{tag}"; + if (!remarks.IsNullOrEmpty()) + { + tag = $"#{HttpUtility.UrlEncode(remarks, Encoding.UTF8)}"; } + return $"ss://{url}{tag}"; } public string FormalHostName diff --git a/shadowsocks-csharp/View/QRCodeForm.cs b/shadowsocks-csharp/View/QRCodeForm.cs index 44483a89..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(server.URL, server.ToString()) + 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/test/UrlTest.cs b/test/UrlTest.cs index 2b37bc55..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 = config.URL; + var actual = config.GetURL(true); Assert.AreEqual(expected, actual); } } From 19633585247bfb8aad3ec71c38c6aa1bf3de1e17 Mon Sep 17 00:00:00 2001 From: Student Main Date: Fri, 5 Jun 2020 16:14:37 +0800 Subject: [PATCH 5/8] add Server.ParseURL, which parse single server url --- shadowsocks-csharp/Model/Server.cs | 125 +++++++++++++++-------------- 1 file changed, 64 insertions(+), 61 deletions(-) diff --git a/shadowsocks-csharp/Model/Server.cs b/shadowsocks-csharp/Model/Server.cs index 40909615..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 { @@ -162,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() From 728e01580cd34b79bbfe639e96f3e50cecc3c65c Mon Sep 17 00:00:00 2001 From: database64128 Date: Thu, 11 Jun 2020 22:13:10 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=94=99=20Backport=202c15276=20from=20?= =?UTF-8?q?v5/master=20to=20master?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Compared to 2c15276: changed socketsHttpHandler to HttpClientHandler due to legacy .NET Framework - Use System.Net.Http.HttpClient for GeositeUpdater - New update check mechanism: first download checksum and compare, only download GeoSite DB on different checksum - Verifiy downloaded GeoSite DB by comparing sha256sum before committing the change --- .../Controller/Service/GeositeUpdater.cs | 105 +++++++++++++++--- .../Controller/ShadowsocksController.cs | 5 - shadowsocks-csharp/View/MenuViewController.cs | 4 +- shadowsocks-csharp/packages.config | 7 ++ shadowsocks-csharp/shadowsocks-csharp.csproj | 36 ++++++ 5 files changed, 133 insertions(+), 24 deletions(-) diff --git a/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs b/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs index 30e29666..c1305f3a 100644 --- a/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs +++ b/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs @@ -9,6 +9,9 @@ using System.Text; using Newtonsoft.Json; using Shadowsocks.Model; using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using System.Security.Cryptography; namespace Shadowsocks.Controller { @@ -32,23 +35,37 @@ namespace Shadowsocks.Controller private static readonly string DATABASE_PATH = Utils.GetTempPath("dlc.dat"); + private static HttpClientHandler httpClientHandler; + private static HttpClient httpClient; private static readonly string GEOSITE_URL = "https://github.com/v2ray/domain-list-community/raw/release/dlc.dat"; + private static readonly string GEOSITE_SHA256SUM_URL = "https://github.com/v2ray/domain-list-community/raw/release/dlc.dat.sha256sum"; + private static byte[] geositeDB; public static readonly Dictionary> Geosites = new Dictionary>(); static GeositeUpdater() { - if (!File.Exists(DATABASE_PATH)) + //socketsHttpHandler = new SocketsHttpHandler(); + //httpClient = new HttpClient(socketsHttpHandler); + + if (File.Exists(DATABASE_PATH) && new FileInfo(DATABASE_PATH).Length > 0) { + geositeDB = File.ReadAllBytes(DATABASE_PATH); + } + else + { + geositeDB = Resources.dlc_dat; File.WriteAllBytes(DATABASE_PATH, Resources.dlc_dat); } LoadGeositeList(); } - static void LoadGeositeList(byte[] data = null) + /// + /// load new GeoSite data from geositeDB + /// + static void LoadGeositeList() { - data = data ?? File.ReadAllBytes(DATABASE_PATH); - var list = GeositeList.Parser.ParseFrom(data); + var list = GeositeList.Parser.ParseFrom(geositeDB); foreach (var item in list.Entries) { Geosites[item.GroupName.ToLower()] = item.Domains; @@ -61,9 +78,12 @@ namespace Shadowsocks.Controller Error = null; } - public static void UpdatePACFromGeosite(Configuration config) + public static async Task UpdatePACFromGeosite() { string geositeUrl = GEOSITE_URL; + string geositeSha256sumUrl = GEOSITE_SHA256SUM_URL; + SHA256 mySHA256 = SHA256.Create(); + var config = Program.MainController.GetCurrentConfiguration(); string group = config.geositeGroup; bool blacklist = config.geositeBlacklistMode; @@ -73,31 +93,82 @@ namespace Shadowsocks.Controller geositeUrl = config.geositeUrl; } logger.Info($"Checking Geosite from {geositeUrl}"); - WebClient http = new WebClient(); + + // use System.Net.Http.HttpClient to download GeoSite db. + // NASTY workaround: new HttpClient every update + // because we can't change proxy on existing socketsHttpHandler instance + httpClientHandler = new HttpClientHandler(); + httpClient = new HttpClient(httpClientHandler); if (config.enabled) { - http.Proxy = new WebProxy( + httpClientHandler.Proxy = new WebProxy( config.isIPv6Enabled ? $"[{IPAddress.IPv6Loopback}]" : IPAddress.Loopback.ToString(), config.localPort); } - http.DownloadDataCompleted += (o, e) => + + try { - try + // download checksum first + var geositeSha256sum = await httpClient.GetStringAsync(geositeSha256sumUrl); + geositeSha256sum = geositeSha256sum.Substring(0, 64).ToUpper(); + logger.Info($"Got Sha256sum: {geositeSha256sum}"); + // compare downloaded checksum with local geositeDB + byte[] localDBHashBytes = mySHA256.ComputeHash(geositeDB); + string localDBHash = BitConverter.ToString(localDBHashBytes).Replace("-", String.Empty); + logger.Info($"Local Sha256sum: {localDBHash}"); + // if already latest + if (geositeSha256sum == localDBHash) + { + logger.Info("Local GeoSite DB is already the latest."); + return; + } + + // not latest. download new DB + var downloadedBytes = await httpClient.GetByteArrayAsync(geositeUrl); + + // verify sha256sum + byte[] downloadedDBHashBytes = mySHA256.ComputeHash(downloadedBytes); + string downloadedDBHash = BitConverter.ToString(downloadedDBHashBytes).Replace("-", String.Empty); + logger.Info($"Actual Sha256sum: {downloadedDBHash}"); + if (geositeSha256sum != downloadedDBHash) { - File.WriteAllBytes(DATABASE_PATH, e.Result); - LoadGeositeList(); + logger.Info("Sha256sum mismatch. Updating aborted."); + throw new Exception("Sha256sum mismatch"); + } + else + { + logger.Info("Sha256sum verification successful."); + } - bool pacFileChanged = MergeAndWritePACFile(group, blacklist); - UpdateCompleted?.Invoke(null, new GeositeResultEventArgs(pacFileChanged)); + // write to geosite file + using (FileStream geositeFileStream = File.Create(DATABASE_PATH)) + await geositeFileStream.WriteAsync(downloadedBytes, 0, downloadedBytes.Length); + + // update stuff + geositeDB = downloadedBytes; + LoadGeositeList(); + bool pacFileChanged = MergeAndWritePACFile(group, blacklist); + UpdateCompleted?.Invoke(null, new GeositeResultEventArgs(pacFileChanged)); + } + catch (Exception ex) + { + Error?.Invoke(null, new ErrorEventArgs(ex)); + } + finally + { + if (httpClientHandler != null) + { + httpClientHandler.Dispose(); + httpClientHandler = null; } - catch (Exception ex) + if (httpClient != null) { - Error?.Invoke(null, new ErrorEventArgs(ex)); + httpClient.Dispose(); + httpClient = null; } - }; - http.DownloadDataAsync(new Uri(geositeUrl)); + } } public static bool MergeAndWritePACFile(string group, bool blacklist) diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index ea1382d9..0f67fe21 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -415,11 +415,6 @@ namespace Shadowsocks.Controller return $"ss://{url}{tag}"; } - public void UpdatePACFromGeosite() - { - GeositeUpdater.UpdatePACFromGeosite(_config); - } - public void UpdateStatisticsConfiguration(bool enabled) { if (availabilityStatistics != null) diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index 89fde38a..385cbe7a 100644 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -685,9 +685,9 @@ namespace Shadowsocks.View controller.TouchPACFile(); } - private void UpdatePACFromGeositeItem_Click(object sender, EventArgs e) + private async void UpdatePACFromGeositeItem_Click(object sender, EventArgs e) { - controller.UpdatePACFromGeosite(); + await GeositeUpdater.UpdatePACFromGeosite(); } private void EditUserRuleFileForGeositeItem_Click(object sender, EventArgs e) diff --git a/shadowsocks-csharp/packages.config b/shadowsocks-csharp/packages.config index 65e34eb9..fbabcf90 100644 --- a/shadowsocks-csharp/packages.config +++ b/shadowsocks-csharp/packages.config @@ -9,8 +9,15 @@ + + + + + + + \ No newline at end of file diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 97a265bd..64a3fc77 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -93,24 +93,60 @@ ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll + + + ..\packages\System.IO.4.3.0\lib\net462\System.IO.dll + True + True + ..\packages\System.Memory.4.5.2\lib\netstandard2.0\System.Memory.dll + + ..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll + True + True + ..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll + + ..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll + True + True + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll + True + True + + + ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + True + True + + + ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + True + True + + + ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + True + True + From 893ff061baae570a91806a65052c4cf087e9a085 Mon Sep 17 00:00:00 2001 From: database64128 Date: Fri, 19 Jun 2020 19:49:00 +0800 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=92=AC=20Refine=20logging=20messages?= =?UTF-8?q?=20as=20suggested=20by=20reviewer=20@celeron533.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shadowsocks-csharp/Controller/Service/GeositeUpdater.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs b/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs index c1305f3a..b06b5167 100644 --- a/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs +++ b/shadowsocks-csharp/Controller/Service/GeositeUpdater.cs @@ -121,7 +121,7 @@ namespace Shadowsocks.Controller // if already latest if (geositeSha256sum == localDBHash) { - logger.Info("Local GeoSite DB is already the latest."); + logger.Info("Local GeoSite DB is up to date."); return; } @@ -134,12 +134,12 @@ namespace Shadowsocks.Controller logger.Info($"Actual Sha256sum: {downloadedDBHash}"); if (geositeSha256sum != downloadedDBHash) { - logger.Info("Sha256sum mismatch. Updating aborted."); + logger.Info("Sha256sum Verification: FAILED. Downloaded GeoSite DB is corrupted. Aborting the update."); throw new Exception("Sha256sum mismatch"); } else { - logger.Info("Sha256sum verification successful."); + logger.Info("Sha256sum Verification: PASSED. Applying to local GeoSite DB."); } // write to geosite file From f6fcab40ad690e1738c2105ab33c95982d4dca53 Mon Sep 17 00:00:00 2001 From: Student Main Date: Fri, 19 Jun 2020 00:42:43 +0800 Subject: [PATCH 8/8] cleaning in Configuration.cs --- shadowsocks-csharp/Model/Configuration.cs | 36 +++++++++-------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/shadowsocks-csharp/Model/Configuration.cs b/shadowsocks-csharp/Model/Configuration.cs index aa393c64..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,16 +24,10 @@ namespace Shadowsocks.Model public bool enabled; public bool shareOverLan; public bool isDefault; - // hidden - public bool isIPv6Enabled = false; public int localPort; public bool portableMode = true; public bool showPluginOutput; public string pacUrl; - // geosite config is hidden - public string geositeUrl; - public string geositeGroup = "geolocation-!cn"; - public bool geositeBlacklistMode = true; public bool useOnlinePac; public bool secureLocalPac = true; @@ -42,8 +36,13 @@ namespace Shadowsocks.Model public bool checkPreRelease; public bool isVerboseLogging; - // hidden config - public bool generateLegacyUrl = false; + // 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; @@ -54,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 @@ -200,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) @@ -218,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()); @@ -237,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)