294 lines
10 KiB
C#
294 lines
10 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using Shadowsocks.Model;
|
|
using Shadowsocks.Util.Sockets;
|
|
|
|
namespace Shadowsocks.Controller.Service
|
|
{
|
|
|
|
class Socks5HandlerFactory : ITCPHandlerFactory
|
|
{
|
|
public bool CanHandle(byte[] firstPacket, int length)
|
|
{
|
|
return length >= 2 && firstPacket[0] == 5;
|
|
}
|
|
|
|
public TCPHandler NewHandler(ShadowsocksController controller, Configuration config, TCPRelay tcprelay, Socket socket)
|
|
{
|
|
return new Socks5Handler(controller, config, tcprelay, socket);
|
|
}
|
|
}
|
|
|
|
class Socks5Handler : TCPHandler
|
|
{
|
|
private byte _command;
|
|
private int _firstPacketLength;
|
|
|
|
public Socks5Handler(ShadowsocksController controller, Configuration config, TCPRelay tcprelay, Socket socket) : base(controller, config, tcprelay, socket, false)
|
|
{
|
|
}
|
|
|
|
public override void StartHandshake(byte[] firstPacket, int length)
|
|
{
|
|
if (Closed) return;
|
|
try
|
|
{
|
|
int bytesRead = length;
|
|
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, 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, and read 2 more bytes to analysis the address.
|
|
// 2 more bytes is designed if address is domain then we don't need to read once more to get the addr length.
|
|
// TODO validate
|
|
Connection.BeginReceive(ConnetionRecvBuffer, 0, 3 + 2, SocketFlags.None,
|
|
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 >= 5)
|
|
{
|
|
_command = ConnetionRecvBuffer[1];
|
|
if (_command != 1 && _command != 3)
|
|
{
|
|
Logging.Debug("Unsupported CMD=" + _command);
|
|
Close();
|
|
}
|
|
else
|
|
{
|
|
int atyp = ConnetionRecvBuffer[3];
|
|
|
|
switch (atyp)
|
|
{
|
|
case 1: // IPv4 address, 4 bytes
|
|
ReadAddress(4 + 2 - 1);
|
|
break;
|
|
case 3: // domain name, length + str
|
|
int len = ConnetionRecvBuffer[4];
|
|
ReadAddress(len + 2);
|
|
break;
|
|
case 4: // IPv6 address, 16 bytes
|
|
ReadAddress(16 + 2 - 1);
|
|
break;
|
|
default:
|
|
Logging.Debug("Unsupported ATYP=" + atyp);
|
|
Close();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logging.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()");
|
|
Close();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
|
|
private void ReadAddress(int bytesRemain)
|
|
{
|
|
Array.Copy(ConnetionRecvBuffer, 3, ConnetionRecvBuffer, 0, 2);
|
|
|
|
// Read the remain address bytes
|
|
Connection.BeginReceive(ConnetionRecvBuffer, 2, RecvSize - 2, SocketFlags.None, OnAddressFullyRead, bytesRemain);
|
|
}
|
|
|
|
private void OnAddressFullyRead(IAsyncResult ar)
|
|
{
|
|
if (Closed) return;
|
|
try
|
|
{
|
|
int bytesRead = Connection.EndReceive(ar);
|
|
int bytesRemain = (int)ar.AsyncState;
|
|
if (bytesRead >= bytesRemain)
|
|
{
|
|
_firstPacketLength = bytesRead + 2;
|
|
|
|
int atyp = ConnetionRecvBuffer[0];
|
|
|
|
string dst_addr = "Unknown";
|
|
int dst_port = -1;
|
|
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];
|
|
|
|
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];
|
|
|
|
break;
|
|
case 4: // IPv6 address, 16 bytes
|
|
dst_addr = $"[{new IPAddress(ConnetionRecvBuffer.Skip(1).Take(16).ToArray())}]";
|
|
dst_port = (ConnetionRecvBuffer[17] << 8) + ConnetionRecvBuffer[18];
|
|
|
|
break;
|
|
}
|
|
if (Config.isVerboseLogging)
|
|
{
|
|
Logging.Info($"connect to {dst_addr}:{dst_port}");
|
|
}
|
|
|
|
var destEndPoint = SocketUtil.GetEndPoint(dst_addr, dst_port);
|
|
|
|
if (_command == 1)
|
|
{
|
|
byte[] response = { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
|
|
Connection.BeginSend(response, 0, response.Length, SocketFlags.None,
|
|
ResponseCallback, destEndPoint);
|
|
}
|
|
else if (_command == 3)
|
|
{
|
|
HandleUDPAssociate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logging.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.OnAddressFullyRead()");
|
|
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((EndPoint) ar.AsyncState);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
|
|
protected override void OnServerConnected(AsyncSession session)
|
|
{
|
|
BeginSendToServer(_firstPacketLength, session, FirstPackageSendCallback);
|
|
}
|
|
|
|
private void FirstPackageSendCallback(IAsyncResult ar)
|
|
{
|
|
if (Closed) return;
|
|
try
|
|
{
|
|
var session = EndSendToServer(ar);
|
|
StartPipe(session);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.LogUsefulException(e);
|
|
Close();
|
|
}
|
|
}
|
|
}
|
|
}
|