Pull to refresh

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

При разработке сетевых приложений нередко для упрощения архитектуры приложения используется асинхронная модель работы с сокетами. Работа с асинхронной моделью делится на 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 клиента
    Версия 5:
    1. Проверка и выбор необходимого метода аутентификации (поддерживаем только стандартный метод аутентификации)
    2. Проверяем тип запроса (поддерживаем только соединение с удаленным узлом)
    3. Устанавливаем соединение с удаленным узлом
  4. Проксируем соединение
  5. Закрываем соединение
Код класса с комментариями неочевидных моментов:

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.ClientReceiveBuffer0,
                    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, 000000 });
            }
            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.ClientReceiveBuffer2, ip, 04);
            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, 000000 });
                }
                catch
                {
                    Disconnect(state);
                    return;
                }
                state.RemoteReceiveBuffer = new byte[state.Remote.ReceiveBufferSize];
                state.ClientReceiveBuffer = new byte[state.Client.ReceiveBufferSize];
                try
                {
                    state.Remote.Client.BeginReceive(state.RemoteReceiveBuffer0
                        state.RemoteReceiveBuffer.Length, SocketFlags.None, RemoteToClient, state);
                    state.Client.Client.BeginReceive(state.ClientReceiveBuffer0,
                        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[] { 50 }); // 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.<
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.