At that time the remote even doesn't exist. Also this debug output doesn't make any sense because when handling udp request, this tcp connection won't have any meaningful data. So I simply removed these two lines. Signed-off-by: noisyfox <timemanager.rick@gmail.com>
680 lines
24 KiB
C#
680 lines
24 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Timers;
|
|
|
|
using Shadowsocks.Controller.Strategy;
|
|
using Shadowsocks.Encryption;
|
|
using Shadowsocks.Model;
|
|
using Shadowsocks.Proxy;
|
|
using Shadowsocks.Util;
|
|
|
|
namespace Shadowsocks.Controller
|
|
{
|
|
class TCPRelay : Listener.Service
|
|
{
|
|
private ShadowsocksController _controller;
|
|
private DateTime _lastSweepTime;
|
|
private Configuration _config;
|
|
|
|
public ISet<TCPHandler> Handlers { get; set; }
|
|
|
|
public TCPRelay(ShadowsocksController controller, Configuration conf)
|
|
{
|
|
_controller = controller;
|
|
_config = conf;
|
|
Handlers = new HashSet<TCPHandler>();
|
|
_lastSweepTime = DateTime.Now;
|
|
}
|
|
|
|
public bool Handle(byte[] firstPacket, int length, Socket socket, object state)
|
|
{
|
|
if (socket.ProtocolType != ProtocolType.Tcp
|
|
|| (length < 2 || firstPacket[0] != 5))
|
|
return false;
|
|
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
|
|
TCPHandler handler = new TCPHandler(this, _config);
|
|
handler.connection = socket;
|
|
handler.controller = _controller;
|
|
handler.tcprelay = this;
|
|
|
|
handler.Start(firstPacket, length);
|
|
IList<TCPHandler> handlersToClose = new List<TCPHandler>();
|
|
lock (Handlers)
|
|
{
|
|
Handlers.Add(handler);
|
|
DateTime now = DateTime.Now;
|
|
if (now - _lastSweepTime > TimeSpan.FromSeconds(1))
|
|
{
|
|
_lastSweepTime = now;
|
|
foreach (TCPHandler handler1 in Handlers)
|
|
if (now - handler1.lastActivity > TimeSpan.FromSeconds(900))
|
|
handlersToClose.Add(handler1);
|
|
}
|
|
}
|
|
foreach (TCPHandler handler1 in handlersToClose)
|
|
{
|
|
Logging.Debug("Closing timed out TCP connection.");
|
|
handler1.Close();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void UpdateInboundCounter(Server server, long n)
|
|
{
|
|
_controller.UpdateInboundCounter(server, n);
|
|
}
|
|
|
|
public void UpdateOutboundCounter(Server server, long n)
|
|
{
|
|
_controller.UpdateOutboundCounter(server, n);
|
|
}
|
|
|
|
public void UpdateLatency(Server server, TimeSpan latency)
|
|
{
|
|
_controller.UpdateLatency(server, latency);
|
|
}
|
|
}
|
|
|
|
class TCPHandler
|
|
{
|
|
// 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
|
|
public static readonly int BufferSize = RecvSize + RecvReserveSize + 32;
|
|
|
|
// public Encryptor encryptor;
|
|
public IEncryptor encryptor;
|
|
public Server server;
|
|
// Client socket.
|
|
public IProxy remote;
|
|
public Socket connection;
|
|
public ShadowsocksController controller;
|
|
public TCPRelay tcprelay;
|
|
|
|
public DateTime lastActivity;
|
|
|
|
private const int MaxRetry = 4;
|
|
private int _retryCount = 0;
|
|
private bool _proxyConnected;
|
|
private bool _destConnected;
|
|
|
|
private byte _command;
|
|
private byte[] _firstPacket;
|
|
private int _firstPacketLength;
|
|
|
|
private int _totalRead = 0;
|
|
private int _totalWrite = 0;
|
|
|
|
private byte[] _remoteRecvBuffer = new byte[BufferSize];
|
|
private byte[] _remoteSendBuffer = new byte[BufferSize];
|
|
private byte[] _connetionRecvBuffer = new byte[BufferSize];
|
|
private byte[] _connetionSendBuffer = new byte[BufferSize];
|
|
|
|
private bool _connectionShutdown = false;
|
|
private bool _remoteShutdown = false;
|
|
private bool _closed = false;
|
|
|
|
private object _encryptionLock = new object();
|
|
private object _decryptionLock = new object();
|
|
|
|
private DateTime _startConnectTime;
|
|
private DateTime _startReceivingTime;
|
|
private DateTime _startSendingTime;
|
|
private int _bytesToSend;
|
|
private TCPRelay _tcprelay; // TODO: is _tcprelay equals tcprelay declared above?
|
|
private Configuration _config;
|
|
|
|
public TCPHandler(TCPRelay tcprelay, Configuration config)
|
|
{
|
|
this._tcprelay = tcprelay;
|
|
this._config = config;
|
|
}
|
|
|
|
public void CreateRemote()
|
|
{
|
|
Server server = controller.GetAServer(IStrategyCallerType.TCP, (IPEndPoint)connection.RemoteEndPoint);
|
|
if (server == null || server.server == "")
|
|
throw new ArgumentException("No server configured");
|
|
encryptor = EncryptorFactory.GetEncryptor(server.method, server.password, server.auth, false);
|
|
this.server = server;
|
|
}
|
|
|
|
public void Start(byte[] firstPacket, int length)
|
|
{
|
|
_firstPacket = firstPacket;
|
|
_firstPacketLength = length;
|
|
HandshakeReceive();
|
|
lastActivity = DateTime.Now;
|
|
}
|
|
|
|
private void CheckClose()
|
|
{
|
|
if (_connectionShutdown && _remoteShutdown)
|
|
Close();
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
lock (tcprelay.Handlers)
|
|
{
|
|
tcprelay.Handlers.Remove(this);
|
|
}
|
|
lock (this) {
|
|
if (_closed) return;
|
|
_closed = true;
|
|
}
|
|
try
|
|
{
|
|
connection?.Shutdown(SocketShutdown.Both);
|
|
connection?.Close();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
}
|
|
try
|
|
{
|
|
remote?.Shutdown(SocketShutdown.Both);
|
|
remote?.Close();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
}
|
|
lock (_encryptionLock)
|
|
{
|
|
lock (_decryptionLock)
|
|
{
|
|
encryptor?.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HandshakeReceive()
|
|
{
|
|
if (_closed) return;
|
|
try
|
|
{
|
|
int bytesRead = _firstPacketLength;
|
|
if (bytesRead > 1)
|
|
{
|
|
byte[] response = { 5, 0 };
|
|
if (_firstPacket[0] != 5)
|
|
{
|
|
// reject socks 4
|
|
response = new byte[] { 0, 91 };
|
|
Logging.Error("socks 5 protocol error");
|
|
}
|
|
connection?.BeginSend(response, 0, response.Length, SocketFlags.None, new AsyncCallback(HandshakeSendCallback), null);
|
|
}
|
|
else
|
|
Close();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void HandshakeSendCallback(IAsyncResult ar)
|
|
{
|
|
if (_closed) return;
|
|
try
|
|
{
|
|
connection.EndSend(ar);
|
|
|
|
// +-----+-----+-------+------+----------+----------+
|
|
// | VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|
|
// +-----+-----+-------+------+----------+----------+
|
|
// | 1 | 1 | X'00' | 1 | Variable | 2 |
|
|
// +-----+-----+-------+------+----------+----------+
|
|
// Skip first 3 bytes
|
|
// TODO validate
|
|
connection.BeginReceive(_connetionRecvBuffer, 0, 3, SocketFlags.None, new AsyncCallback(handshakeReceive2Callback), null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void handshakeReceive2Callback(IAsyncResult ar)
|
|
{
|
|
if (_closed) return;
|
|
try
|
|
{
|
|
int bytesRead = connection.EndReceive(ar);
|
|
if (bytesRead >= 3)
|
|
{
|
|
_command = _connetionRecvBuffer[1];
|
|
if (_command == 1)
|
|
{
|
|
byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
|
|
connection.BeginSend(response, 0, response.Length, SocketFlags.None, new AsyncCallback(ResponseCallback), null);
|
|
}
|
|
else if (_command == 3)
|
|
HandleUDPAssociate();
|
|
}
|
|
else
|
|
{
|
|
Logging.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()");
|
|
Close();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void HandleUDPAssociate()
|
|
{
|
|
IPEndPoint endPoint = (IPEndPoint)connection.LocalEndPoint;
|
|
byte[] address = endPoint.Address.GetAddressBytes();
|
|
int port = endPoint.Port;
|
|
byte[] response = new byte[4 + address.Length + 2];
|
|
response[0] = 5;
|
|
switch (endPoint.AddressFamily)
|
|
{
|
|
case AddressFamily.InterNetwork:
|
|
response[3] = 1;
|
|
break;
|
|
case AddressFamily.InterNetworkV6:
|
|
response[3] = 4;
|
|
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, new AsyncCallback(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, new AsyncCallback(ReadAll), null);
|
|
}
|
|
else
|
|
{
|
|
int bytesRead = connection.EndReceive(ar);
|
|
if (bytesRead > 0)
|
|
{
|
|
connection.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(ReadAll), null);
|
|
}
|
|
else
|
|
Close();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void ResponseCallback(IAsyncResult ar)
|
|
{
|
|
try
|
|
{
|
|
connection?.EndSend(ar);
|
|
StartConnect();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
// inner class
|
|
private class ProxyTimer : Timer
|
|
{
|
|
public IProxy Proxy;
|
|
public EndPoint DestEndPoint;
|
|
public Server Server;
|
|
|
|
public ProxyTimer(int p) : base(p)
|
|
{
|
|
}
|
|
}
|
|
|
|
private class ServerTimer : Timer
|
|
{
|
|
public Server Server;
|
|
public ServerTimer(int p) : base(p) { }
|
|
}
|
|
|
|
private void StartConnect()
|
|
{
|
|
try
|
|
{
|
|
CreateRemote();
|
|
|
|
// Setting up proxy
|
|
EndPoint proxyEP;
|
|
if (_config.useProxy)
|
|
{
|
|
remote = new Socks5Proxy();
|
|
proxyEP = SocketUtil.GetEndPoint(_config.proxyServer, _config.proxyPort);
|
|
}
|
|
else
|
|
{
|
|
remote = new DirectConnect();
|
|
proxyEP = null;
|
|
}
|
|
|
|
|
|
ProxyTimer proxyTimer = new ProxyTimer(3000);
|
|
proxyTimer.AutoReset = false;
|
|
proxyTimer.Elapsed += proxyConnectTimer_Elapsed;
|
|
proxyTimer.Enabled = true;
|
|
|
|
proxyTimer.Proxy = remote;
|
|
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);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void proxyConnectTimer_Elapsed(object sender, ElapsedEventArgs e)
|
|
{
|
|
if (_proxyConnected || _destConnected)
|
|
{
|
|
return;
|
|
}
|
|
var proxy = ((ProxyTimer)sender).Proxy;
|
|
|
|
Logging.Info($"Proxy {proxy.ProxyEndPoint} timed out");
|
|
remote?.Close();
|
|
RetryConnect();
|
|
}
|
|
|
|
private void ProxyConnectCallback(IAsyncResult ar)
|
|
{
|
|
Server server = null;
|
|
if (_closed)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
ProxyTimer timer = (ProxyTimer)ar.AsyncState;
|
|
var destEndPoint = timer.DestEndPoint;
|
|
server = timer.Server;
|
|
timer.Elapsed -= proxyConnectTimer_Elapsed;
|
|
timer.Enabled = false;
|
|
timer.Dispose();
|
|
|
|
// Complete the connection.
|
|
remote.EndConnectProxy(ar);
|
|
|
|
_proxyConnected = true;
|
|
|
|
if (_config.isVerboseLogging)
|
|
{
|
|
if (!(remote is DirectConnect))
|
|
{
|
|
Logging.Info($"Socket connected to proxy {remote.ProxyEndPoint}");
|
|
}
|
|
}
|
|
|
|
_startConnectTime = DateTime.Now;
|
|
ServerTimer connectTimer = new ServerTimer(3000);
|
|
connectTimer.AutoReset = false;
|
|
connectTimer.Elapsed += destConnectTimer_Elapsed;
|
|
connectTimer.Enabled = true;
|
|
connectTimer.Server = server;
|
|
|
|
_destConnected = false;
|
|
// Connect to the remote endpoint.
|
|
remote.BeginConnectDest(destEndPoint, new AsyncCallback(ConnectCallback), connectTimer);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
RetryConnect();
|
|
}
|
|
}
|
|
|
|
private void destConnectTimer_Elapsed(object sender, ElapsedEventArgs e)
|
|
{
|
|
if (_destConnected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Server server = ((ServerTimer)sender).Server;
|
|
IStrategy strategy = controller.GetCurrentStrategy();
|
|
strategy?.SetFailure(server);
|
|
Logging.Info($"{server.FriendlyName()} timed out");
|
|
remote?.Close();
|
|
RetryConnect();
|
|
}
|
|
|
|
private void RetryConnect()
|
|
{
|
|
if (_retryCount < MaxRetry)
|
|
{
|
|
Logging.Debug($"Connection failed, retry ({_retryCount})");
|
|
StartConnect();
|
|
_retryCount++;
|
|
}
|
|
else
|
|
Close();
|
|
}
|
|
|
|
private void ConnectCallback(IAsyncResult ar)
|
|
{
|
|
if (_closed) return;
|
|
try
|
|
{
|
|
ServerTimer timer = (ServerTimer)ar.AsyncState;
|
|
server = timer.Server;
|
|
timer.Elapsed -= destConnectTimer_Elapsed;
|
|
timer.Enabled = false;
|
|
timer.Dispose();
|
|
|
|
// Complete the connection.
|
|
remote?.EndConnectDest(ar);
|
|
|
|
_destConnected = true;
|
|
|
|
if (_config.isVerboseLogging)
|
|
{
|
|
Logging.Info($"Socket connected to ss server: {server.FriendlyName()}");
|
|
}
|
|
|
|
var latency = DateTime.Now - _startConnectTime;
|
|
IStrategy strategy = controller.GetCurrentStrategy();
|
|
strategy?.UpdateLatency(server, latency);
|
|
_tcprelay.UpdateLatency(server, latency);
|
|
|
|
StartPipe();
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (server != null)
|
|
{
|
|
IStrategy strategy = controller.GetCurrentStrategy();
|
|
strategy?.SetFailure(server);
|
|
}
|
|
Logging.LogUsefulException(e);
|
|
RetryConnect();
|
|
}
|
|
}
|
|
|
|
private void StartPipe()
|
|
{
|
|
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);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void PipeRemoteReceiveCallback(IAsyncResult ar)
|
|
{
|
|
if (_closed) return;
|
|
try
|
|
{
|
|
if ( remote == null ) return;
|
|
int bytesRead = remote.EndReceive(ar);
|
|
_totalRead += bytesRead;
|
|
_tcprelay.UpdateInboundCounter(server, bytesRead);
|
|
if (bytesRead > 0)
|
|
{
|
|
lastActivity = DateTime.Now;
|
|
int bytesToSend;
|
|
lock (_decryptionLock)
|
|
{
|
|
if (_closed) return;
|
|
encryptor.Decrypt(_remoteRecvBuffer, bytesRead, _remoteSendBuffer, out bytesToSend);
|
|
}
|
|
connection.BeginSend(_remoteSendBuffer, 0, bytesToSend, SocketFlags.None, new AsyncCallback(PipeConnectionSendCallback), null);
|
|
IStrategy strategy = controller.GetCurrentStrategy();
|
|
strategy?.UpdateLastRead(server);
|
|
}
|
|
else
|
|
{
|
|
connection.Shutdown(SocketShutdown.Send);
|
|
_connectionShutdown = true;
|
|
CheckClose();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void PipeConnectionReceiveCallback(IAsyncResult ar)
|
|
{
|
|
if (_closed) return;
|
|
try
|
|
{
|
|
if(connection == null) return;
|
|
int bytesRead = connection.EndReceive(ar);
|
|
_totalWrite += bytesRead;
|
|
if (bytesRead > 0)
|
|
{
|
|
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];
|
|
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 bytesToSend;
|
|
lock (_encryptionLock)
|
|
{
|
|
if (_closed) return;
|
|
encryptor.Encrypt(_connetionRecvBuffer, bytesRead, _connetionSendBuffer, out bytesToSend);
|
|
}
|
|
_tcprelay.UpdateOutboundCounter(server, bytesToSend);
|
|
_startSendingTime = DateTime.Now;
|
|
_bytesToSend = bytesToSend;
|
|
remote.BeginSend(_connetionSendBuffer, 0, bytesToSend, SocketFlags.None, new AsyncCallback(PipeRemoteSendCallback), null);
|
|
IStrategy strategy = controller.GetCurrentStrategy();
|
|
strategy?.UpdateLastWrite(server);
|
|
}
|
|
else
|
|
{
|
|
remote.Shutdown(SocketShutdown.Send);
|
|
_remoteShutdown = true;
|
|
CheckClose();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void PipeRemoteSendCallback(IAsyncResult ar)
|
|
{
|
|
if (_closed) return;
|
|
try
|
|
{
|
|
remote?.EndSend(ar);
|
|
connection?.BeginReceive(_connetionRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeConnectionReceiveCallback), null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void PipeConnectionSendCallback(IAsyncResult ar)
|
|
{
|
|
if (_closed) return;
|
|
try
|
|
{
|
|
connection?.EndSend(ar);
|
|
remote?.BeginReceive(_remoteRecvBuffer, 0, RecvSize, SocketFlags.None, new AsyncCallback(PipeRemoteReceiveCallback), null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
}
|
|
}
|