diff --git a/shadowsocks-csharp/Controller/Service/Listener.cs b/shadowsocks-csharp/Controller/Service/Listener.cs index 1d755eca..9fed2cb7 100644 --- a/shadowsocks-csharp/Controller/Service/Listener.cs +++ b/shadowsocks-csharp/Controller/Service/Listener.cs @@ -116,8 +116,9 @@ namespace Shadowsocks.Controller catch (ObjectDisposedException) { } - catch (Exception) + catch (Exception ex) { + Logging.Debug(ex); } finally { diff --git a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs index cc4d1b35..284f4141 100644 --- a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs +++ b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs @@ -122,6 +122,7 @@ namespace Shadowsocks.Controller * different instance will create their unique "privoxy_UID.conf" where * UID is hash of ss's location. */ + private static bool IsChildProcess(Process process) { if (Utils.IsPortableMode()) @@ -129,8 +130,21 @@ namespace Shadowsocks.Controller /* * Under PortableMode, we could identify it by the path of ss_privoxy.exe. */ - string path = process.MainModule.FileName; - return Utils.GetTempPath("ss_privoxy.exe").Equals(path); + try + { + /* + * Sometimes Process.GetProcessesByName will return some processes that + * are already dead, and that will cause exceptions here. + * We could simply ignore those exceptions. + */ + string path = process.MainModule.FileName; + return Utils.GetTempPath("ss_privoxy.exe").Equals(path); + } + catch (Exception ex) + { + Logging.LogUsefulException(ex); + return false; + } } else { diff --git a/shadowsocks-csharp/Controller/Service/PortForwarder.cs b/shadowsocks-csharp/Controller/Service/PortForwarder.cs index f76a1284..dcac75bf 100644 --- a/shadowsocks-csharp/Controller/Service/PortForwarder.cs +++ b/shadowsocks-csharp/Controller/Service/PortForwarder.cs @@ -48,12 +48,8 @@ namespace Shadowsocks.Controller { EndPoint remoteEP = SocketUtil.GetEndPoint("localhost", targetPort); - _remote = SocketUtil.CreateSocket(remoteEP); - _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - // Connect to the remote endpoint. - _remote.BeginConnect(remoteEP, - new AsyncCallback(ConnectCallback), null); + SocketUtil.BeginConnectTcp(remoteEP, ConnectCallback, null); } catch (Exception e) { @@ -70,7 +66,7 @@ namespace Shadowsocks.Controller } try { - _remote.EndConnect(ar); + _remote = SocketUtil.EndConnectTcp(ar); HandshakeReceive(); } catch (Exception e) diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/shadowsocks-csharp/Controller/Service/TCPRelay.cs index 35f9f9a7..2d86de25 100644 --- a/shadowsocks-csharp/Controller/Service/TCPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/TCPRelay.cs @@ -80,6 +80,33 @@ namespace Shadowsocks.Controller class TCPHandler { + + class AsyncSession + { + public IProxy Remote { get; } + + public AsyncSession(IProxy remote) + { + Remote = remote; + } + } + + 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; + } + } + + // Size of receive buffer. public static readonly int RecvSize = 8192; public static readonly int RecvReserveSize = IVEncryptor.ONETIMEAUTH_BYTES + IVEncryptor.AUTH_BYTES; // reserve for one-time auth @@ -89,7 +116,8 @@ namespace Shadowsocks.Controller public IEncryptor encryptor; public Server server; // Client socket. - public IProxy remote; + private AsyncSession _currentRemoteSession; + public Socket connection; public ShadowsocksController controller; public TCPRelay tcprelay; @@ -177,6 +205,7 @@ namespace Shadowsocks.Controller } try { + var remote = _currentRemoteSession?.Remote; remote?.Shutdown(SocketShutdown.Both); remote?.Close(); } @@ -340,7 +369,8 @@ namespace Shadowsocks.Controller // inner class private class ProxyTimer : Timer { - public IProxy Proxy; + public AsyncSession Session; + public EndPoint DestEndPoint; public Server Server; @@ -351,6 +381,8 @@ namespace Shadowsocks.Controller private class ServerTimer : Timer { + public AsyncSession Session; + public Server Server; public ServerTimer(int p) : base(p) { } } @@ -362,6 +394,7 @@ namespace Shadowsocks.Controller CreateRemote(); // Setting up proxy + IProxy remote; EndPoint proxyEP; if (_config.useProxy) { @@ -374,20 +407,22 @@ namespace Shadowsocks.Controller proxyEP = null; } + var session = new AsyncSession(remote); + _currentRemoteSession = session; ProxyTimer proxyTimer = new ProxyTimer(3000); proxyTimer.AutoReset = false; proxyTimer.Elapsed += proxyConnectTimer_Elapsed; proxyTimer.Enabled = true; - proxyTimer.Proxy = remote; + proxyTimer.Session = session; proxyTimer.DestEndPoint = SocketUtil.GetEndPoint(server.server, server.server_port); proxyTimer.Server = server; _proxyConnected = false; // Connect to the proxy server. - remote.BeginConnectProxy(proxyEP, new AsyncCallback(ProxyConnectCallback), proxyTimer); + remote.BeginConnectProxy(proxyEP, new AsyncCallback(ProxyConnectCallback), new AsyncSession(remote, proxyTimer)); } catch (Exception e) { @@ -402,10 +437,10 @@ namespace Shadowsocks.Controller { return; } - var proxy = ((ProxyTimer)sender).Proxy; + var proxy = ((ProxyTimer)sender).Session.Remote; Logging.Info($"Proxy {proxy.ProxyEndPoint} timed out"); - remote?.Close(); + proxy.Close(); RetryConnect(); } @@ -418,13 +453,16 @@ namespace Shadowsocks.Controller } try { - ProxyTimer timer = (ProxyTimer)ar.AsyncState; + var session = (AsyncSession) ar.AsyncState; + ProxyTimer timer = session.State; var destEndPoint = timer.DestEndPoint; server = timer.Server; timer.Elapsed -= proxyConnectTimer_Elapsed; timer.Enabled = false; timer.Dispose(); + var remote = session.Remote; + // Complete the connection. remote.EndConnectProxy(ar); @@ -443,11 +481,12 @@ namespace Shadowsocks.Controller connectTimer.AutoReset = false; connectTimer.Elapsed += destConnectTimer_Elapsed; connectTimer.Enabled = true; + connectTimer.Session = session; connectTimer.Server = server; _destConnected = false; // Connect to the remote endpoint. - remote.BeginConnectDest(destEndPoint, new AsyncCallback(ConnectCallback), connectTimer); + remote.BeginConnectDest(destEndPoint, new AsyncCallback(ConnectCallback), new AsyncSession(session, connectTimer)); } catch (ArgumentException) { @@ -466,11 +505,12 @@ namespace Shadowsocks.Controller return; } + var session = ((ServerTimer) sender).Session; Server server = ((ServerTimer)sender).Server; IStrategy strategy = controller.GetCurrentStrategy(); strategy?.SetFailure(server); Logging.Info($"{server.FriendlyName()} timed out"); - remote?.Close(); + session.Remote.Close(); RetryConnect(); } @@ -491,12 +531,14 @@ namespace Shadowsocks.Controller if (_closed) return; try { - ServerTimer timer = (ServerTimer)ar.AsyncState; + var session = (AsyncSession) ar.AsyncState; + ServerTimer timer = session.State; server = timer.Server; timer.Elapsed -= destConnectTimer_Elapsed; timer.Enabled = false; timer.Dispose(); + var remote = session.Remote; // Complete the connection. remote?.EndConnectDest(ar); @@ -512,7 +554,7 @@ namespace Shadowsocks.Controller strategy?.UpdateLatency(server, latency); _tcprelay.UpdateLatency(server, latency); - StartPipe(); + StartPipe(session); } catch (ArgumentException) { @@ -529,14 +571,15 @@ namespace Shadowsocks.Controller } } - private void StartPipe() + private void StartPipe(AsyncSession session) { if (_closed) return; try { _startReceivingTime = DateTime.Now; - remote?.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeRemoteReceiveCallback), null); - connection?.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeConnectionReceiveCallback), null); + session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeRemoteReceiveCallback), session); + connection?.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeConnectionReceiveCallback), + new AsyncSession(session, true) /* to tell the callback this is the first time reading packet, and we haven't found the header yet. */); } catch (Exception e) { @@ -550,8 +593,8 @@ namespace Shadowsocks.Controller if (_closed) return; try { - if ( remote == null ) return; - int bytesRead = remote.EndReceive(ar); + var session = (AsyncSession) ar.AsyncState; + int bytesRead = session.Remote.EndReceive(ar); _totalRead += bytesRead; _tcprelay.UpdateInboundCounter(server, bytesRead); if (bytesRead > 0) @@ -563,7 +606,7 @@ namespace Shadowsocks.Controller if (_closed) return; encryptor.Decrypt(_remoteRecvBuffer, bytesRead, _remoteSendBuffer, out bytesToSend); } - connection.BeginSend(_remoteSendBuffer, 0, bytesToSend, SocketFlags.None, new AsyncCallback(PipeConnectionSendCallback), null); + connection.BeginSend(_remoteSendBuffer, 0, bytesToSend, SocketFlags.None, new AsyncCallback(PipeConnectionSendCallback), session); IStrategy strategy = controller.GetCurrentStrategy(); strategy?.UpdateLastRead(server); } @@ -589,36 +632,48 @@ namespace Shadowsocks.Controller if(connection == null) return; int bytesRead = connection.EndReceive(ar); _totalWrite += bytesRead; + + var session = (AsyncSession) ar.AsyncState; + var remote = session.Remote; + if (bytesRead > 0) { - int atyp = _connetionRecvBuffer[0]; - string dst_addr; - int dst_port; - switch (atyp) + /* + * Only the first packet contains the socks5 header, it doesn't make sense to parse every packets. + * Also it's unnecessary to parse these data if we turn off the VerboseLogging. + */ + if (session.State && _config.isVerboseLogging) { - case 1: // IPv4 address, 4 bytes - dst_addr = new IPAddress(_connetionRecvBuffer.Skip(1).Take(4).ToArray()).ToString(); - dst_port = (_connetionRecvBuffer[5] << 8) + _connetionRecvBuffer[6]; - if ( _config.isVerboseLogging ) { - Logging.Info( $"connect to {dst_addr}:{dst_port}" ); - } - break; - case 3: // domain name, length + str - int len = _connetionRecvBuffer[1]; - dst_addr = System.Text.Encoding.UTF8.GetString(_connetionRecvBuffer, 2, len); - dst_port = (_connetionRecvBuffer[len + 2] << 8) + _connetionRecvBuffer[len + 3]; - if ( _config.isVerboseLogging ) { - Logging.Info( $"connect to {dst_addr}:{dst_port}" ); - } - break; - case 4: // IPv6 address, 16 bytes - dst_addr = new IPAddress(_connetionRecvBuffer.Skip(1).Take(16).ToArray()).ToString(); - dst_port = (_connetionRecvBuffer[17] << 8) + _connetionRecvBuffer[18]; - if ( _config.isVerboseLogging ) { - Logging.Info( $"connect to [{dst_addr}]:{dst_port}" ); - } - break; + int atyp = _connetionRecvBuffer[0]; + string dst_addr; + int dst_port; + switch (atyp) + { + case 1: // IPv4 address, 4 bytes + dst_addr = new IPAddress(_connetionRecvBuffer.Skip(1).Take(4).ToArray()).ToString(); + dst_port = (_connetionRecvBuffer[5] << 8) + _connetionRecvBuffer[6]; + + Logging.Info($"connect to {dst_addr}:{dst_port}"); + session.State = false; + break; + case 3: // domain name, length + str + int len = _connetionRecvBuffer[1]; + dst_addr = System.Text.Encoding.UTF8.GetString(_connetionRecvBuffer, 2, len); + dst_port = (_connetionRecvBuffer[len + 2] << 8) + _connetionRecvBuffer[len + 3]; + + Logging.Info($"connect to {dst_addr}:{dst_port}"); + session.State = false; + break; + case 4: // IPv6 address, 16 bytes + dst_addr = new IPAddress(_connetionRecvBuffer.Skip(1).Take(16).ToArray()).ToString(); + dst_port = (_connetionRecvBuffer[17] << 8) + _connetionRecvBuffer[18]; + + Logging.Info($"connect to [{dst_addr}]:{dst_port}"); + session.State = false; + break; + } } + int bytesToSend; lock (_encryptionLock) { @@ -628,7 +683,7 @@ namespace Shadowsocks.Controller _tcprelay.UpdateOutboundCounter(server, bytesToSend); _startSendingTime = DateTime.Now; _bytesToSend = bytesToSend; - remote.BeginSend(_connetionSendBuffer, 0, bytesToSend, SocketFlags.None, new AsyncCallback(PipeRemoteSendCallback), null); + remote.BeginSend(_connetionSendBuffer, 0, bytesToSend, SocketFlags.None, new AsyncCallback(PipeRemoteSendCallback), session); IStrategy strategy = controller.GetCurrentStrategy(); strategy?.UpdateLastWrite(server); } @@ -651,8 +706,9 @@ namespace Shadowsocks.Controller if (_closed) return; try { - remote?.EndSend(ar); - connection?.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeConnectionReceiveCallback), null); + var session = (AsyncSession)ar.AsyncState; + session.Remote.EndSend(ar); + connection?.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeConnectionReceiveCallback), session); } catch (Exception e) { @@ -666,8 +722,9 @@ namespace Shadowsocks.Controller if (_closed) return; try { + var session = (AsyncSession)ar.AsyncState; connection?.EndSend(ar); - remote?.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeRemoteReceiveCallback), null); + session.Remote.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeRemoteReceiveCallback), session); } catch (Exception e) { diff --git a/shadowsocks-csharp/Controller/Service/UDPRelay.cs b/shadowsocks-csharp/Controller/Service/UDPRelay.cs index 0a5a18ac..5f0d2363 100644 --- a/shadowsocks-csharp/Controller/Service/UDPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/UDPRelay.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using Shadowsocks.Controller.Strategy; using Shadowsocks.Encryption; using Shadowsocks.Model; -using Shadowsocks.Util; namespace Shadowsocks.Controller { @@ -57,7 +56,7 @@ namespace Shadowsocks.Controller private byte[] _buffer = new byte[1500]; private IPEndPoint _localEndPoint; - private EndPoint _remoteEndPoint; + private IPEndPoint _remoteEndPoint; public UDPHandler(Socket local, Server server, IPEndPoint localEndPoint) { @@ -65,8 +64,16 @@ namespace Shadowsocks.Controller _server = server; _localEndPoint = localEndPoint; - _remoteEndPoint = SocketUtil.GetEndPoint(server.server, server.server_port); - _remote = SocketUtil.CreateSocket(_remoteEndPoint, ProtocolType.Udp); + // TODO async resolving + IPAddress ipAddress; + bool parsed = IPAddress.TryParse(server.server, out ipAddress); + if (!parsed) + { + IPHostEntry ipHostInfo = Dns.GetHostEntry(server.server); + ipAddress = ipHostInfo.AddressList[0]; + } + _remoteEndPoint = new IPEndPoint(ipAddress, server.server_port); + _remote = new Socket(_remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); } public void Send(byte[] data, int length) diff --git a/shadowsocks-csharp/Proxy/DirectConnect.cs b/shadowsocks-csharp/Proxy/DirectConnect.cs index 487adc41..76bdbc77 100644 --- a/shadowsocks-csharp/Proxy/DirectConnect.cs +++ b/shadowsocks-csharp/Proxy/DirectConnect.cs @@ -55,17 +55,12 @@ namespace Shadowsocks.Proxy { DestEndPoint = destEndPoint; - if (_remote == null) - { - _remote = SocketUtil.CreateSocket(destEndPoint); - _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - } - _remote.BeginConnect(destEndPoint, callback, state); + SocketUtil.BeginConnectTcp(destEndPoint, callback, state); } public void EndConnectDest(IAsyncResult asyncResult) { - _remote?.EndConnect(asyncResult); + _remote = SocketUtil.EndConnectTcp(asyncResult); } public void BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, diff --git a/shadowsocks-csharp/Proxy/Socks5Proxy.cs b/shadowsocks-csharp/Proxy/Socks5Proxy.cs index adb94402..4ee6917c 100644 --- a/shadowsocks-csharp/Proxy/Socks5Proxy.cs +++ b/shadowsocks-csharp/Proxy/Socks5Proxy.cs @@ -52,16 +52,13 @@ namespace Shadowsocks.Proxy public void BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state) { - _remote = SocketUtil.CreateSocket(remoteEP); - _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - var st = new Socks5State(); st.Callback = callback; st.AsyncState = state; ProxyEndPoint = remoteEP; - _remote.BeginConnect(remoteEP, ConnectCallback, st); + SocketUtil.BeginConnectTcp(remoteEP, ConnectCallback, st); } public void EndConnectProxy(IAsyncResult asyncResult) @@ -180,7 +177,7 @@ namespace Shadowsocks.Proxy var state = (Socks5State) ar.AsyncState; try { - _remote.EndConnect(ar); + _remote = SocketUtil.EndConnectTcp(ar); byte[] handshake = {5, 1, 0}; _remote.BeginSend(handshake, 0, handshake.Length, 0, Socks5HandshakeSendCallback, state); diff --git a/shadowsocks-csharp/Util/SocketUtil.cs b/shadowsocks-csharp/Util/SocketUtil.cs index 46e18aa1..d7543b5b 100644 --- a/shadowsocks-csharp/Util/SocketUtil.cs +++ b/shadowsocks-csharp/Util/SocketUtil.cs @@ -1,6 +1,7 @@ using System; using System.Net; using System.Net.Sockets; +using System.Threading; namespace Shadowsocks.Util { @@ -35,33 +36,68 @@ namespace Shadowsocks.Util return new DnsEndPoint2(host, port); } - public static Socket CreateSocket(EndPoint endPoint, ProtocolType protocolType = ProtocolType.Tcp) + private class TcpUserToken : IAsyncResult { - SocketType socketType; - switch (protocolType) + public AsyncCallback Callback { get; } + public SocketAsyncEventArgs Args { get; } + + public TcpUserToken(AsyncCallback callback, object state, SocketAsyncEventArgs args) { - case ProtocolType.Tcp: - socketType = SocketType.Stream; - break; - case ProtocolType.Udp: - socketType = SocketType.Dgram; - break; - default: - throw new NotSupportedException("Protocol " + protocolType + " doesn't supported!"); + Callback = callback; + AsyncState = state; + Args = args; } - if (endPoint is DnsEndPoint) - { - // use dual-mode socket - var socket = new Socket(AddressFamily.InterNetworkV6, socketType, protocolType); - socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false); + public bool IsCompleted { get; } = true; + public WaitHandle AsyncWaitHandle { get; } = null; + public object AsyncState { get; } + public bool CompletedSynchronously { get; } = true; + } + + private static void OnTcpConnectCompleted(object sender, SocketAsyncEventArgs args) + { + TcpUserToken token = (TcpUserToken) args.UserToken; + + token.Callback(token); + } + + 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); + - return socket; + Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, arg); + } + + public static Socket EndConnectTcp(IAsyncResult asyncResult) + { + var tut = asyncResult as TcpUserToken; + if (tut == null) + { + throw new ArgumentException("Invalid asyncResult.", nameof(asyncResult)); } - else + + var arg = tut.Args; + + if (arg.SocketError != SocketError.Success) { - return new Socket(endPoint.AddressFamily, socketType, protocolType); + 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; } } } diff --git a/shadowsocks-csharp/app.manifest b/shadowsocks-csharp/app.manifest index 3cffa5a2..529b1a7c 100755 --- a/shadowsocks-csharp/app.manifest +++ b/shadowsocks-csharp/app.manifest @@ -13,4 +13,17 @@ True/PM + + + + + + + + + + + + + \ No newline at end of file