Socks Proxy на C#: асинхронные сокеты

5 ноября 2009 г.

При разработке сетевых приложений нередко для упрощения архитектуры приложения используется асинхронная модель работы с сокетами. Работа с асинхронной моделью делится на 2 этапа: начало операции и ее завершение. В метод начала операции передается метод, который будет вызван после завершения операции (т.н. callback), а также объект, который будет доступен в callback-методе.

Например, метод:

socket.BeginReceive(buffer, offset, bytesToReceive, SocketFlags.None, ClientToRemote, socket); 

после своего завершения вызовет метод ClientToRemote, из которого будет доступен пользовательский объект socket.

Callback-метод должен вызвать метод завершения операции. Для вышеприведенного примера это выглядит следующим образом:

    static void ClientToRemote(IAsyncResult state)
    {
        Socket socket = (ConnectionState)state.AsyncState;
        int recievedBytes = socket.EndReceive(state); //Завершаем асинхронную операцию
    }

В данной статье мы разработаем Socks Proxy сервер V4 – V5 с использованием асинхронных сокетов.
Для упрощения поставленной задачи, реализуем лишь минимальный функционал нашего сервера:

  • Cоединение с удаленным узлом по протоколу IPv4
  • Проксирование информации от клиента к удаленному узлу и обратно

Такой функционал, как проброс порта, поддержку аутентификации по имени пользователя/паролю, соединение по протоколу IPv6, реализовывать не будем.

Алгоритм работы нашего сервера:

  1. Принимаем соединение
  2. Проверяем версию желаемого socks-сервера
  3. Версия 4:
    1. Проверяем тип запроса (поддерживаем только соединение с удаленным узлом)
    2. Устанавливаем соединение с удаленным узлом
    3. Читаем id клиента
    1. Проверка и выбор необходимого метода аутентификации (поддерживаем только стандартный метод аутентификации)
    2. Проверяем тип запроса (поддерживаем только соединение с удаленным узлом)
    3. Устанавливаем соединение с удаленным узлом
  4. Версия 5:

  5. Проксируем соединение
  6. Закрываем соединение

Код класса с комментариями неочевидных моментов:

using System;
using System.Net;
using System.Net.Sockets;

namespace Proxy
{
class SocksServer
{
//Класс хранящий состояние для каждого прокрируемого соединения (буферы), сокеты клиента
//и удаленного узла, а также колбэк, используемый в вспомогательном методе
//BeginReceive(ConnectionState connectionState, int numberOfBytesToReceive, AsyncCallback callback)
class ConnectionState
{
public byte[] ClientReceiveBuffer;
public int ClientReceivedBytes;
public TcpClient Client;
public TcpClient Remote;
public byte[] RemoteReceiveBuffer;
public AsyncCallback Callback;

public ConnectionState(TcpClient tcpClient)
{
Client = tcpClient;
}
}

delegate void AsyncCallback(ConnectionState state);

TcpListener listener;

//Старт сервера
public void Start(int port)
{
listener = new TcpListener(IPAddress.Any, port);
listener.Start();
listener.BeginAcceptTcpClient(EndAcceptTcpClient, listener);
}

//Остановка сервера
public void Stop()
{
listener.Stop();
}

//При входящем соединении
static void EndAcceptTcpClient(IAsyncResult state)
{
TcpListener listener = (TcpListener)state.AsyncState;
try
{
TcpClient client = listener.EndAcceptTcpClient(state);
ConnectionState connectionState = new ConnectionState(client);
BeginReceive(connectionState, 1, CheckVersion);
}
finally
{
listener.BeginAcceptTcpClient(EndAcceptTcpClient, listener);
}
}

//Вспомогательный асинхронные метод, который начинает операцию получения от
//connectionState.Client numberOfBytesToReceive байт
static void BeginReceive(ConnectionState connectionState, int numberOfBytesToReceive
, AsyncCallback callback)
{
connectionState.ClientReceiveBuffer = new byte[numberOfBytesToReceive];
connectionState.ClientReceivedBytes = 0;
connectionState.Callback = callback;
try
{
connectionState.Client.Client.BeginReceive(connectionState.ClientReceiveBuffer, 0,
numberOfBytesToReceive, SocketFlags.None, EndRecieve, connectionState);
}
catch
{ }
}

//Вспомогательный асинхронные метод, который вызывается при получении информации от клиента.
//Если кол-во принятых байтов равно numberOfBytesToReceive, переданному в BeginReceive,
//то вызывается callback, в противном случае вызывается метод на получение от клиента
//(numberOfBytesToReceive-полученныеБайты) байт
static void EndRecieve(IAsyncResult state)
{
ConnectionState connectionState = (ConnectionState)state.AsyncState;
int recievedBytes;
try
{
recievedBytes = connectionState.Client.Client.EndReceive(state);
}
catch
{
return;
}
if (recievedBytes == 0)
{
connectionState.Client.Close();
}
else
{
connectionState.ClientReceivedBytes += recievedBytes;
if (connectionState.ClientReceivedBytes == connectionState.ClientReceiveBuffer.Length)
{
connectionState.Callback.Invoke(connectionState);
}
else
{
try
{
connectionState.Client.Client.BeginReceive(connectionState.ClientReceiveBuffer,
connectionState.ClientReceivedBytes,
connectionState.ClientReceiveBuffer.Length - connectionState.ClientReceivedBytes,
SocketFlags.None, EndRecieve, connectionState);
}
catch
{ }
}
}
}

//Проверка версии желаемого socks сервера
static void CheckVersion(ConnectionState state)
{
if (state.ClientReceiveBuffer[0] == 4)
{
BeginReceive(state, 1, SocksV4Request);
}
else if (state.ClientReceiveBuffer[0] == 5)
{
BeginReceive(state, 1, SocksV5GetingNumAuth);
}
else
{
state.Client.Close();
}
}

//Отправка ошибки от socks v4 сервера и разрыв соединения
static void SocksV4Error(ConnectionState state)
{
try
{
state.Client.Client.Send(new byte[] { 0, 0x5b, 0, 0, 0, 0, 0, 0 });
}
finally
{
Disconnect(state);
}
}

//Проверка типа запроса
static void SocksV4Request(ConnectionState state)
{
if (state.ClientReceiveBuffer[0] == 1) // Only TcpIp request enabled
{
BeginReceive(state, 6, SocksV4RequestIPPort);
}
else
{
SocksV4Error(state);
}
}

//Установка соединения с удаленным узлом
static void SocksV4RequestIPPort(ConnectionState state)
{
byte[] ip = new byte[4];
Array.Copy(state.ClientReceiveBuffer, 2, ip, 0, 4);
state.Remote = new TcpClient();
try
{
state.Remote.Connect(new IPAddress(ip), state.ClientReceiveBuffer[0] * 256 +
state.ClientReceiveBuffer[1]);
}
catch
{
SocksV4Error(state);
return;
}
BeginReceive(state, 1, SocksV4UserID);
}

//Чтение id клиента
static void SocksV4UserID(ConnectionState state)
{
if (state.ClientReceiveBuffer[0] == 0)
{
try
{
state.Client.Client.Send(new byte[] { 0, 0x5a, 0, 0, 0, 0, 0, 0 });
}
catch
{
Disconnect(state);
return;
}
state.RemoteReceiveBuffer = new byte[state.Remote.ReceiveBufferSize];
state.ClientReceiveBuffer = new byte[state.Client.ReceiveBufferSize];
try
{
state.Remote.Client.BeginReceive(state.RemoteReceiveBuffer, 0,
state.RemoteReceiveBuffer.Length, SocketFlags.None, RemoteToClient, state);
state.Client.Client.BeginReceive(state.ClientReceiveBuffer, 0,
state.ClientReceiveBuffer.Length, SocketFlags.None, ClientToRemote, state);
}
catch
{
Disconnect(state);
}
}
else
{
BeginReceive(state, 1, SocksV4UserID);
}
}

//Отправка ошибки от socks v5 сервера и разрыв соединения
static void SocksV5Error(ConnectionState state, byte error)
{
try
{
state.Client.Client.Send(new byte[] { 5, error });
}
finally
{
Disconnect(state);
}
}

//Чтение кол-ва желаемых методов аутентификации
static void SocksV5GetingNumAuth(ConnectionState state)
{
if (state.ClientReceiveBuffer[0] == 0)
{
SocksV5Error(state, 0x07);
}
else
{
BeginReceive(state, state.ClientReceiveBuffer[0], SocksV5AuthChecking);
}
}

//Проверка стандартного метода аутентификации
static void SocksV5AuthChecking(ConnectionState state)
{
for (int i = 0; i < state.ClientReceiveBuffer.Length; i++)
{
if (state.ClientReceiveBuffer[i] == 0)
{
try
{
state.Client.Client.Send(new byte[] { 5, 0 }); // Authentication is not required
}
catch
{
state.Client.Close();
return;
}
BeginReceive(state, 4, SocksV5Request);
return;
}
}
SocksV5Error(state, 0xFF); // Necessary authentication is not received
}

//Проверка типа запроса
static void SocksV5Request(ConnectionState state)
{
if (state.ClientReceiveBuffer[0] == 5 && state.ClientReceiveBuffer[2] == 0)
{
switch (state.ClientReceiveBuffer[1])
{
case 0x01:
if (state.ClientReceiveBuffer[3] == 0x01)
{
BeginReceive(state, 6, SocksV5RequestIPPort);
}
else
{
SocksV5Error(state, 0x08); //Address type is not supported
}
break;
case 0x02:
case 0x03:
SocksV5Error(state, 0x05); //Denial of connection
break;
default:
SocksV5Error(state, 0x07); //Command is not supported
break;
}
}
else
{
SocksV5Error(state, 0x07); //Command is not supported
}
}

//Установка соединения с удаленным узлом
static void SocksV5RequestIPPort(ConnectionState state)
{
byte[] ip = new byte[4];
Array.Copy(state.ClientReceiveBuffer, ip, 4);
state.Remote = new TcpClient();
try
{
state.Remote.Connect(new IPAddress(ip), state.ClientReceiveBuffer[4] << 8 +
state.ClientReceiveBuffer[5]);
}
catch
{
SocksV5Error(state, 0x04); //Host is not available
return;
}

try
{
state.Client.<
Теги:
рубрика C#, Интернет