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