diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/shadowsocks-csharp/Controller/Service/TCPRelay.cs index c15eb198..0653d8dd 100644 --- a/shadowsocks-csharp/Controller/Service/TCPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/TCPRelay.cs @@ -1,10 +1,12 @@ -using NLog; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Timers; + +using NLog; + using Shadowsocks.Controller.Strategy; using Shadowsocks.Encryption; using Shadowsocks.Encryption.AEAD; @@ -12,16 +14,22 @@ using Shadowsocks.Encryption.Exception; using Shadowsocks.Model; using Shadowsocks.Proxy; using Shadowsocks.Util.Sockets; + using static Shadowsocks.Encryption.EncryptorBase; namespace Shadowsocks.Controller { - class TCPRelay : Listener.Service + internal class TCPRelay : Listener.Service { - private static Logger logger = LogManager.GetCurrentClassLogger(); - private ShadowsocksController _controller; + public event EventHandler OnConnected; + public event EventHandler OnInbound; + public event EventHandler OnOutbound; + public event EventHandler OnFailed; + + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + private readonly ShadowsocksController _controller; private DateTime _lastSweepTime; - private Configuration _config; + private readonly Configuration _config; public ISet Handlers { get; set; } @@ -37,9 +45,24 @@ namespace Shadowsocks.Controller { if (socket.ProtocolType != ProtocolType.Tcp || (length < 2 || firstPacket[0] != 5)) + { return false; + } + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - TCPHandler handler = new TCPHandler(_controller, _config, this, socket); + TCPHandler handler = new TCPHandler(_controller, _config, socket); + + handler.OnConnected += OnConnected; + handler.OnInbound += OnInbound; + handler.OnOutbound += OnOutbound; + handler.OnFailed += OnFailed; + handler.OnClosed += (h, arg) => + { + lock (Handlers) + { + Handlers.Remove(handler); + } + }; IList handlersToClose = new List(); lock (Handlers) @@ -50,8 +73,12 @@ namespace Shadowsocks.Controller { _lastSweepTime = now; foreach (TCPHandler handler1 in Handlers) + { if (now - handler1.lastActivity > TimeSpan.FromSeconds(900)) + { handlersToClose.Add(handler1); + } + } } } foreach (TCPHandler handler1 in handlersToClose) @@ -80,26 +107,46 @@ namespace Shadowsocks.Controller } handlersToClose.ForEach(h => h.Close()); } + } - public void UpdateInboundCounter(Server server, long n) + public class SSRelayEventArgs : EventArgs + { + public readonly Server server; + + public SSRelayEventArgs(Server server) { - _controller.UpdateInboundCounter(server, n); + this.server = server; } + } - public void UpdateOutboundCounter(Server server, long n) + public class SSTransmitEventArgs : SSRelayEventArgs + { + public readonly long length; + public SSTransmitEventArgs(Server server, long length) : base(server) { - _controller.UpdateOutboundCounter(server, n); + this.length = length; } + } + + public class SSTCPConnectedEventArgs : SSRelayEventArgs + { + public readonly TimeSpan latency; - public void UpdateLatency(Server server, TimeSpan latency) + public SSTCPConnectedEventArgs(Server server, TimeSpan latency) : base(server) { - _controller.UpdateLatency(server, latency); + this.latency = latency; } } internal class TCPHandler { - class AsyncSession + public event EventHandler OnConnected; + public event EventHandler OnInbound; + public event EventHandler OnOutbound; + public event EventHandler OnClosed; + public event EventHandler OnFailed; + + private class AsyncSession { public IProxy Remote { get; } @@ -109,7 +156,7 @@ namespace Shadowsocks.Controller } } - class AsyncSession : AsyncSession + private class AsyncSession : AsyncSession { public T State { get; set; } @@ -124,7 +171,7 @@ namespace Shadowsocks.Controller } } - private static Logger Logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly int _serverTimeout; private readonly int _proxyTimeout; @@ -143,10 +190,9 @@ namespace Shadowsocks.Controller public DateTime lastActivity; - private ShadowsocksController _controller; - private Configuration _config; - private TCPRelay _tcprelay; - private Socket _connection; + private readonly ShadowsocksController _controller; + private readonly ProxyConfig _config; + private readonly Socket _connection; private IEncryptor _encryptor; private Server _server; @@ -170,16 +216,16 @@ namespace Shadowsocks.Controller private int _totalWrite = 0; // remote -> local proxy (ciphertext, before decrypt) - private byte[] _remoteRecvBuffer = new byte[BufferSize]; + private readonly byte[] _remoteRecvBuffer = new byte[BufferSize]; // client -> local proxy (plaintext, before encrypt) - private byte[] _connetionRecvBuffer = new byte[BufferSize]; + private readonly byte[] _connetionRecvBuffer = new byte[BufferSize]; // local proxy -> remote (plaintext, after decrypt) - private byte[] _remoteSendBuffer = new byte[BufferSize]; + private readonly byte[] _remoteSendBuffer = new byte[BufferSize]; // local proxy -> client (ciphertext, before decrypt) - private byte[] _connetionSendBuffer = new byte[BufferSize]; + private readonly byte[] _connetionSendBuffer = new byte[BufferSize]; private bool _connectionShutdown = false; private bool _remoteShutdown = false; @@ -197,11 +243,11 @@ namespace Shadowsocks.Controller private EndPoint _destEndPoint = null; - public TCPHandler(ShadowsocksController controller, Configuration config, TCPRelay tcprelay, Socket socket) + // TODO: decouple controller + public TCPHandler(ShadowsocksController controller, Configuration config, Socket socket) { _controller = controller; - _config = config; - _tcprelay = tcprelay; + _config = config.proxy; _connection = socket; _proxyTimeout = config.proxy.proxyTimeout * 1000; _serverTimeout = config.GetCurrentServer().timeout * 1000; @@ -214,11 +260,13 @@ namespace Shadowsocks.Controller Server server = _controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)_connection.RemoteEndPoint, _destEndPoint); if (server == null || server.server == "") + { throw new ArgumentException("No server configured"); + } _encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); - this._server = server; + _server = server; /* prepare address buffer length for AEAD */ Logger.Trace($"_addrBufLength={_addrBufLength}"); @@ -235,20 +283,31 @@ namespace Shadowsocks.Controller private void CheckClose() { if (_connectionShutdown && _remoteShutdown) + { Close(); + } + } + + private void ErrorClose(Exception e) + { + Logger.LogUsefulException(e); + Close(); } public void Close() { lock (_closeConnLock) { - if (_closed) return; + if (_closed) + { + return; + } + _closed = true; } - lock (_tcprelay.Handlers) - { - _tcprelay.Handlers.Remove(this); - } + + OnClosed?.Invoke(this, new SSRelayEventArgs(_server)); + try { _connection.Shutdown(SocketShutdown.Both); @@ -263,7 +322,7 @@ namespace Shadowsocks.Controller { try { - var remote = _currentRemoteSession.Remote; + IProxy remote = _currentRemoteSession.Remote; remote.Shutdown(SocketShutdown.Both); remote.Close(); } @@ -284,7 +343,11 @@ namespace Shadowsocks.Controller private void HandshakeReceive() { - if (_closed) return; + if (_closed) + { + return; + } + try { int bytesRead = _firstPacketLength; @@ -301,18 +364,23 @@ namespace Shadowsocks.Controller HandshakeSendCallback, null); } else + { Close(); + } } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } private void HandshakeSendCallback(IAsyncResult ar) { - if (_closed) return; + if (_closed) + { + return; + } + try { _connection.EndSend(ar); @@ -324,27 +392,30 @@ namespace Shadowsocks.Controller // +-----+-----+-------+------+----------+----------+ // Skip first 3 bytes, and read 2 more bytes to analysis the address. // 2 more bytes is designed if address is domain then we don't need to read once more to get the addr length. - // TODO validate + // validate is unnecessary, we did it in first packet, but we can do it in future version _connection.BeginReceive(_connetionRecvBuffer, 0, 3 + ADDR_ATYP_LEN + 1, SocketFlags.None, - HandshakeReceive2Callback, null); + AddressReceiveCallback, null); } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } - private void HandshakeReceive2Callback(IAsyncResult ar) + private void AddressReceiveCallback(IAsyncResult ar) { - if (_closed) return; + if (_closed) + { + return; + } + try { int bytesRead = _connection.EndReceive(ar); if (bytesRead >= 5) { _command = _connetionRecvBuffer[1]; - switch(_command) + switch (_command) { case CMD_CONNECT: @@ -355,7 +426,7 @@ namespace Shadowsocks.Controller // +----+-----+-------+------+----------+----------+ byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }; _connection.BeginSend(response, 0, response.Length, SocketFlags.None, - ResponseCallback, null); + ConnectResponseCallback, null); break; case CMD_UDP_ASSOC: ReadAddress(HandleUDPAssociate); @@ -376,12 +447,11 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } - private void ResponseCallback(IAsyncResult ar) + private void ConnectResponseCallback(IAsyncResult ar) { try { @@ -391,8 +461,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } @@ -431,15 +500,19 @@ namespace Shadowsocks.Controller private void OnAddressFullyRead(IAsyncResult ar) { - if (_closed) return; + if (_closed) + { + return; + } + try { int bytesRead = _connection.EndReceive(ar); - var states = (object[])ar.AsyncState; + object[] states = (object[])ar.AsyncState; int bytesRemain = (int)states[0]; - var onSuccess = (Action)states[1]; + Action onSuccess = (Action)states[1]; if (bytesRead >= bytesRemain) { @@ -472,7 +545,7 @@ namespace Shadowsocks.Controller break; } - Logger.Debug($"connect to {dstAddr}:{dstPort}"); + Logger.Debug($"connect to {dstAddr}:{dstPort}"); _destEndPoint = SocketUtil.GetEndPoint(dstAddr, dstPort); @@ -486,8 +559,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } @@ -515,7 +587,11 @@ namespace Shadowsocks.Controller private void ReadAll(IAsyncResult ar) { - if (_closed) return; + if (_closed) + { + return; + } + try { if (ar.AsyncState != null) @@ -533,13 +609,14 @@ namespace Shadowsocks.Controller ReadAll, null); } else + { Close(); + } } } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } @@ -584,9 +661,9 @@ namespace Shadowsocks.Controller serverEP = pluginEP; remote = new DirectConnect(); } - else if (_config.proxy.useProxy) + else if (_config.useProxy) { - switch (_config.proxy.proxyType) + switch (_config.proxyType) { case ProxyConfig.PROXY_SOCKS5: remote = new Socks5Proxy(); @@ -597,14 +674,14 @@ namespace Shadowsocks.Controller default: throw new NotSupportedException("Unknown forward proxy."); } - proxyEP = SocketUtil.GetEndPoint(_config.proxy.proxyServer, _config.proxy.proxyPort); + proxyEP = SocketUtil.GetEndPoint(_config.proxyServer, _config.proxyPort); } else { remote = new DirectConnect(); } - var session = new AsyncSession(remote); + AsyncSession session = new AsyncSession(remote); lock (_closeConnLock) { if (_closed) @@ -632,14 +709,13 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } private void ProxyConnectTimer_Elapsed(object sender, ElapsedEventArgs e) { - var timer = (ProxyTimer)sender; + ProxyTimer timer = (ProxyTimer)sender; timer.Elapsed -= ProxyConnectTimer_Elapsed; timer.Enabled = false; timer.Dispose(); @@ -649,7 +725,7 @@ namespace Shadowsocks.Controller { return; } - var proxy = timer.Session.Remote; + IProxy proxy = timer.Session.Remote; Logger.Info($"Proxy {proxy.ProxyEndPoint} timed out"); proxy.Close(); @@ -664,15 +740,15 @@ namespace Shadowsocks.Controller } try { - var session = (AsyncSession)ar.AsyncState; + AsyncSession session = (AsyncSession)ar.AsyncState; ProxyTimer timer = session.State; - var destEndPoint = timer.DestEndPoint; - var server = timer.Server; + EndPoint destEndPoint = timer.DestEndPoint; + Server server = timer.Server; timer.Elapsed -= ProxyConnectTimer_Elapsed; timer.Enabled = false; timer.Dispose(); - var remote = session.Remote; + IProxy remote = session.Remote; // Complete the connection. remote.EndConnectProxy(ar); @@ -694,9 +770,9 @@ namespace Shadowsocks.Controller _destConnected = false; NetworkCredential auth = null; - if (_config.proxy.useAuth) + if (_config.useAuth) { - auth = new NetworkCredential(_config.proxy.authUser, _config.proxy.authPwd); + auth = new NetworkCredential(_config.authUser, _config.authPwd); } // Connect to the remote endpoint. @@ -708,14 +784,13 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } private void DestConnectTimer_Elapsed(object sender, ElapsedEventArgs e) { - var timer = (ServerTimer)sender; + ServerTimer timer = (ServerTimer)sender; timer.Elapsed -= DestConnectTimer_Elapsed; timer.Enabled = false; timer.Dispose(); @@ -725,10 +800,9 @@ namespace Shadowsocks.Controller return; } - var session = timer.Session; + AsyncSession session = timer.Session; Server server = timer.Server; - IStrategy strategy = _controller.GetCurrentStrategy(); - strategy?.SetFailure(server); + OnFailed?.Invoke(this, new SSRelayEventArgs(_server)); Logger.Info($"{server.FriendlyName()} timed out"); session.Remote.Close(); Close(); @@ -736,17 +810,21 @@ namespace Shadowsocks.Controller private void ConnectCallback(IAsyncResult ar) { - if (_closed) return; + if (_closed) + { + return; + } + try { - var session = (AsyncSession)ar.AsyncState; + AsyncSession session = (AsyncSession)ar.AsyncState; ServerTimer timer = session.State; _server = timer.Server; timer.Elapsed -= DestConnectTimer_Elapsed; timer.Enabled = false; timer.Dispose(); - var remote = session.Remote; + IProxy remote = session.Remote; // Complete the connection. remote.EndConnectDest(ar); @@ -754,10 +832,9 @@ namespace Shadowsocks.Controller Logger.Debug($"Socket connected to ss server: {_server.FriendlyName()}"); - var latency = DateTime.Now - _startConnectTime; - IStrategy strategy = _controller.GetCurrentStrategy(); - strategy?.UpdateLatency(_server, latency); - _tcprelay.UpdateLatency(_server, latency); + TimeSpan latency = DateTime.Now - _startConnectTime; + + OnConnected?.Invoke(this, new SSTCPConnectedEventArgs(_server, latency)); StartPipe(session); } @@ -768,11 +845,9 @@ namespace Shadowsocks.Controller { if (_server != null) { - IStrategy strategy = _controller.GetCurrentStrategy(); - strategy?.SetFailure(_server); + OnFailed?.Invoke(this, new SSRelayEventArgs(_server)); } - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } @@ -781,7 +856,7 @@ namespace Shadowsocks.Controller int available = Math.Min(_connection.Available, RecvSize - _firstPacketLength); if (available > 0) { - var size = _connection.Receive(_connetionRecvBuffer, _firstPacketLength, available, + int size = _connection.Receive(_connetionRecvBuffer, _firstPacketLength, available, SocketFlags.None); _firstPacketLength += size; @@ -790,7 +865,11 @@ namespace Shadowsocks.Controller private void StartPipe(AsyncSession session) { - if (_closed) return; + if (_closed) + { + return; + } + try { _startReceivingTime = DateTime.Now; @@ -803,20 +882,24 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } private void PipeRemoteReceiveCallback(IAsyncResult ar) { - if (_closed) return; + if (_closed) + { + return; + } + try { - var session = (AsyncSession)ar.AsyncState; + AsyncSession session = (AsyncSession)ar.AsyncState; int bytesRead = session.Remote.EndReceive(ar); _totalRead += bytesRead; - _tcprelay.UpdateInboundCounter(_server, bytesRead); + + OnInbound?.Invoke(this, new SSTransmitEventArgs(_server, bytesRead)); if (bytesRead > 0) { lastActivity = DateTime.Now; @@ -845,8 +928,6 @@ namespace Shadowsocks.Controller Logger.Trace($"start sending {bytesToSend}"); _connection.BeginSend(_remoteSendBuffer, 0, bytesToSend, SocketFlags.None, PipeConnectionSendCallback, new object[] { session, bytesToSend }); - IStrategy strategy = _controller.GetCurrentStrategy(); - strategy?.UpdateLastRead(_server); } else { @@ -857,20 +938,23 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } private void PipeConnectionReceiveCallback(IAsyncResult ar) { - if (_closed) return; + if (_closed) + { + return; + } + try { int bytesRead = _connection.EndReceive(ar); - var session = (AsyncSession)ar.AsyncState; - var remote = session.Remote; + AsyncSession session = (AsyncSession)ar.AsyncState; + IProxy remote = session.Remote; if (bytesRead > 0) { @@ -885,8 +969,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } @@ -907,22 +990,25 @@ namespace Shadowsocks.Controller return; } } - _tcprelay.UpdateOutboundCounter(_server, bytesToSend); + + OnOutbound?.Invoke(this, new SSTransmitEventArgs(_server, bytesToSend)); _startSendingTime = DateTime.Now; session.Remote.BeginSend(_connetionSendBuffer, 0, bytesToSend, SocketFlags.None, PipeRemoteSendCallback, new object[] { session, bytesToSend }); - IStrategy strategy = _controller.GetCurrentStrategy(); - strategy?.UpdateLastWrite(_server); } private void PipeRemoteSendCallback(IAsyncResult ar) { - if (_closed) return; + if (_closed) + { + return; + } + try { - var container = (object[])ar.AsyncState; - var session = (AsyncSession)container[0]; - var bytesShouldSend = (int)container[1]; + object[] container = (object[])ar.AsyncState; + AsyncSession session = (AsyncSession)container[0]; + int bytesShouldSend = (int)container[1]; int bytesSent = session.Remote.EndSend(ar); if (bytesSent > 0) @@ -944,8 +1030,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } @@ -954,11 +1039,11 @@ namespace Shadowsocks.Controller { try { - var container = (object[])ar.AsyncState; - var session = (AsyncSession)container[0]; - var bytesShouldSend = (int)container[1]; - var bytesSent = _connection.EndSend(ar); - var bytesRemaining = bytesShouldSend - bytesSent; + object[] container = (object[])ar.AsyncState; + AsyncSession session = (AsyncSession)container[0]; + int bytesShouldSend = (int)container[1]; + int bytesSent = _connection.EndSend(ar); + int bytesRemaining = bytesShouldSend - bytesSent; if (bytesRemaining > 0) { Logger.Info("reconstruct _remoteSendBuffer to re-send"); @@ -972,8 +1057,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Logger.LogUsefulException(e); - Close(); + ErrorClose(e); } } } diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index e1df6186..5b293e54 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -218,25 +218,25 @@ namespace Shadowsocks.Controller StatisticsStrategyConfiguration.Save(configuration); } - public bool AskAddServerBySSURL(string ssURL) - { - var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo); - if (dr == DialogResult.Yes) - { + public bool AskAddServerBySSURL(string ssURL) + { + var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo); + if (dr == DialogResult.Yes) + { if (AddServerBySSURL(ssURL)) { MessageBox.Show(I18N.GetString("Successfully imported from {0}", ssURL)); return true; - } + } else { MessageBox.Show(I18N.GetString("Failed to import. Please check if the link is valid.")); - } - } - return false; - } - - + } + } + return false; + } + + public bool AddServerBySSURL(string ssURL) { try @@ -490,29 +490,32 @@ namespace Shadowsocks.Controller ConfigChanged?.Invoke(this, new EventArgs()); } - public void UpdateLatency(Server server, TimeSpan latency) + public void UpdateLatency(object sender, SSTCPConnectedEventArgs args) { + GetCurrentStrategy()?.UpdateLatency(args.server, args.latency); if (_config.availabilityStatistics) { - availabilityStatistics.UpdateLatency(server, (int)latency.TotalMilliseconds); + availabilityStatistics.UpdateLatency(args.server, (int)args.latency.TotalMilliseconds); } } - public void UpdateInboundCounter(Server server, long n) + public void UpdateInboundCounter(object sender, SSTransmitEventArgs args) { - Interlocked.Add(ref _inboundCounter, n); + GetCurrentStrategy()?.UpdateLastRead(args.server); + Interlocked.Add(ref _inboundCounter, args.length); if (_config.availabilityStatistics) { - availabilityStatistics.UpdateInboundCounter(server, n); + availabilityStatistics.UpdateInboundCounter(args.server, args.length); } } - public void UpdateOutboundCounter(Server server, long n) + public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args) { - Interlocked.Add(ref _outboundCounter, n); + GetCurrentStrategy()?.UpdateLastWrite(args.server); + Interlocked.Add(ref _outboundCounter, args.length); if (_config.availabilityStatistics) { - availabilityStatistics.UpdateOutboundCounter(server, n); + availabilityStatistics.UpdateOutboundCounter(args.server, args.length); } } @@ -556,6 +559,11 @@ namespace Shadowsocks.Controller privoxyRunner.Start(_config); TCPRelay tcpRelay = new TCPRelay(this, _config); + tcpRelay.OnConnected += UpdateLatency; + tcpRelay.OnInbound += UpdateInboundCounter; + tcpRelay.OnOutbound += UpdateOutboundCounter; + tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server); + UDPRelay udpRelay = new UDPRelay(this); List services = new List {