| @@ -1,7 +1,7 @@ | |||
| using System; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using Shadowsocks.Util; | |||
| using Shadowsocks.Util.Sockets; | |||
| namespace Shadowsocks.Controller | |||
| { | |||
| @@ -29,7 +29,7 @@ namespace Shadowsocks.Controller | |||
| private byte[] _firstPacket; | |||
| private int _firstPacketLength; | |||
| private Socket _local; | |||
| private Socket _remote; | |||
| private WrappedSocket _remote; | |||
| private bool _closed = false; | |||
| private bool _localShutdown = false; | |||
| private bool _remoteShutdown = false; | |||
| @@ -46,10 +46,11 @@ namespace Shadowsocks.Controller | |||
| this._local = socket; | |||
| try | |||
| { | |||
| EndPoint remoteEP = SocketUtil.GetEndPoint("localhost", targetPort); | |||
| EndPoint remoteEP = SocketUtil.GetEndPoint("127.0.0.1", targetPort); | |||
| // Connect to the remote endpoint. | |||
| SocketUtil.BeginConnectTcp(remoteEP, ConnectCallback, null); | |||
| _remote = new WrappedSocket(); | |||
| _remote.BeginConnect(remoteEP, ConnectCallback, null); | |||
| } | |||
| catch (Exception e) | |||
| { | |||
| @@ -66,7 +67,8 @@ namespace Shadowsocks.Controller | |||
| } | |||
| try | |||
| { | |||
| _remote = SocketUtil.EndConnectTcp(ar); | |||
| _remote.EndConnect(ar); | |||
| _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
| HandshakeReceive(); | |||
| } | |||
| catch (Exception e) | |||
| @@ -243,7 +245,7 @@ namespace Shadowsocks.Controller | |||
| try | |||
| { | |||
| _remote.Shutdown(SocketShutdown.Both); | |||
| _remote.Close(); | |||
| _remote.Dispose(); | |||
| } | |||
| catch (SocketException e) | |||
| { | |||
| @@ -9,7 +9,7 @@ using Shadowsocks.Controller.Strategy; | |||
| using Shadowsocks.Encryption; | |||
| using Shadowsocks.Model; | |||
| using Shadowsocks.Proxy; | |||
| using Shadowsocks.Util; | |||
| using Shadowsocks.Util.Sockets; | |||
| namespace Shadowsocks.Controller | |||
| { | |||
| @@ -37,7 +37,6 @@ namespace Shadowsocks.Controller | |||
| socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
| TCPHandler handler = new TCPHandler(_controller, _config, this, socket); | |||
| handler.Start(firstPacket, length); | |||
| IList<TCPHandler> handlersToClose = new List<TCPHandler>(); | |||
| lock (Handlers) | |||
| { | |||
| @@ -56,6 +55,15 @@ namespace Shadowsocks.Controller | |||
| Logging.Debug("Closing timed out TCP connection."); | |||
| handler1.Close(); | |||
| } | |||
| /* | |||
| * Start after we put it into Handlers set. Otherwise if it failed in handler.Start() | |||
| * then it will call handler.Close() before we add it into the set. | |||
| * 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); | |||
| return true; | |||
| } | |||
| @@ -165,6 +173,8 @@ namespace Shadowsocks.Controller | |||
| this._config = config; | |||
| this._tcprelay = tcprelay; | |||
| this._connection = socket; | |||
| lastActivity = DateTime.Now; | |||
| } | |||
| public void CreateRemote() | |||
| @@ -187,7 +197,6 @@ namespace Shadowsocks.Controller | |||
| _firstPacket = firstPacket; | |||
| _firstPacketLength = length; | |||
| HandshakeReceive(); | |||
| lastActivity = DateTime.Now; | |||
| } | |||
| private void CheckClose() | |||
| @@ -452,7 +461,7 @@ namespace Shadowsocks.Controller | |||
| timer.Dispose(); | |||
| if (_proxyConnected || _destConnected) | |||
| if (_proxyConnected || _destConnected || _closed) | |||
| { | |||
| return; | |||
| } | |||
| @@ -524,7 +533,7 @@ namespace Shadowsocks.Controller | |||
| timer.Enabled = false; | |||
| timer.Dispose(); | |||
| if (_destConnected) | |||
| if (_destConnected || _closed) | |||
| { | |||
| return; | |||
| } | |||
| @@ -2,7 +2,7 @@ | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using System.Threading; | |||
| using Shadowsocks.Util; | |||
| using Shadowsocks.Util.Sockets; | |||
| namespace Shadowsocks.Proxy | |||
| { | |||
| @@ -31,7 +31,7 @@ namespace Shadowsocks.Proxy | |||
| } | |||
| } | |||
| private Socket _remote; | |||
| private WrappedSocket _remote = new WrappedSocket(); | |||
| public EndPoint LocalEndPoint => _remote.LocalEndPoint; | |||
| @@ -55,12 +55,13 @@ namespace Shadowsocks.Proxy | |||
| { | |||
| DestEndPoint = destEndPoint; | |||
| SocketUtil.BeginConnectTcp(destEndPoint, callback, state); | |||
| _remote.BeginConnect(destEndPoint, callback, state); | |||
| } | |||
| public void EndConnectDest(IAsyncResult asyncResult) | |||
| { | |||
| _remote = SocketUtil.EndConnectTcp(asyncResult); | |||
| _remote.EndConnect(asyncResult); | |||
| _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
| } | |||
| public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, | |||
| @@ -92,7 +93,7 @@ namespace Shadowsocks.Proxy | |||
| public void Close() | |||
| { | |||
| _remote?.Close(); | |||
| _remote?.Dispose(); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,12 +1,10 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using System.Text; | |||
| using System.Threading; | |||
| using Shadowsocks.Controller; | |||
| using Shadowsocks.Util; | |||
| using Shadowsocks.Util.Sockets; | |||
| namespace Shadowsocks.Proxy | |||
| { | |||
| @@ -41,7 +39,7 @@ namespace Shadowsocks.Proxy | |||
| public Exception ex { get; set; } | |||
| } | |||
| private Socket _remote; | |||
| private WrappedSocket _remote = new WrappedSocket(); | |||
| private const int Socks5PktMaxSize = 4 + 16 + 2; | |||
| private readonly byte[] _receiveBuffer = new byte[Socks5PktMaxSize]; | |||
| @@ -58,7 +56,7 @@ namespace Shadowsocks.Proxy | |||
| ProxyEndPoint = remoteEP; | |||
| SocketUtil.BeginConnectTcp(remoteEP, ConnectCallback, st); | |||
| _remote.BeginConnect(remoteEP, ConnectCallback, st); | |||
| } | |||
| public void EndConnectProxy(IAsyncResult asyncResult) | |||
| @@ -168,7 +166,7 @@ namespace Shadowsocks.Proxy | |||
| public void Close() | |||
| { | |||
| _remote?.Close(); | |||
| _remote?.Dispose(); | |||
| } | |||
| @@ -177,7 +175,9 @@ namespace Shadowsocks.Proxy | |||
| var state = (Socks5State) ar.AsyncState; | |||
| try | |||
| { | |||
| _remote = SocketUtil.EndConnectTcp(ar); | |||
| _remote.EndConnect(ar); | |||
| _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
| byte[] handshake = {5, 1, 0}; | |||
| _remote.BeginSend(handshake, 0, handshake.Length, 0, Socks5HandshakeSendCallback, state); | |||
| @@ -1,132 +0,0 @@ | |||
| using System; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| using System.Threading; | |||
| namespace Shadowsocks.Util | |||
| { | |||
| public static class SocketUtil | |||
| { | |||
| private class DnsEndPoint2 : DnsEndPoint | |||
| { | |||
| public DnsEndPoint2(string host, int port) : base(host, port) | |||
| { | |||
| } | |||
| public DnsEndPoint2(string host, int port, AddressFamily addressFamily) : base(host, port, addressFamily) | |||
| { | |||
| } | |||
| public override string ToString() | |||
| { | |||
| return this.Host + ":" + this.Port; | |||
| } | |||
| } | |||
| public static EndPoint GetEndPoint(string host, int port) | |||
| { | |||
| IPAddress ipAddress; | |||
| bool parsed = IPAddress.TryParse(host, out ipAddress); | |||
| if (parsed) | |||
| { | |||
| return new IPEndPoint(ipAddress, port); | |||
| } | |||
| // maybe is a domain name | |||
| return new DnsEndPoint2(host, port); | |||
| } | |||
| private class AutoReleaseAsyncResult : IAsyncResult | |||
| { | |||
| public bool IsCompleted { get; } = true; | |||
| public WaitHandle AsyncWaitHandle { get; } = null; | |||
| public object AsyncState { get; set; } | |||
| public bool CompletedSynchronously { get; } = true; | |||
| public TcpUserToken UserToken { get; set; } | |||
| ~AutoReleaseAsyncResult() | |||
| { | |||
| UserToken.Dispose(); | |||
| } | |||
| } | |||
| private class TcpUserToken | |||
| { | |||
| public AsyncCallback Callback { get; private set; } | |||
| public SocketAsyncEventArgs Args { get; private set; } | |||
| public object AsyncState { get; private set; } | |||
| public TcpUserToken(AsyncCallback callback, object state, SocketAsyncEventArgs args) | |||
| { | |||
| Callback = callback; | |||
| AsyncState = state; | |||
| Args = args; | |||
| } | |||
| public void Dispose() | |||
| { | |||
| Args?.Dispose(); | |||
| Callback = null; | |||
| Args = null; | |||
| AsyncState = null; | |||
| } | |||
| } | |||
| private static void OnTcpConnectCompleted(object sender, SocketAsyncEventArgs args) | |||
| { | |||
| args.Completed -= OnTcpConnectCompleted; | |||
| TcpUserToken token = (TcpUserToken) args.UserToken; | |||
| AutoReleaseAsyncResult r = new AutoReleaseAsyncResult | |||
| { | |||
| AsyncState = token.AsyncState, | |||
| UserToken = token | |||
| }; | |||
| token.Callback(r); | |||
| } | |||
| public static void BeginConnectTcp(EndPoint endPoint, AsyncCallback callback, object state) | |||
| { | |||
| var arg = new SocketAsyncEventArgs(); | |||
| arg.RemoteEndPoint = endPoint; | |||
| arg.Completed += OnTcpConnectCompleted; | |||
| arg.UserToken = new TcpUserToken(callback, state, arg); | |||
| Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, arg); | |||
| } | |||
| public static Socket EndConnectTcp(IAsyncResult asyncResult) | |||
| { | |||
| var r = asyncResult as AutoReleaseAsyncResult; | |||
| if (r == null) | |||
| { | |||
| throw new ArgumentException("Invalid asyncResult.", nameof(asyncResult)); | |||
| } | |||
| var tut = r.UserToken; | |||
| var arg = tut.Args; | |||
| if (arg.SocketError != SocketError.Success) | |||
| { | |||
| if (arg.ConnectByNameError != null) | |||
| { | |||
| throw arg.ConnectByNameError; | |||
| } | |||
| var ex = new SocketException((int)arg.SocketError); | |||
| throw ex; | |||
| } | |||
| var so = tut.Args.ConnectSocket; | |||
| so.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
| return so; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| using System; | |||
| using System.Net; | |||
| using System.Net.Sockets; | |||
| namespace Shadowsocks.Util.Sockets | |||
| { | |||
| public static class SocketUtil | |||
| { | |||
| private class DnsEndPoint2 : DnsEndPoint | |||
| { | |||
| public DnsEndPoint2(string host, int port) : base(host, port) | |||
| { | |||
| } | |||
| public DnsEndPoint2(string host, int port, AddressFamily addressFamily) : base(host, port, addressFamily) | |||
| { | |||
| } | |||
| public override string ToString() | |||
| { | |||
| return this.Host + ":" + this.Port; | |||
| } | |||
| } | |||
| public static EndPoint GetEndPoint(string host, int port) | |||
| { | |||
| IPAddress ipAddress; | |||
| bool parsed = IPAddress.TryParse(host, out ipAddress); | |||
| if (parsed) | |||
| { | |||
| return new IPEndPoint(ipAddress, port); | |||
| } | |||
| // maybe is a domain name | |||
| return new DnsEndPoint2(host, port); | |||
| } | |||
| public static void FullClose(this System.Net.Sockets.Socket s) | |||
| { | |||
| try | |||
| { | |||
| s.Shutdown(SocketShutdown.Both); | |||
| } | |||
| catch (Exception) | |||
| { | |||
| } | |||
| try | |||
| { | |||
| s.Disconnect(false); | |||
| } | |||
| catch (Exception) | |||
| { | |||
| } | |||
| try | |||
| { | |||
| s.Close(); | |||
| } | |||
| catch (Exception) | |||
| { | |||
| } | |||
| try | |||
| { | |||
| s.Dispose(); | |||
| } | |||
| catch (Exception) | |||
| { | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,265 @@ | |||
| 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 volatile 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); | |||
| Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -186,7 +186,8 @@ | |||
| <Compile Include="Util\Hotkeys.cs" /> | |||
| <Compile Include="Util\ProcessManagement\Job.cs" /> | |||
| <Compile Include="Util\ProcessManagement\ThreadUtil.cs" /> | |||
| <Compile Include="Util\SocketUtil.cs" /> | |||
| <Compile Include="Util\Sockets\SocketUtil.cs" /> | |||
| <Compile Include="Util\Sockets\WrappedSocket.cs" /> | |||
| <Compile Include="Util\Util.cs" /> | |||
| <Compile Include="View\ConfigForm.cs"> | |||
| <SubType>Form</SubType> | |||