diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/shadowsocks-csharp/Controller/Service/TCPRelay.cs index 9c162780..31def20c 100644 --- a/shadowsocks-csharp/Controller/Service/TCPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/TCPRelay.cs @@ -7,6 +7,7 @@ using System.Timers; using Shadowsocks.Controller.Strategy; using Shadowsocks.Encryption; using Shadowsocks.Model; +using Shadowsocks.Proxy; namespace Shadowsocks.Controller { @@ -14,15 +15,17 @@ namespace Shadowsocks.Controller { private ShadowsocksController _controller; private DateTime _lastSweepTime; + private Configuration _config; public ISet Handlers { get; set; } - public TCPRelay(ShadowsocksController controller) + public TCPRelay(ShadowsocksController controller, Configuration conf) { _controller = controller; + _config = conf; Handlers = new HashSet(); _lastSweepTime = DateTime.Now; } @@ -91,7 +94,7 @@ namespace Shadowsocks.Controller public IEncryptor encryptor; public Server server; // Client socket. - public Socket remote; + public IProxy remote; public Socket connection; public ShadowsocksController controller; public TCPRelay relay; @@ -100,7 +103,8 @@ namespace Shadowsocks.Controller private const int maxRetry = 4; private int retryCount = 0; - private bool connected; + private bool proxyConnected; + private bool destConnected; private byte command; private byte[] _firstPacket; @@ -345,7 +349,7 @@ namespace Shadowsocks.Controller if (ar.AsyncState != null) { connection.EndSend(ar); - Logging.Debug(remote, RecvSize, "TCP Relay"); + Logging.Debug(remote.LocalEndPoint, remote.DestEndPoint, RecvSize, "TCP Relay"); connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(ReadAll), null); } else @@ -353,7 +357,7 @@ namespace Shadowsocks.Controller int bytesRead = connection.EndReceive(ar); if (bytesRead > 0) { - Logging.Debug(remote, RecvSize, "TCP Relay"); + Logging.Debug(remote.LocalEndPoint, remote.DestEndPoint, RecvSize, "TCP Relay"); connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(ReadAll), null); } else @@ -386,6 +390,16 @@ namespace Shadowsocks.Controller } // inner class + private class ProxyTimer : Timer + { + public EndPoint DestEndPoint; + public Server Server; + + public ProxyTimer(int p) : base(p) + { + } + } + private class ServerTimer : Timer { public Server Server; @@ -411,31 +425,88 @@ namespace Shadowsocks.Controller } IPEndPoint remoteEP = new IPEndPoint(ipAddress, server.server_port); - remote = new Socket(ipAddress.AddressFamily, - SocketType.Stream, ProtocolType.Tcp); - remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); + remote = new DirectConnect(); + + ProxyTimer proxyTimer = new ProxyTimer(3000); + proxyTimer.AutoReset = false; + proxyTimer.Elapsed += proxyConnectTimer_Elapsed; + proxyTimer.Enabled = true; + proxyTimer.DestEndPoint = remoteEP; + proxyTimer.Server = server; + + proxyConnected = false; + + // Connect to the proxy server. + remote.BeginConnectProxy(remoteEP, new AsyncCallback(ProxyConnectCallback), proxyTimer); + } + catch (Exception e) + { + Logging.LogUsefulException(e); + Close(); + } + } + + private void proxyConnectTimer_Elapsed(object sender, ElapsedEventArgs e) + { + if (proxyConnected || destConnected) + { + return; + } + var ep = ((ProxyTimer)sender).DestEndPoint; + + Logging.Info($"Proxy {ep} timed out"); + remote.Close(); + RetryConnect(); + } + + private void ProxyConnectCallback(IAsyncResult ar) + { + Server server = null; + if (closed) + { + return; + } + try + { + ProxyTimer timer = (ProxyTimer)ar.AsyncState; + var destEP = timer.DestEndPoint; + server = timer.Server; + timer.Elapsed -= proxyConnectTimer_Elapsed; + timer.Enabled = false; + timer.Dispose(); + + // Complete the connection. + remote.EndConnectProxy(ar); + + proxyConnected = true; + + Logging.Debug($"Socket connected to proxy {remote.ProxyEndPoint}"); + _startConnectTime = DateTime.Now; ServerTimer connectTimer = new ServerTimer(3000); connectTimer.AutoReset = false; - connectTimer.Elapsed += connectTimer_Elapsed; + connectTimer.Elapsed += destConnectTimer_Elapsed; connectTimer.Enabled = true; connectTimer.Server = server; - connected = false; + destConnected = false; // Connect to the remote endpoint. - remote.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), connectTimer); + remote.BeginConnectDest(destEP, new AsyncCallback(ConnectCallback), connectTimer); + } + catch (ArgumentException) + { } catch (Exception e) { Logging.LogUsefulException(e); - Close(); + RetryConnect(); } } - private void connectTimer_Elapsed(object sender, ElapsedEventArgs e) + private void destConnectTimer_Elapsed(object sender, ElapsedEventArgs e) { - if (connected) + if (destConnected) { return; } @@ -475,16 +546,16 @@ namespace Shadowsocks.Controller { ServerTimer timer = (ServerTimer)ar.AsyncState; server = timer.Server; - timer.Elapsed -= connectTimer_Elapsed; + timer.Elapsed -= destConnectTimer_Elapsed; timer.Enabled = false; timer.Dispose(); // Complete the connection. - remote.EndConnect(ar); + remote.EndConnectDest(ar); - connected = true; + destConnected = true; - Logging.Debug($"Socket connected to {remote.RemoteEndPoint}"); + Logging.Debug($"Socket connected to {remote.DestEndPoint}"); var latency = DateTime.Now - _startConnectTime; IStrategy strategy = controller.GetCurrentStrategy(); @@ -554,7 +625,7 @@ namespace Shadowsocks.Controller } encryptor.Decrypt(remoteRecvBuffer, bytesRead, remoteSendBuffer, out bytesToSend); } - Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeRemoteReceiveCallback() (download)"); + Logging.Debug(remote.LocalEndPoint, remote.DestEndPoint, bytesToSend, "TCP Relay", "@PipeRemoteReceiveCallback() (download)"); connection.BeginSend(remoteSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeConnectionSendCallback), null); IStrategy strategy = controller.GetCurrentStrategy(); @@ -606,7 +677,7 @@ namespace Shadowsocks.Controller } encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend); } - Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeConnectionReceiveCallback() (upload)"); + Logging.Debug(remote.LocalEndPoint, remote.DestEndPoint, bytesToSend, "TCP Relay", "@PipeConnectionReceiveCallback() (upload)"); tcprelay.UpdateOutboundCounter(server, bytesToSend); _startSendingTime = DateTime.Now; _bytesToSend = bytesToSend; diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index b4c80379..bbe880e3 100755 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -394,7 +394,7 @@ namespace Shadowsocks.Controller polipoRunner.Start(_config); - TCPRelay tcpRelay = new TCPRelay(this); + TCPRelay tcpRelay = new TCPRelay(this, _config); UDPRelay udpRelay = new UDPRelay(this); List services = new List(); services.Add(tcpRelay); diff --git a/shadowsocks-csharp/Proxy/DirectConnect.cs b/shadowsocks-csharp/Proxy/DirectConnect.cs new file mode 100644 index 00000000..48af6ac7 --- /dev/null +++ b/shadowsocks-csharp/Proxy/DirectConnect.cs @@ -0,0 +1,98 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Shadowsocks.Proxy +{ + public class DirectConnect : IProxy + { + private class FakeAsyncResult : IAsyncResult + { + public FakeAsyncResult(object state) + { + AsyncState = state; + } + + public bool IsCompleted { get; } = true; + public WaitHandle AsyncWaitHandle { get; } = null; + public object AsyncState { get; } + public bool CompletedSynchronously { get; } = true; + } + + private Socket _remote; + + public EndPoint LocalEndPoint => _remote.LocalEndPoint; + + public EndPoint ProxyEndPoint { get; private set; } + + public EndPoint DestEndPoint { get; private set; } + + + public IAsyncResult BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state) + { + // do nothing + ProxyEndPoint = remoteEP; + + var r = new FakeAsyncResult(state); + callback?.Invoke(r); + + return r; + } + + public void EndConnectProxy(IAsyncResult asyncResult) + { + // do nothing + } + + public IAsyncResult BeginConnectDest(EndPoint remoteEP, AsyncCallback callback, object state) + { + if (_remote == null) + { + _remote = new Socket(remoteEP.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + _remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); + } + + DestEndPoint = remoteEP; + + return _remote.BeginConnect(remoteEP, callback, state); + } + + public void EndConnectDest(IAsyncResult asyncResult) + { + _remote.EndConnect(asyncResult); + } + + public IAsyncResult BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, + object state) + { + return _remote.BeginSend(buffer, offset, size, socketFlags, callback, state); + } + + public int EndSend(IAsyncResult asyncResult) + { + return _remote.EndSend(asyncResult); + } + + public IAsyncResult BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, + object state) + { + return _remote.BeginReceive(buffer, offset, size, socketFlags, callback, state); + } + + public int EndReceive(IAsyncResult asyncResult) + { + return _remote.EndReceive(asyncResult); + } + + public void Shutdown(SocketShutdown how) + { + _remote?.Shutdown(how); + } + + public void Close() + { + _remote?.Close(); + } + } +} diff --git a/shadowsocks-csharp/Proxy/IProxy.cs b/shadowsocks-csharp/Proxy/IProxy.cs new file mode 100644 index 00000000..39a7c3e7 --- /dev/null +++ b/shadowsocks-csharp/Proxy/IProxy.cs @@ -0,0 +1,38 @@ +using System; +using System.Net; +using System.Net.Sockets; + +namespace Shadowsocks.Proxy +{ + + public interface IProxy + { + EndPoint LocalEndPoint { get; } + + EndPoint ProxyEndPoint { get; } + + EndPoint DestEndPoint { get; } + + IAsyncResult BeginConnectProxy(EndPoint remoteEP, AsyncCallback callback, object state); + + void EndConnectProxy(IAsyncResult asyncResult); + + IAsyncResult BeginConnectDest(EndPoint remoteEP, AsyncCallback callback, object state); + + void EndConnectDest(IAsyncResult asyncResult); + + IAsyncResult BeginSend(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, + object state); + + int EndSend(IAsyncResult asyncResult); + + IAsyncResult BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, + object state); + + int EndReceive(IAsyncResult asyncResult); + + void Shutdown(SocketShutdown how); + + void Close(); + } +} diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index d339b634..3b0adae2 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -170,6 +170,8 @@ + +