diff --git a/Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs b/Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs index b3dbd644..c58b623f 100644 --- a/Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs +++ b/Shadowsocks.Crypto/Crypto/AEAD/AEADCrypto.cs @@ -43,6 +43,9 @@ namespace Shadowsocks.Crypto.AEAD // Is first chunk(tcp request) protected bool tcpRequestSent; + // [len(2)][lentag][data][datatag] + private int ChunkOverhead => tagLen * 2 + 2; + public AEADCrypto(string method, string password) : base(method, password) { @@ -119,17 +122,6 @@ namespace Shadowsocks.Crypto.AEAD outlength = saltLen; } - if (!tcpRequestSent) - { - tcpRequestSent = true; - - // read addr byte to encrypt - int encAddrBufLength = ChunkEncrypt(tmp.Slice(0, AddressBufferLength), cipher.Slice(outlength)); - tmp = tmp.Slice(AddressBufferLength); - outlength += encAddrBufLength; - } - - // handle other chunks while (true) { // calculate next chunk size @@ -147,7 +139,7 @@ namespace Shadowsocks.Crypto.AEAD // check if we have enough space for outbuf // if not, keep buf for next run, at this condition, buffer is not empty - if (outlength + TCPParameter.ChunkOverheadSize > TCPParameter.BufferSize) + if (outlength + ChunkOverhead > cipher.Length) { logger.Debug("enc outbuf almost full, giving up"); @@ -234,7 +226,7 @@ namespace Shadowsocks.Crypto.AEAD outlength += len; // logger.Debug("aead dec outlength " + outlength); - if (outlength + 100 > TCPParameter.BufferSize) + if (outlength + ChunkOverhead > cipher.Length) { logger.Trace($"{instanceId} output almost full, write {tmp.Length} byte back to buffer."); tmp.CopyTo(buffer); diff --git a/Shadowsocks.Crypto/Crypto/CryptoFactory.cs b/Shadowsocks.Crypto/Crypto/CryptoFactory.cs index 01aaec72..845bd527 100644 --- a/Shadowsocks.Crypto/Crypto/CryptoFactory.cs +++ b/Shadowsocks.Crypto/Crypto/CryptoFactory.cs @@ -62,12 +62,12 @@ namespace Shadowsocks.Crypto _registeredEncryptors.Add(method.Key, typeof(AEADAesGcmNativeCrypto)); } } - foreach (var method in AEADNaClCrypto.SupportedCiphers()) + foreach (var method in AEADBouncyCastleCrypto.SupportedCiphers()) { if (!_registeredEncryptors.ContainsKey(method.Key)) { ciphers.Add(method.Key, method.Value); - _registeredEncryptors.Add(method.Key, typeof(AEADNaClCrypto)); + _registeredEncryptors.Add(method.Key, typeof(AEADBouncyCastleCrypto)); } } } diff --git a/Shadowsocks.Crypto/Crypto/ICrypto.cs b/Shadowsocks.Crypto/Crypto/ICrypto.cs index 48c5f210..04bda0eb 100644 --- a/Shadowsocks.Crypto/Crypto/ICrypto.cs +++ b/Shadowsocks.Crypto/Crypto/ICrypto.cs @@ -4,8 +4,6 @@ namespace Shadowsocks.Crypto { public interface ICrypto { - /* length == -1 means not used */ - int AddressBufferLength { set; get; } int Encrypt(ReadOnlySpan plain, Span cipher); int Decrypt(Span plain, ReadOnlySpan cipher); int EncryptUDP(ReadOnlySpan plain, Span cipher); diff --git a/shadowsocks-csharp/Controller/Service/PortForwarder.cs b/shadowsocks-csharp/Controller/Service/PortForwarder.cs index 5dc5d8d4..47914da5 100644 --- a/shadowsocks-csharp/Controller/Service/PortForwarder.cs +++ b/shadowsocks-csharp/Controller/Service/PortForwarder.cs @@ -46,7 +46,7 @@ namespace Shadowsocks.Controller private byte[] _firstPacket; private int _firstPacketLength; private Socket _local; - private WrappedSocket _remote; + private Socket _remote; private bool _closed = false; private bool _localShutdown = false; private bool _remoteShutdown = false; @@ -70,7 +70,7 @@ namespace Shadowsocks.Controller EndPoint remoteEP = SocketUtil.GetEndPoint(_local.AddressFamily == AddressFamily.InterNetworkV6 ? "[::1]" : "127.0.0.1", targetPort); // Connect to the remote endpoint. - _remote = new WrappedSocket(); + _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); _remote.BeginConnect(remoteEP, ConnectCallback, null); } catch (Exception e) diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/shadowsocks-csharp/Controller/Service/TCPRelay.cs index e6bd021c..4e93ca2c 100644 --- a/shadowsocks-csharp/Controller/Service/TCPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/TCPRelay.cs @@ -1,16 +1,19 @@ using System; +using System.Buffers; using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; using System.Net; using System.Net.Sockets; -using System.Timers; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; using NLog; using Shadowsocks.Controller.Strategy; using Shadowsocks.Encryption; using Shadowsocks.Encryption.AEAD; -using Shadowsocks.Encryption.Exception; using Shadowsocks.Model; using Shadowsocks.Proxy; using Shadowsocks.Util.Sockets; @@ -43,16 +46,16 @@ namespace Shadowsocks.Controller public override bool Handle(CachedNetworkStream stream, object state) { - + byte[] fp = new byte[256]; int len = stream.ReadFirstBlock(fp); - + var socket = stream.Socket; if (socket.ProtocolType != ProtocolType.Tcp || (len < 2 || fp[0] != 5)) return false; - + socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); TCPHandler handler = new TCPHandler(_controller, _config, socket); @@ -82,7 +85,8 @@ namespace Shadowsocks.Controller * Then the handler will never release until the next Handle call. Sometimes it will * cause odd problems (especially during memory profiling). */ - handler.Start(fp, len); + // handler.Start(fp, len); + _ = handler.StartAsync(fp, len); return true; // return Handle(fp, len, stream.Socket, state); @@ -141,7 +145,8 @@ namespace Shadowsocks.Controller * Then the handler will never release until the next Handle call. Sometimes it will * cause odd problems (especially during memory profiling). */ - handler.Start(firstPacket, length); + // handler.Start(firstPacket, length); + _ = handler.StartAsync(firstPacket, length); return true; } @@ -194,65 +199,32 @@ namespace Shadowsocks.Controller public event EventHandler OnClosed; public event EventHandler OnFailed; - private class AsyncSession - { - public IProxy Remote { get; } - - public AsyncSession(IProxy remote) - { - Remote = remote; - } - } - - private class AsyncSession : AsyncSession - { - public T State { get; set; } - - public AsyncSession(IProxy remote, T state) : base(remote) - { - State = state; - } - - public AsyncSession(AsyncSession session, T state) : base(session.Remote) - { - State = state; - } - } - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly int _serverTimeout; private readonly int _proxyTimeout; + private readonly MemoryPool pool = MemoryPool.Shared; // each recv size. - public const int RecvSize = 2048; + public const int RecvSize = 16384; // overhead of one chunk, reserved for AEAD ciphers - public const int ChunkOverheadSize = 16 * 2 /* two tags */ + AEADEncryptor.ChunkLengthBytes; - - // max chunk size - public const uint MaxChunkSize = AEADEncryptor.ChunkLengthMask + AEADEncryptor.ChunkLengthBytes + 16 * 2; + public const int ChunkOverheadSize = 100;//16 * 2 /* two tags */ + AEADEncryptor.ChunkLengthBytes; // In general, the ciphertext length, we should take overhead into account - public const int BufferSize = RecvSize + (int)MaxChunkSize + 32 /* max salt len */; + public const int SendSize = 32768; public DateTime lastActivity; private readonly ShadowsocksController _controller; private readonly ForwardProxyConfig _config; private readonly Socket _connection; - + private IProxy _remote; private IEncryptor encryptor; // workaround private IEncryptor decryptor; private Server _server; - private AsyncSession _currentRemoteSession; - - private bool _proxyConnected; - private bool _destConnected; - - private byte _command; private byte[] _firstPacket; private int _firstPacketLength; @@ -260,39 +232,13 @@ namespace Shadowsocks.Controller private const int CMD_BIND = 0x02; private const int CMD_UDP_ASSOC = 0x03; - private int _addrBufLength = -1; - - private int _totalRead = 0; - private int _totalWrite = 0; - - // remote -> local proxy (ciphertext, before decrypt) - private readonly byte[] _remoteRecvBuffer = new byte[BufferSize]; - - // client -> local proxy (plaintext, before encrypt) - private readonly byte[] _connetionRecvBuffer = new byte[BufferSize]; - - // local proxy -> remote (plaintext, after decrypt) - private readonly byte[] _remoteSendBuffer = new byte[BufferSize]; - - // local proxy -> client (ciphertext, before decrypt) - private readonly byte[] _connetionSendBuffer = new byte[BufferSize]; - - private bool _connectionShutdown = false; - private bool _remoteShutdown = false; private bool _closed = false; // instance-based lock without static private readonly object _encryptionLock = new object(); - private readonly object _decryptionLock = new object(); private readonly object _closeConnLock = new object(); - private DateTime _startConnectTime; - private DateTime _startReceivingTime; - private DateTime _startSendingTime; - - private EndPoint _destEndPoint = null; - // TODO: decouple controller public TCPHandler(ShadowsocksController controller, Configuration config, Socket socket) { @@ -305,10 +251,9 @@ namespace Shadowsocks.Controller lastActivity = DateTime.Now; } - public void CreateRemote() + public void CreateRemote(EndPoint destination) { - Server server = _controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)_connection.RemoteEndPoint, - _destEndPoint); + Server server = _controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)_connection.RemoteEndPoint, destination); if (server == null || server.server == "") { throw new ArgumentException("No server configured"); @@ -316,26 +261,23 @@ namespace Shadowsocks.Controller encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); decryptor = EncryptorFactory.GetEncryptor(server.method, server.password); - this._server = server; - - /* prepare address buffer length for AEAD */ - Logger.Debug($"_addrBufLength={_addrBufLength}"); - encryptor.AddressBufferLength = _addrBufLength; - decryptor.AddressBufferLength = _addrBufLength; + _server = server; } - public void Start(byte[] firstPacket, int length) + public async Task StartAsync(byte[] firstPacket, int length) { _firstPacket = firstPacket; _firstPacketLength = length; - HandshakeReceive(); - } - - private void CheckClose() - { - if (_connectionShutdown && _remoteShutdown) + (int cmd, EndPoint dst) = await Socks5Handshake(); + if (cmd == CMD_CONNECT) { - Close(); + await ConnectRemote(dst); + await SendAddress(dst); + await Forward(); + } + else if (cmd == CMD_UDP_ASSOC) + { + await DrainConnection(); } } @@ -368,139 +310,77 @@ namespace Shadowsocks.Controller { Logger.LogUsefulException(e); } - - if (_currentRemoteSession != null) - { - try - { - IProxy remote = _currentRemoteSession.Remote; - remote.Shutdown(SocketShutdown.Both); - remote.Close(); - } - catch (Exception e) - { - Logger.LogUsefulException(e); - } - } } - private void HandshakeReceive() + async Task<(int cmd, EndPoint destination)> Socks5Handshake() { - if (_closed) - { - return; - } + // not so strict here + // 5 2 1 2 should return 5 255 + // 5 1 0 5 / 1 0 1 127 0 0 1 0 80 will cause handshake fail - try - { - int bytesRead = _firstPacketLength; - if (bytesRead > 1) - { - byte[] response = { 5, 0 }; - if (_firstPacket[0] != 5) - { - // reject socks 4 - response = new byte[] { 0, 91 }; - Logger.Error("socks 5 protocol error"); - } - _connection.BeginSend(response, 0, response.Length, SocketFlags.None, - HandshakeSendCallback, null); - } - else - { - Close(); - } - } - catch (Exception e) + int bytesRead = _firstPacketLength; + if (bytesRead <= 1) { - ErrorClose(e); + Close(); + return (0, default); } - } - private void HandshakeSendCallback(IAsyncResult ar) - { - if (_closed) + byte[] response = { 5, 0 }; + if (_firstPacket[0] != 5) { - return; + // reject socks 4 + response = new byte[] { 0, 91 }; + Logger.Error("socks 5 protocol error"); } + await _connection.SendAsync(response, SocketFlags.None); - try + using var bufOwner = pool.Rent(512); + var buf = bufOwner.Memory; + + if (await _connection.ReceiveAsync(buf.Slice(0, 5), SocketFlags.None) != 5) { - _connection.EndSend(ar); - - // +-----+-----+-------+------+----------+----------+ - // | VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | - // +-----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +-----+-----+-------+------+----------+----------+ - // 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. - // 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, - AddressReceiveCallback, null); + Close(); + return (0, default); } - catch (Exception e) + + var cmd = buf.Span[1]; + EndPoint dst = default; + switch (cmd) { - ErrorClose(e); + case CMD_CONNECT: + await _connection.SendAsync(new byte[] { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, SocketFlags.None); + dst = await ReadAddress(buf); + // start forward + break; + case CMD_UDP_ASSOC: + dst = await ReadAddress(buf); + await SendUdpAssociate(); + // drain + break; + default: + Close(); + break; } + return (cmd, dst); } - private void AddressReceiveCallback(IAsyncResult ar) + async Task DrainConnection() { if (_closed) { return; } - + using var b = pool.Rent(512); try { - int bytesRead = _connection.EndReceive(ar); - if (bytesRead >= 5) - { - _command = _connetionRecvBuffer[1]; - switch (_command) - { - case CMD_CONNECT: - - // +----+-----+-------+------+----------+----------+ - // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ - byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }; - _connection.BeginSend(response, 0, response.Length, SocketFlags.None, - ConnectResponseCallback, null); - break; - case CMD_UDP_ASSOC: - ReadAddress(HandleUDPAssociate); - break; - case CMD_BIND: // not implemented - default: - Logger.Debug("Unsupported CMD=" + _command); - Close(); - break; - } - } - else + int l; + do { - Logger.Debug( - "failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()"); - Close(); + l = await _connection.ReceiveAsync(b.Memory, SocketFlags.None); } - } - catch (Exception e) - { - ErrorClose(e); - } - } + while (l > 0); - private void ConnectResponseCallback(IAsyncResult ar) - { - try - { - _connection.EndSend(ar); - - ReadAddress(StartConnect); + Close(); } catch (Exception e) { @@ -508,105 +388,44 @@ namespace Shadowsocks.Controller } } - private void ReadAddress(Action onSuccess) + private async Task ReadAddress(Memory buf) { - int atyp = _connetionRecvBuffer[3]; + var atyp = buf.Span[3]; + var maybeDomainLength = buf.Span[4]; + buf.Span[0] = atyp; + buf.Span[1] = maybeDomainLength; - switch (atyp) + int toRead = atyp switch { - case ATYP_IPv4: // IPv4 address, 4 bytes - ReadAddress(4 + ADDR_PORT_LEN - 1, onSuccess); - break; - case ATYP_DOMAIN: // domain name, length + str - int len = _connetionRecvBuffer[4]; - ReadAddress(len + ADDR_PORT_LEN, onSuccess); - break; - case ATYP_IPv6: // IPv6 address, 16 bytes - ReadAddress(16 + ADDR_PORT_LEN - 1, onSuccess); - break; - default: - Logger.Debug("Unsupported ATYP=" + atyp); - Close(); - break; - } + ATYP_IPv4 => 4, + ATYP_IPv6 => 16, + ATYP_DOMAIN => maybeDomainLength + 1, + _ => throw new NotSupportedException(), + } + 2 - 1; + await _connection.ReceiveAsync(buf.Slice(2, toRead), SocketFlags.None); + + return GetSocks5EndPoint(buf.ToArray()); } - private void ReadAddress(int bytesRemain, Action onSuccess) + private int ReadPort(byte[] arr, long offset) { - // drop [ VER | CMD | RSV ] - Array.Copy(_connetionRecvBuffer, 3, _connetionRecvBuffer, 0, ADDR_ATYP_LEN + 1); - - // Read the remain address bytes - _connection.BeginReceive(_connetionRecvBuffer, 2, RecvSize - 2, SocketFlags.None, OnAddressFullyRead, - new object[] { bytesRemain, onSuccess }); + return (arr[offset] << 8) + arr[offset + 1]; } - private void OnAddressFullyRead(IAsyncResult ar) + private EndPoint GetSocks5EndPoint(byte[] buf) { - if (_closed) - { - return; - } + int maybeDomainLength = buf[1] + 2; - try + return (buf[0]) switch { - int bytesRead = _connection.EndReceive(ar); - - object[] states = (object[])ar.AsyncState; - - int bytesRemain = (int)states[0]; - Action onSuccess = (Action)states[1]; - - if (bytesRead >= bytesRemain) - { - _firstPacketLength = bytesRead + 2; - - int atyp = _connetionRecvBuffer[0]; - - string dstAddr = "Unknown"; - int dstPort = -1; - switch (atyp) - { - case ATYP_IPv4: // IPv4 address, 4 bytes - dstAddr = new IPAddress(_connetionRecvBuffer.Skip(1).Take(4).ToArray()).ToString(); - dstPort = (_connetionRecvBuffer[5] << 8) + _connetionRecvBuffer[6]; - - _addrBufLength = ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; - break; - case ATYP_DOMAIN: // domain name, length + str - int len = _connetionRecvBuffer[1]; - dstAddr = System.Text.Encoding.UTF8.GetString(_connetionRecvBuffer, 2, len); - dstPort = (_connetionRecvBuffer[len + 2] << 8) + _connetionRecvBuffer[len + 3]; - - _addrBufLength = ADDR_ATYP_LEN + 1 + len + ADDR_PORT_LEN; - break; - case ATYP_IPv6: // IPv6 address, 16 bytes - dstAddr = $"[{new IPAddress(_connetionRecvBuffer.Skip(1).Take(16).ToArray())}]"; - dstPort = (_connetionRecvBuffer[17] << 8) + _connetionRecvBuffer[18]; - - _addrBufLength = ADDR_ATYP_LEN + 16 + ADDR_PORT_LEN; - break; - } - - Logger.Debug($"connect to {dstAddr}:{dstPort}"); - - _destEndPoint = SocketUtil.GetEndPoint(dstAddr, dstPort); - - onSuccess.Invoke(); /* StartConnect() */ - } - else - { - Logger.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.OnAddressFullyRead()"); - Close(); - } - } - catch (Exception e) - { - ErrorClose(e); - } + ATYP_IPv4 => new IPEndPoint(new IPAddress(buf[1..5]), ReadPort(buf, 5)), + ATYP_IPv6 => new IPEndPoint(new IPAddress(buf[1..17]), ReadPort(buf, 17)), + ATYP_DOMAIN => new DnsEndPoint(Encoding.ASCII.GetString(buf[2..maybeDomainLength]), ReadPort(buf, maybeDomainLength)), + _ => throw new NotSupportedException(), + }; } - private void HandleUDPAssociate() + private async Task SendUdpAssociate() { IPEndPoint endPoint = (IPEndPoint)_connection.LocalEndPoint; byte[] address = endPoint.Address.GetAddressBytes(); @@ -623,305 +442,72 @@ namespace Shadowsocks.Controller break; } address.CopyTo(response, 4); - response[response.Length - 1] = (byte)(port & 0xFF); - response[response.Length - 2] = (byte)((port >> 8) & 0xFF); - _connection.BeginSend(response, 0, response.Length, SocketFlags.None, ReadAll, true); - } - - private void ReadAll(IAsyncResult ar) - { - if (_closed) - { - return; - } - - try - { - if (ar.AsyncState != null) - { - _connection.EndSend(ar); - _connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, - ReadAll, null); - } - else - { - int bytesRead = _connection.EndReceive(ar); - if (bytesRead > 0) - { - _connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, - ReadAll, null); - } - else - { - Close(); - } - } - } - catch (Exception e) - { - ErrorClose(e); - } - } - - // inner class - private class ProxyTimer : Timer - { - public AsyncSession Session; - - public EndPoint DestEndPoint; - public Server Server; - - public ProxyTimer(int p) : base(p) - { - } + response[^1] = (byte)(port & 0xFF); + response[^2] = (byte)((port >> 8) & 0xFF); + await _connection.SendAsync(response, SocketFlags.None); } - private class ServerTimer : Timer + private async Task ConnectRemote(EndPoint destination) { - public AsyncSession Session; - - public Server Server; - - public ServerTimer(int p) : base(p) - { - } - } + CreateRemote(destination); + IProxy remote; + EndPoint proxyEP = null; + EndPoint serverEP = SocketUtil.GetEndPoint(_server.server, _server.server_port); + EndPoint pluginEP = _controller.GetPluginLocalEndPointIfConfigured(_server); - private void StartConnect() - { - try + NetworkCredential auth = null; + if (_config.useAuth) { - CreateRemote(); - - // Setting up proxy - IProxy remote; - EndPoint proxyEP = null; - EndPoint serverEP = SocketUtil.GetEndPoint(_server.server, _server.server_port); - EndPoint pluginEP = _controller.GetPluginLocalEndPointIfConfigured(_server); - - if (pluginEP != null) - { - serverEP = pluginEP; - remote = new DirectConnect(); - } - else if (_config.useProxy) - { - switch (_config.proxyType) - { - case ForwardProxyConfig.PROXY_SOCKS5: - remote = new Socks5Proxy(); - break; - case ForwardProxyConfig.PROXY_HTTP: - remote = new HttpProxy(); - break; - default: - throw new NotSupportedException("Unknown forward proxy."); - } - proxyEP = SocketUtil.GetEndPoint(_config.proxyServer, _config.proxyPort); - } - else - { - remote = new DirectConnect(); - } - - AsyncSession session = new AsyncSession(remote); - lock (_closeConnLock) - { - if (_closed) - { - remote.Close(); - return; - } - - _currentRemoteSession = session; - } - - ProxyTimer proxyTimer = new ProxyTimer(_proxyTimeout) { AutoReset = false }; - proxyTimer.Elapsed += ProxyConnectTimer_Elapsed; - proxyTimer.Enabled = true; - - proxyTimer.Session = session; - proxyTimer.DestEndPoint = serverEP; - proxyTimer.Server = _server; - - _proxyConnected = false; - - // Connect to the proxy server. - remote.BeginConnectProxy(proxyEP, ProxyConnectCallback, - new AsyncSession(remote, proxyTimer)); + auth = new NetworkCredential(_config.authUser, _config.authPwd); } - catch (Exception e) + if (pluginEP != null) { - ErrorClose(e); + serverEP = pluginEP; + remote = new DirectConnect(); } - } - - private void ProxyConnectTimer_Elapsed(object sender, ElapsedEventArgs e) - { - ProxyTimer timer = (ProxyTimer)sender; - timer.Elapsed -= ProxyConnectTimer_Elapsed; - timer.Enabled = false; - timer.Dispose(); - - - if (_proxyConnected || _destConnected || _closed) + else if (_config.useProxy) { - return; - } - IProxy proxy = timer.Session.Remote; - - Logger.Info($"Proxy {proxy.ProxyEndPoint} timed out"); - proxy.Close(); - Close(); - } - - private void ProxyConnectCallback(IAsyncResult ar) - { - if (_closed) - { - return; - } - try - { - AsyncSession session = (AsyncSession)ar.AsyncState; - ProxyTimer timer = session.State; - EndPoint destEndPoint = timer.DestEndPoint; - Server server = timer.Server; - timer.Elapsed -= ProxyConnectTimer_Elapsed; - timer.Enabled = false; - timer.Dispose(); - - IProxy remote = session.Remote; - - // Complete the connection. - remote.EndConnectProxy(ar); - - _proxyConnected = true; - - if (!(remote is DirectConnect)) - { - Logger.Debug($"Socket connected to proxy {remote.ProxyEndPoint}"); - } - - _startConnectTime = DateTime.Now; - ServerTimer connectTimer = new ServerTimer(_serverTimeout) { AutoReset = false }; - connectTimer.Elapsed += DestConnectTimer_Elapsed; - connectTimer.Enabled = true; - connectTimer.Session = session; - connectTimer.Server = server; - - _destConnected = false; - - NetworkCredential auth = null; - if (_config.useAuth) + remote = _config.proxyType switch { - auth = new NetworkCredential(_config.authUser, _config.authPwd); - } - - // Connect to the remote endpoint. - remote.BeginConnectDest(destEndPoint, ConnectCallback, - new AsyncSession(session, connectTimer), auth); + ForwardProxyConfig.PROXY_SOCKS5 => new Socks5Proxy(), + ForwardProxyConfig.PROXY_HTTP => new HttpProxy(), + _ => throw new NotSupportedException("Unknown forward proxy."), + }; + proxyEP = SocketUtil.GetEndPoint(_config.proxyServer, _config.proxyPort); } - catch (ArgumentException) + else { - } - catch (Exception e) - { - ErrorClose(e); - } - } - - private void DestConnectTimer_Elapsed(object sender, ElapsedEventArgs e) - { - ServerTimer timer = (ServerTimer)sender; - timer.Elapsed -= DestConnectTimer_Elapsed; - timer.Enabled = false; - timer.Dispose(); - - if (_destConnected || _closed) - { - return; - } - - AsyncSession session = timer.Session; - Server server = timer.Server; - OnFailed?.Invoke(this, new SSRelayEventArgs(_server)); - Logger.Info($"{server.ToString()} timed out"); - session.Remote.Close(); - Close(); - } - - private void ConnectCallback(IAsyncResult ar) - { - if (_closed) - { - return; + remote = new DirectConnect(); } - try - { - AsyncSession session = (AsyncSession)ar.AsyncState; - ServerTimer timer = session.State; - _server = timer.Server; - timer.Elapsed -= DestConnectTimer_Elapsed; - timer.Enabled = false; - timer.Dispose(); - - IProxy remote = session.Remote; - // Complete the connection. - remote.EndConnectDest(ar); - - _destConnected = true; - - Logger.Debug($"Socket connected to ss server: {_server.ToString()}"); - TimeSpan latency = DateTime.Now - _startConnectTime; + CancellationTokenSource cancelProxy = new CancellationTokenSource(_proxyTimeout * 1000); - OnConnected?.Invoke(this, new SSTCPConnectedEventArgs(_server, latency)); + await remote.ConnectProxyAsync(proxyEP, auth, cancelProxy.Token); + _remote = remote; - StartPipe(session); - } - catch (ArgumentException) + if (!(remote is DirectConnect)) { + Logger.Debug($"Socket connected to proxy {remote.ProxyEndPoint}"); } - catch (Exception e) - { - if (_server != null) - { - OnFailed?.Invoke(this, new SSRelayEventArgs(_server)); - } - ErrorClose(e); - } - } - private void TryReadAvailableData() - { - int available = Math.Min(_connection.Available, RecvSize - _firstPacketLength); - if (available > 0) - { - int size = _connection.Receive(_connetionRecvBuffer, _firstPacketLength, available, - SocketFlags.None); + var _startConnectTime = DateTime.Now; + CancellationTokenSource cancelServer = new CancellationTokenSource(_serverTimeout * 1000); + await remote.ConnectRemoteAsync(serverEP, cancelServer.Token); + Logger.Debug($"Socket connected to ss server: {_server}"); + TimeSpan latency = DateTime.Now - _startConnectTime; + OnConnected?.Invoke(this, new SSTCPConnectedEventArgs(_server, latency)); - _firstPacketLength += size; - } } - private void StartPipe(AsyncSession session) + private async Task SendAddress(EndPoint dest) { - if (_closed) - { - return; - } - + byte[] dstByte = GetSocks5EndPointByte(dest); + using var t = pool.Rent(512); try { - _startReceivingTime = DateTime.Now; - session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, - PipeRemoteReceiveCallback, session); - - TryReadAvailableData(); - Logger.Debug($"_firstPacketLength = {_firstPacketLength}"); - SendToServer(_firstPacketLength, session); + int addrlen = encryptor.Encrypt(dstByte, t.Memory.Span); + await _remote.SendAsync(t.Memory.Slice(0, addrlen)); } catch (Exception e) { @@ -929,87 +515,48 @@ namespace Shadowsocks.Controller } } - private void PipeRemoteReceiveCallback(IAsyncResult ar) + private byte[] GetSocks5EndPointByte(EndPoint dest) { - if (_closed) + if (dest is DnsEndPoint d) { - return; + byte[] r = new byte[d.Host.Length + 4]; + r[0] = 3; + r[1] = (byte)d.Host.Length; + Encoding.ASCII.GetBytes(d.Host, r.AsSpan(2)); + r[^2] = (byte)(d.Port / 256); + r[^1] = (byte)(d.Port % 256); + return r; } - - try + else if (dest is IPEndPoint i) { - AsyncSession session = (AsyncSession)ar.AsyncState; - int bytesRead = session.Remote.EndReceive(ar); - _totalRead += bytesRead; - - OnInbound?.Invoke(this, new SSTransmitEventArgs(_server, bytesRead)); - if (bytesRead > 0) + if (i.AddressFamily == AddressFamily.InterNetwork) { - lastActivity = DateTime.Now; - int bytesToSend = -1; - lock (_decryptionLock) - { - try - { - bytesToSend = decryptor.Decrypt(_remoteSendBuffer, _remoteRecvBuffer.AsSpan(0, bytesRead)); - // decryptor.Decrypt(_remoteRecvBuffer, bytesRead, _remoteSendBuffer, out bytesToSend); - } - catch (CryptoErrorException) - { - Logger.Error("decryption error"); - Close(); - return; - } - } - if (bytesToSend == 0) - { - // need more to decrypt - Logger.Debug("Need more to decrypt"); - session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, - PipeRemoteReceiveCallback, session); - return; - } - Logger.Debug($"start sending {bytesToSend}"); - _connection.BeginSend(_remoteSendBuffer, 0, bytesToSend, SocketFlags.None, - PipeConnectionSendCallback, new object[] { session, bytesToSend }); + byte[] r = new byte[7]; + r[0] = 1; + i.Address.GetAddressBytes().CopyTo(r, 1); + r[^2] = (byte)(i.Port / 256); + r[^1] = (byte)(i.Port % 256); + return r; } - else + else if (i.AddressFamily == AddressFamily.InterNetworkV6) { - _connection.Shutdown(SocketShutdown.Send); - _connectionShutdown = true; - CheckClose(); + byte[] r = new byte[19]; + r[0] = 1; + i.Address.GetAddressBytes().CopyTo(r, 1); + r[^2] = (byte)(i.Port / 256); + r[^1] = (byte)(i.Port % 256); + return r; } } - catch (Exception e) - { - ErrorClose(e); - } + throw new NotImplementedException(); } - private void PipeConnectionReceiveCallback(IAsyncResult ar) + private async Task Forward() { - if (_closed) - { - return; - } - try { - int bytesRead = _connection.EndReceive(ar); - - AsyncSession session = (AsyncSession)ar.AsyncState; - IProxy remote = session.Remote; - - if (bytesRead > 0) - { - SendToServer(bytesRead, session); - } - else - { - remote.Shutdown(SocketShutdown.Send); - _remoteShutdown = true; - CheckClose(); - } + await Task.WhenAll(ForwardInbound(), ForwardOutbound()); + Close(); } catch (Exception e) { @@ -1017,55 +564,25 @@ namespace Shadowsocks.Controller } } - private void SendToServer(int length, AsyncSession session) + private async Task ForwardInbound() { - _totalWrite += length; - int bytesToSend; - lock (_encryptionLock) - { - try - { - bytesToSend = encryptor.Encrypt(_connetionRecvBuffer.AsSpan(0, length), _connetionSendBuffer); - // encryptor.Encrypt(_connetionRecvBuffer, length, _connetionSendBuffer, out bytesToSend); - } - catch (CryptoErrorException) - { - Logger.Debug("encryption error"); - Close(); - return; - } - } - - OnOutbound?.Invoke(this, new SSTransmitEventArgs(_server, bytesToSend)); - _startSendingTime = DateTime.Now; - session.Remote.BeginSend(_connetionSendBuffer, 0, bytesToSend, SocketFlags.None, - PipeRemoteSendCallback, new object[] { session, bytesToSend }); - } - - private void PipeRemoteSendCallback(IAsyncResult ar) - { - if (_closed) - { - return; - } - + using var cipherOwner = pool.Rent(RecvSize); + using var plainOwner = pool.Rent(SendSize); + var plain = plainOwner.Memory; + var cipher = cipherOwner.Memory; try { - object[] container = (object[])ar.AsyncState; - AsyncSession session = (AsyncSession)container[0]; - int bytesShouldSend = (int)container[1]; - int bytesSent = session.Remote.EndSend(ar); - int bytesRemaining = bytesShouldSend - bytesSent; - if (bytesRemaining > 0) + + while (true) { - Logger.Info("reconstruct _connetionSendBuffer to re-send"); - Buffer.BlockCopy(_connetionSendBuffer, bytesSent, _connetionSendBuffer, 0, bytesRemaining); - session.Remote.BeginSend(_connetionSendBuffer, 0, bytesRemaining, SocketFlags.None, - PipeRemoteSendCallback, new object[] { session, bytesRemaining }); - return; + int len = await _remote.ReceiveAsync(cipher); + if (len == 0) break; + int plen = decryptor.Decrypt(plain.Span, cipher.Span.Slice(0, len)); + if (plen == 0) continue; + int len2 = await _connection.SendAsync(plain.Slice(0, plen), SocketFlags.None); + if (len2 == 0) break; + OnInbound?.Invoke(this, new SSTransmitEventArgs(_server, plen)); } - _connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, - PipeConnectionReceiveCallback, session); } catch (Exception e) { @@ -1073,31 +590,23 @@ namespace Shadowsocks.Controller } } - // In general, we assume there is no delay between local proxy and client, add this for sanity - private void PipeConnectionSendCallback(IAsyncResult ar) + private async Task ForwardOutbound() { - try + using var plainOwner = pool.Rent(RecvSize); + using var cipherOwner = pool.Rent(SendSize); + var plain = plainOwner.Memory; + var cipher = cipherOwner.Memory; + while (true) { - 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"); - Buffer.BlockCopy(_remoteSendBuffer, bytesSent, _remoteSendBuffer, 0, bytesRemaining); - _connection.BeginSend(_remoteSendBuffer, 0, bytesRemaining, SocketFlags.None, - PipeConnectionSendCallback, new object[] { session, bytesRemaining }); - return; - } - session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, - PipeRemoteReceiveCallback, session); - } - catch (Exception e) - { - ErrorClose(e); + int len = await _connection.ReceiveAsync(plain, SocketFlags.None); + + if (len == 0) break; + int clen = encryptor.Encrypt(plain.Span.Slice(0, len), cipher.Span); + int len2 = await _remote.SendAsync(cipher.Slice(0, clen)); + if (len2 == 0) break; + OnOutbound?.Invoke(this, new SSTransmitEventArgs(_server, len)); } + _remote.Shutdown(SocketShutdown.Send); } } } \ No newline at end of file diff --git a/shadowsocks-csharp/Proxy/DirectConnect.cs b/shadowsocks-csharp/Proxy/DirectConnect.cs index 8a5e7479..88506328 100644 --- a/shadowsocks-csharp/Proxy/DirectConnect.cs +++ b/shadowsocks-csharp/Proxy/DirectConnect.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Sockets; using System.Threading; +using System.Threading.Tasks; using Shadowsocks.Util.Sockets; namespace Shadowsocks.Proxy @@ -31,69 +32,42 @@ namespace Shadowsocks.Proxy } } - private WrappedSocket _remote = new WrappedSocket(); + private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); public EndPoint LocalEndPoint => _remote.LocalEndPoint; public EndPoint ProxyEndPoint { get; } = new FakeEndPoint(); public EndPoint DestEndPoint { get; private set; } - public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state) - { - // do nothing - - var r = new FakeAsyncResult(state); - callback?.Invoke(r); - } - - public void EndConnectProxy(IAsyncResult asyncResult) - { - // do nothing - } - - public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null) - { - DestEndPoint = destEndPoint; - - _remote.BeginConnect(destEndPoint, callback, state); - } - - public void EndConnectDest(IAsyncResult asyncResult) - { - _remote.EndConnect(asyncResult); - _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - } - - public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, - object state) + public void Shutdown(SocketShutdown how) { - _remote.BeginSend(buffer, offset, size, socketFlags, callback, state); + _remote.Shutdown(how); } - public int EndSend(IAsyncResult asyncResult) + public void Close() { - return _remote.EndSend(asyncResult); + _remote.Dispose(); } - public void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, - object state) + public Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default) { - _remote.BeginReceive(buffer, offset, size, socketFlags, callback, state); + return Task.CompletedTask; } - public int EndReceive(IAsyncResult asyncResult) + public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default) { - return _remote.EndReceive(asyncResult); + DestEndPoint = destEndPoint; + await _remote.ConnectAsync(destEndPoint); } - public void Shutdown(SocketShutdown how) + public async Task SendAsync(ReadOnlyMemory buffer, CancellationToken token = default) { - _remote.Shutdown(how); + return await _remote.SendAsync(buffer, SocketFlags.None, token); } - public void Close() + public async Task ReceiveAsync(Memory buffer, CancellationToken token = default) { - _remote.Dispose(); + return await _remote.ReceiveAsync(buffer, SocketFlags.None, token); } } } diff --git a/shadowsocks-csharp/Proxy/HttpProxy.cs b/shadowsocks-csharp/Proxy/HttpProxy.cs index b7580f04..7ee73ec4 100644 --- a/shadowsocks-csharp/Proxy/HttpProxy.cs +++ b/shadowsocks-csharp/Proxy/HttpProxy.cs @@ -4,6 +4,7 @@ using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Threading; +using System.Threading.Tasks; using NLog; using Shadowsocks.Controller; using Shadowsocks.Util.Sockets; @@ -12,60 +13,17 @@ 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 Exception ex { get; set; } - } + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); 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 readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); private const string HTTP_CRLF = "\r\n"; - private const string HTTP_CONNECT_TEMPLATE = - "CONNECT {0} HTTP/1.1" + HTTP_CRLF + + 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/85.0.4183.83 Safari/537.36" + HTTP_CRLF + @@ -73,58 +31,6 @@ namespace Shadowsocks.Proxy "" + 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); @@ -135,45 +41,6 @@ namespace Shadowsocks.Proxy _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; @@ -206,5 +73,45 @@ namespace Shadowsocks.Proxy return false; } + + private NetworkCredential auth; + + public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default) + { + ProxyEndPoint = remoteEP; + this.auth = auth; + await _remote.ConnectAsync(remoteEP); + _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); + } + + public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default) + { + 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); + + await _remote.SendAsync(Encoding.UTF8.GetBytes(request), SocketFlags.None, token); + + // start line read + LineReader reader = new LineReader(_remote, OnLineRead, (e, _) => throw e, (_1, _2, _3, _4) => { }, Encoding.UTF8, HTTP_CRLF, 1024, null); + await reader.Finished; + } + + public async Task SendAsync(ReadOnlyMemory buffer, CancellationToken token = default) + { + return await _remote.SendAsync(buffer, SocketFlags.None, token); + } + + public async Task ReceiveAsync(Memory buffer, CancellationToken token = default) + { + return await _remote.ReceiveAsync(buffer, SocketFlags.None, token); + } } } diff --git a/shadowsocks-csharp/Proxy/IProxy.cs b/shadowsocks-csharp/Proxy/IProxy.cs index c7fae1eb..da982744 100644 --- a/shadowsocks-csharp/Proxy/IProxy.cs +++ b/shadowsocks-csharp/Proxy/IProxy.cs @@ -1,6 +1,8 @@ using System; using System.Net; using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; namespace Shadowsocks.Proxy { @@ -13,23 +15,13 @@ namespace Shadowsocks.Proxy EndPoint DestEndPoint { get; } - void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state); + Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default); - void EndConnectProxy(IAsyncResult asyncResult); + Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default); - void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null); + Task SendAsync(ReadOnlyMemory buffer, CancellationToken token = default); - void EndConnectDest(IAsyncResult asyncResult); - - void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, - object state); - - int EndSend(IAsyncResult asyncResult); - - void BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, - object state); - - int EndReceive(IAsyncResult asyncResult); + Task ReceiveAsync(Memory buffer, CancellationToken token = default); void Shutdown(SocketShutdown how); diff --git a/shadowsocks-csharp/Proxy/Socks5Proxy.cs b/shadowsocks-csharp/Proxy/Socks5Proxy.cs index 2fe294df..828fb83a 100644 --- a/shadowsocks-csharp/Proxy/Socks5Proxy.cs +++ b/shadowsocks-csharp/Proxy/Socks5Proxy.cs @@ -3,6 +3,7 @@ using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; +using System.Threading.Tasks; using Shadowsocks.Controller; using Shadowsocks.Util.Sockets; @@ -10,36 +11,7 @@ namespace Shadowsocks.Proxy { public class Socks5Proxy : IProxy { - private class FakeAsyncResult : IAsyncResult - { - public readonly Socks5State innerState; - - private readonly IAsyncResult r; - - public FakeAsyncResult(IAsyncResult orig, Socks5State 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 Socks5State - { - public AsyncCallback Callback { get; set; } - - public object AsyncState { get; set; } - - public int BytesToRead; - - public Exception ex { get; set; } - } - - private readonly WrappedSocket _remote = new WrappedSocket(); + private readonly Socket _remote = new Socket(SocketType.Stream, ProtocolType.Tcp); private const int Socks5PktMaxSize = 4 + 16 + 2; private readonly byte[] _receiveBuffer = new byte[Socks5PktMaxSize]; @@ -48,38 +20,40 @@ namespace Shadowsocks.Proxy public EndPoint ProxyEndPoint { get; private set; } public EndPoint DestEndPoint { get; private set; } - public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state) + public void Shutdown(SocketShutdown how) { - var st = new Socks5State(); - st.Callback = callback; - st.AsyncState = state; - - ProxyEndPoint = remoteEP; - - _remote.BeginConnect(remoteEP, ConnectCallback, st); + _remote.Shutdown(how); } - public void EndConnectProxy(IAsyncResult asyncResult) + public void Close() { - var state = ((FakeAsyncResult)asyncResult).innerState; + _remote.Dispose(); + } - if (state.ex != null) + public async Task ConnectProxyAsync(EndPoint remoteEP, NetworkCredential auth = null, CancellationToken token = default) + { + ProxyEndPoint = remoteEP; + await _remote.ConnectAsync(remoteEP); + await _remote.SendAsync(new byte[] { 5, 1, 0 }, SocketFlags.None); + if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 2), SocketFlags.None) != 2) + { + throw new Exception(I18N.GetString("Proxy handshake failed")); + } + if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0) { - throw state.ex; + throw new Exception(I18N.GetString("Proxy handshake failed")); } } - public void BeginConnectDest(EndPoint destEndPoint, AsyncCallback callback, object state, NetworkCredential auth = null) + public async Task ConnectRemoteAsync(EndPoint destEndPoint, CancellationToken token = default) { // TODO: support SOCKS5 auth DestEndPoint = destEndPoint; - byte[] request = null; - byte atyp = 0; + byte[] request; + byte atyp; int port; - - var dep = destEndPoint as DnsEndPoint; - if (dep != null) + if (destEndPoint is DnsEndPoint dep) { // is a domain name, we will leave it to server @@ -108,7 +82,7 @@ namespace Shadowsocks.Proxy default: throw new Exception(I18N.GetString("Proxy request failed")); } - port = ((IPEndPoint) DestEndPoint).Port; + port = ((IPEndPoint)DestEndPoint).Port; var addr = ((IPEndPoint)DestEndPoint).Address.GetAddressBytes(); Array.Copy(addr, 0, request, 4, request.Length - 4 - 2); } @@ -118,206 +92,42 @@ namespace Shadowsocks.Proxy request[1] = 1; request[2] = 0; request[3] = atyp; - request[request.Length - 2] = (byte) ((port >> 8) & 0xff); - request[request.Length - 1] = (byte) (port & 0xff); - - var st = new Socks5State(); - st.Callback = callback; - st.AsyncState = state; - - _remote.BeginSend(request, 0, request.Length, 0, Socks5RequestSendCallback, 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); - } + request[^2] = (byte)((port >> 8) & 0xff); + request[^1] = (byte)(port & 0xff); - public void Close() - { - _remote.Dispose(); - } - + await _remote.SendAsync(request, SocketFlags.None, token); - private void ConnectCallback(IAsyncResult ar) - { - var state = (Socks5State) ar.AsyncState; - try + if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, 4), SocketFlags.None, token) != 4) { - _remote.EndConnect(ar); - - _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - - byte[] handshake = {5, 1, 0}; - _remote.BeginSend(handshake, 0, handshake.Length, 0, Socks5HandshakeSendCallback, state); - } - catch (Exception ex) + throw new Exception(I18N.GetString("Proxy request failed")); + }; + if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0) { - state.ex = ex; - state.Callback?.Invoke(new FakeAsyncResult(ar, state)); + throw new Exception(I18N.GetString("Proxy request failed")); } - } - - private void Socks5HandshakeSendCallback(IAsyncResult ar) - { - var state = (Socks5State)ar.AsyncState; - try - { - _remote.EndSend(ar); - - _remote.BeginReceive(_receiveBuffer, 0, 2, 0, Socks5HandshakeReceiveCallback, state); - } - catch (Exception ex) - { - state.ex = ex; - state.Callback?.Invoke(new FakeAsyncResult(ar, state)); - } - } - - private void Socks5HandshakeReceiveCallback(IAsyncResult ar) - { - Exception ex = null; - var state = (Socks5State)ar.AsyncState; - try + var addrLen = _receiveBuffer[3] switch { - var bytesRead = _remote.EndReceive(ar); - if (bytesRead >= 2) - { - if (_receiveBuffer[0] != 5 || _receiveBuffer[1] != 0) - { - ex = new Exception(I18N.GetString("Proxy handshake failed")); - } - } - else - { - ex = new Exception(I18N.GetString("Proxy handshake failed")); - } - } - catch (Exception ex2) + 1 => 6, + 4 => 18, + _ => throw new NotImplementedException(), + }; + if (await _remote.ReceiveAsync(_receiveBuffer.AsMemory(0, addrLen), SocketFlags.None, token) != addrLen) { - ex = ex2; + throw new Exception(I18N.GetString("Proxy request failed")); } - state.ex = ex; - state.Callback?.Invoke(new FakeAsyncResult(ar, state)); - } - private void Socks5RequestSendCallback(IAsyncResult ar) - { - var state = (Socks5State)ar.AsyncState; - try - { - _remote.EndSend(ar); - _remote.BeginReceive(_receiveBuffer, 0, 4, 0, Socks5ReplyReceiveCallback, state); - } - catch (Exception ex) - { - state.ex = ex; - state.Callback?.Invoke(new FakeAsyncResult(ar, state)); - } } - private void Socks5ReplyReceiveCallback(IAsyncResult ar) + public async Task SendAsync(ReadOnlyMemory buffer, CancellationToken token = default) { - var state = (Socks5State)ar.AsyncState; - try - { - var bytesRead = _remote.EndReceive(ar); - if (bytesRead >= 4) - { - if (_receiveBuffer[0] == 5 && _receiveBuffer[1] == 0) - { - // 跳过剩下的reply - switch (_receiveBuffer[3]) // atyp - { - case 1: - state.BytesToRead = 4 + 2; - _remote.BeginReceive(_receiveBuffer, 0, 4 + 2, 0, Socks5ReplyReceiveCallback2, state); - break; - case 4: - state.BytesToRead = 16 + 2; - _remote.BeginReceive(_receiveBuffer, 0, 16 + 2, 0, Socks5ReplyReceiveCallback2, state); - break; - default: - state.ex = new Exception(I18N.GetString("Proxy request failed")); - state.Callback?.Invoke(new FakeAsyncResult(ar, state)); - break; - } - } - else - { - state.ex = new Exception(I18N.GetString("Proxy request failed")); - state.Callback?.Invoke(new FakeAsyncResult(ar, state)); - } - } - else - { - state.ex = new Exception(I18N.GetString("Proxy request failed")); - state.Callback?.Invoke(new FakeAsyncResult(ar, state)); - } - } - catch (Exception ex) - { - state.ex = ex; - state.Callback?.Invoke(new FakeAsyncResult(ar, state)); - } + return await _remote.SendAsync(buffer, SocketFlags.None, token); } - - private void Socks5ReplyReceiveCallback2(IAsyncResult ar) + public async Task ReceiveAsync(Memory buffer, CancellationToken token = default) { - Exception ex = null; - var state = (Socks5State)ar.AsyncState; - try - { - var bytesRead = _remote.EndReceive(ar); - var bytesNeedSkip = state.BytesToRead; - - if (bytesRead < bytesNeedSkip) - { - ex = new Exception(I18N.GetString("Proxy request failed")); - } - } - catch (Exception ex2) - { - ex = ex2; - } - - state.ex = ex; - state.Callback?.Invoke(new FakeAsyncResult(ar, state)); + return await _remote.ReceiveAsync(buffer, SocketFlags.None, token); } } } diff --git a/shadowsocks-csharp/Util/Sockets/LineReader.cs b/shadowsocks-csharp/Util/Sockets/LineReader.cs index 93626371..b1bbcc27 100644 --- a/shadowsocks-csharp/Util/Sockets/LineReader.cs +++ b/shadowsocks-csharp/Util/Sockets/LineReader.cs @@ -1,11 +1,13 @@ using System; +using System.Net.Sockets; using System.Text; +using System.Threading.Tasks; namespace Shadowsocks.Util.Sockets { public class LineReader { - private readonly WrappedSocket _socket; + private readonly Socket _socket; private readonly Func _onLineRead; private readonly Action _onException; private readonly Action _onFinish; @@ -20,7 +22,10 @@ namespace Shadowsocks.Util.Sockets private int _bufferIndex; - public LineReader(WrappedSocket socket, Func onLineRead, Action onException, + private readonly TaskCompletionSource finishPromise = new TaskCompletionSource(); + public Task Finished => finishPromise.Task; + + public LineReader(Socket socket, Func onLineRead, Action onException, Action onFinish, Encoding encoding, string delimiter, int maxLineBytes, object state) { if (socket == null) @@ -80,6 +85,7 @@ namespace Shadowsocks.Util.Sockets if (bytesRead == 0) { OnFinish(length); + finishPromise.TrySetResult(0); return; } @@ -128,6 +134,7 @@ namespace Shadowsocks.Util.Sockets private void OnException(Exception ex) { + finishPromise.TrySetException(ex); _onException?.Invoke(ex, _state); } diff --git a/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs b/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs deleted file mode 100644 index c5308c90..00000000 --- a/shadowsocks-csharp/Util/Sockets/WrappedSocket.cs +++ /dev/null @@ -1,268 +0,0 @@ -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); - } - } -} diff --git a/test/CryptographyTest.cs b/test/CryptographyTest.cs index f90f6f81..07e18d67 100644 --- a/test/CryptographyTest.cs +++ b/test/CryptographyTest.cs @@ -55,10 +55,7 @@ namespace Shadowsocks.Test { IEncryptor encryptor = (IEncryptor)ector.Invoke(new object[] { method, password }); IEncryptor decryptor = (IEncryptor)dctor.Invoke(new object[] { method, password }); - encryptor.AddressBufferLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; - decryptor.AddressBufferLength = 1 + 4 + 2;// ADDR_ATYP_LEN + 4 + ADDR_PORT_LEN; - - + for (int i = 0; i < 16; i++) { RunEncryptionRound(encryptor, decryptor);