Correct AvailabilityStatistics's behavior under special strategies like "LoadBalance", which may switch the server several times in a short period.
669 lines
21 KiB
C#
669 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Timers;
|
|
|
|
using Shadowsocks.Controller.Strategy;
|
|
using Shadowsocks.Encryption;
|
|
using Shadowsocks.Model;
|
|
|
|
namespace Shadowsocks.Controller
|
|
{
|
|
class TCPRelay : Listener.Service
|
|
{
|
|
private ShadowsocksController _controller;
|
|
private DateTime _lastSweepTime;
|
|
|
|
public ISet<TCPHandler> Handlers
|
|
{
|
|
get; set;
|
|
}
|
|
|
|
public TCPRelay(ShadowsocksController controller)
|
|
{
|
|
_controller = controller;
|
|
Handlers = new HashSet<TCPHandler>();
|
|
_lastSweepTime = DateTime.Now;
|
|
}
|
|
|
|
public bool Handle(byte[] firstPacket, int length, Socket socket, object state)
|
|
{
|
|
if (socket.ProtocolType != ProtocolType.Tcp)
|
|
{
|
|
return false;
|
|
}
|
|
if (length < 2 || firstPacket[0] != 5)
|
|
{
|
|
return false;
|
|
}
|
|
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
|
|
TCPHandler handler = new TCPHandler(this);
|
|
handler.connection = socket;
|
|
handler.controller = _controller;
|
|
handler.relay = 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
|
|
{
|
|
// public Encryptor encryptor;
|
|
public IEncryptor encryptor;
|
|
public Server server;
|
|
// Client socket.
|
|
public Socket remote;
|
|
public Socket connection;
|
|
public ShadowsocksController controller;
|
|
public TCPRelay relay;
|
|
|
|
public DateTime lastActivity;
|
|
|
|
private const int maxRetry = 4;
|
|
private int retryCount = 0;
|
|
private bool connected;
|
|
|
|
private byte command;
|
|
private byte[] _firstPacket;
|
|
private int _firstPacketLength;
|
|
// Size of receive buffer.
|
|
public const int RecvSize = 8192;
|
|
public const int RecvReserveSize = IVEncryptor.ONETIMEAUTH_BYTES + IVEncryptor.AUTH_BYTES; // reserve for one-time auth
|
|
public const int BufferSize = RecvSize + RecvReserveSize + 32;
|
|
|
|
private int totalRead = 0;
|
|
private int totalWrite = 0;
|
|
|
|
// remote receive buffer
|
|
private byte[] remoteRecvBuffer = new byte[BufferSize];
|
|
// remote send buffer
|
|
private byte[] remoteSendBuffer = new byte[BufferSize];
|
|
// connection receive buffer
|
|
private byte[] connetionRecvBuffer = new byte[BufferSize];
|
|
// connection send buffer
|
|
private byte[] connetionSendBuffer = new byte[BufferSize];
|
|
// Received data string.
|
|
|
|
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: tcprelay ?= relay
|
|
|
|
public TCPHandler(TCPRelay tcprelay)
|
|
{
|
|
this.tcprelay = tcprelay;
|
|
}
|
|
|
|
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 (relay.Handlers)
|
|
{
|
|
relay.Handlers.Remove(this);
|
|
}
|
|
lock (this)
|
|
{
|
|
if (closed)
|
|
{
|
|
return;
|
|
}
|
|
closed = true;
|
|
}
|
|
if (connection != null)
|
|
{
|
|
try
|
|
{
|
|
connection.Shutdown(SocketShutdown.Both);
|
|
connection.Close();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
}
|
|
}
|
|
if (remote != null)
|
|
{
|
|
try
|
|
{
|
|
remote.Shutdown(SocketShutdown.Both);
|
|
remote.Close();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
}
|
|
}
|
|
lock (encryptionLock)
|
|
{
|
|
lock (decryptionLock)
|
|
{
|
|
if (encryptor != null)
|
|
{
|
|
((IDisposable)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, 0, 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, 0, 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, 0, 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;
|
|
if (endPoint.AddressFamily == AddressFamily.InterNetwork)
|
|
{
|
|
response[3] = 1;
|
|
}
|
|
else if (endPoint.AddressFamily == AddressFamily.InterNetworkV6)
|
|
{
|
|
response[3] = 4;
|
|
}
|
|
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, 0, new AsyncCallback(ReadAll), true);
|
|
}
|
|
|
|
private void ReadAll(IAsyncResult ar)
|
|
{
|
|
if (closed)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
if (ar.AsyncState != null)
|
|
{
|
|
connection.EndSend(ar);
|
|
Logging.Debug(remote, RecvSize, "TCP Relay");
|
|
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(ReadAll), null);
|
|
}
|
|
else
|
|
{
|
|
int bytesRead = connection.EndReceive(ar);
|
|
if (bytesRead > 0)
|
|
{
|
|
Logging.Debug(remote, RecvSize, "TCP Relay");
|
|
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, 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 ServerTimer : Timer
|
|
{
|
|
public Server Server;
|
|
|
|
public ServerTimer(int p) : base(p)
|
|
{
|
|
}
|
|
}
|
|
|
|
private void StartConnect()
|
|
{
|
|
try
|
|
{
|
|
CreateRemote();
|
|
|
|
// 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];
|
|
}
|
|
IPEndPoint remoteEP = new IPEndPoint(ipAddress, server.server_port);
|
|
|
|
remote = new Socket(ipAddress.AddressFamily,
|
|
SocketType.Stream, ProtocolType.Tcp);
|
|
remote.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
|
|
|
|
_startConnectTime = DateTime.Now;
|
|
ServerTimer connectTimer = new ServerTimer(3000);
|
|
connectTimer.AutoReset = false;
|
|
connectTimer.Elapsed += connectTimer_Elapsed;
|
|
connectTimer.Enabled = true;
|
|
connectTimer.Server = server;
|
|
|
|
connected = false;
|
|
// Connect to the remote endpoint.
|
|
remote.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), connectTimer);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void connectTimer_Elapsed(object sender, ElapsedEventArgs e)
|
|
{
|
|
if (connected)
|
|
{
|
|
return;
|
|
}
|
|
Server server = ((ServerTimer)sender).Server;
|
|
IStrategy strategy = controller.GetCurrentStrategy();
|
|
if (strategy != null)
|
|
{
|
|
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)
|
|
{
|
|
Server server = null;
|
|
if (closed)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
ServerTimer timer = (ServerTimer)ar.AsyncState;
|
|
server = timer.Server;
|
|
timer.Elapsed -= connectTimer_Elapsed;
|
|
timer.Enabled = false;
|
|
timer.Dispose();
|
|
|
|
// Complete the connection.
|
|
remote.EndConnect(ar);
|
|
|
|
connected = true;
|
|
|
|
Logging.Debug($"Socket connected to {remote.RemoteEndPoint}");
|
|
|
|
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();
|
|
if (strategy != null)
|
|
{
|
|
strategy.SetFailure(server);
|
|
}
|
|
}
|
|
Logging.LogUsefulException(e);
|
|
RetryConnect();
|
|
}
|
|
}
|
|
|
|
private void StartPipe()
|
|
{
|
|
if (closed)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
_startReceivingTime = DateTime.Now;
|
|
remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeRemoteReceiveCallback), null);
|
|
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeConnectionReceiveCallback), null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void PipeRemoteReceiveCallback(IAsyncResult ar)
|
|
{
|
|
if (closed)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
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);
|
|
}
|
|
Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeRemoteReceiveCallback() (download)");
|
|
connection.BeginSend(remoteSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeConnectionSendCallback), null);
|
|
|
|
IStrategy strategy = controller.GetCurrentStrategy();
|
|
if (strategy != null)
|
|
{
|
|
strategy.UpdateLastRead(server);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
connection.Shutdown(SocketShutdown.Send);
|
|
connectionShutdown = true;
|
|
CheckClose();
|
|
|
|
//if (totalRead == 0)
|
|
//{
|
|
// // closed before anything received, reports as failure
|
|
// // disable this feature
|
|
// controller.GetCurrentStrategy().SetFailure(this.server);
|
|
//}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
private void PipeConnectionReceiveCallback(IAsyncResult ar)
|
|
{
|
|
if (closed)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
int bytesRead = connection.EndReceive(ar);
|
|
totalWrite += bytesRead;
|
|
|
|
if (bytesRead > 0)
|
|
{
|
|
int bytesToSend;
|
|
lock (encryptionLock)
|
|
{
|
|
if (closed)
|
|
{
|
|
return;
|
|
}
|
|
encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend);
|
|
}
|
|
Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeConnectionReceiveCallback() (upload)");
|
|
tcprelay.UpdateOutboundCounter(server, bytesToSend);
|
|
_startSendingTime = DateTime.Now;
|
|
_bytesToSend = bytesToSend;
|
|
remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, 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, 0, 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, 0, new AsyncCallback(PipeRemoteReceiveCallback), null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
}
|
|
}
|