'Multithread Proxy Endless Loop C# Tor

I use socks5 and a proxy to access my own server through Tor services. My program is multithread and has a client-server arhitecture.

When I tried to connect to the server directly using Destination IP and Destination Port it worked like a charm, but after I connect to the Tor Proxy it behaves strange, it goes on endless loops or I get A connect request was made on an already connected socket depending on where I chose to connect to the proxy in code.

The flow is:

MainThread

Initial Form.cs: ConnectLoop() -> Client Class: Connect() -> CreateProxied()/Socks5 Tor Proxy Code -> Initial Form.cs: GetData() -> EventType.Connected case -> EventType.Disconnected case -> CoonectLoop() -> repeat from step 1


Client Class

public class Client : Common
    {
        private readonly ManualResetEvent sendPending = new ManualResetEvent(false);

        private readonly SafeQueue<byte[]> sendQueue = new SafeQueue<byte[]>();
        private volatile bool _Connecting;
        private Socket s;
        public TcpClient client;
        private Thread receiveThread;
        private Thread sendThread;

        public bool Connected => client != null &&
                                 client.Client != null &&
                                 client.Client.Connected;

        public bool Connecting => _Connecting;

        private void setTorProxy(string proxyAdress, ushort proxyPort, string userName = "", string password = "")
        {
            IPAddress proxyIP = null;
            byte[] request = new byte[257];
            byte[] response = new byte[257];
            ushort nIndex;

            try
            {
                proxyIP = IPAddress.Parse(proxyAdress);
            }
            catch (FormatException)
            {   // get the IP address
                proxyIP = Dns.GetHostByAddress(proxyAdress).AddressList[0];
            }

            // Parse destAddress (assume it in string dotted format "212.116.65.112" )
            try
            {
                //destIP = IPAddress.Parse(destAddress);
            }
            catch (FormatException)
            {
                // wrong assumption its in domain name format "www.microsoft.com"
            }

            IPEndPoint proxyEndPoint = new IPEndPoint(proxyIP, proxyPort);

            // open a TCP connection to SOCKS server...
            Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            s.Connect(proxyEndPoint);

            //this.client.Client.AddressFamily = AddressFamily.InterNetwork;
            //this.client.Client.SocketType = SocketType.Stream;
            //this.client.Client.ProtocolType = ProtocolType.Tcp;


            nIndex = 0;
            request[nIndex++] = 0x05; // Version 5.
            request[nIndex++] = 0x01; // 2 Authentication methods are in packet...
            request[nIndex++] = 0x00; // NO AUTHENTICATION REQUIRED
                                      //request[nIndex++] = 0x02; // USERNAME/PASSWORD
                                      // Send the authentication negotiation request...
            s.Send(request, nIndex, SocketFlags.None);

            // Receive 2 byte response...
            int nGot = s.Receive(response, 2, SocketFlags.None);
            if (nGot != 2)
                throw new ConnectionException("Bad response received from proxy server.");

            if (response[1] == 0xFF)
            {    // No authentication method was accepted close the socket.
                s.Close();
                throw new ConnectionException("None of the authentication method was accepted by proxy server.");
            }

            byte[] rawBytes;


            if (/*response[1]==0x02*/false)
            {//Username/Password Authentication protocol
                nIndex = 0;
                request[nIndex++] = 0x05; // Version 5.

                // add user name
                request[nIndex++] = (byte)userName.Length;
                rawBytes = Encoding.Default.GetBytes(userName);
                rawBytes.CopyTo(request, nIndex);
                nIndex += (ushort)rawBytes.Length;

                // add password
                request[nIndex++] = (byte)password.Length;
                rawBytes = Encoding.Default.GetBytes(password);
                rawBytes.CopyTo(request, nIndex);
                nIndex += (ushort)rawBytes.Length;

                // Send the Username/Password request
                s.Send(request, nIndex, SocketFlags.None);
                // Receive 2 byte response...
                nGot = s.Receive(response, 2, SocketFlags.None);
                if (nGot != 2)
                    throw new ConnectionException("Bad response received from proxy server.");
                if (response[1] != 0x00)
                    throw new ConnectionException("Bad Usernaem/Password.");
            }

            // This version only supports connect command. 
            // UDP and Bind are not supported.

            // Send connect request now...
            //nIndex = 0;
            //request[nIndex++] = 0x05;   // version 5.
            //request[nIndex++] = 0x01;   // command = connect.
            //request[nIndex++] = 0x00;   // Reserve = must be 0x00

           
        }

        private void ReceiveThreadFunction(string ip, int port)
        {
            try
            {


                //client.Connect(ip, port);
                _Connecting = false;

                sendThread = new Thread(() => { SendLoop(0, client, sendQueue, sendPending); });
                sendThread.IsBackground = true;
                sendThread.Start();

                ReceiveLoop(0, client, receiveQueue, MaxMessageSize);
            }
            catch (SocketException exception)
            {
                Logger.Log("Client Recv: failed to connect to ip=" + ip + " port=" + port + " reason=" + exception);

                receiveQueue.Enqueue(new Message(0, EventType.Disconnected, null));
            }
            catch (Exception exception)
            {
                Logger.LogError("Client Recv Exception: " + exception);
            }

            sendThread?.Interrupt();

            _Connecting = false;

            client.Close();
        }

        private void DebugHTTPRequest(TcpClient client)
        {
            string result = string.Empty;

            
            using (var stream = client.GetStream())
            {
                client.SendTimeout = 500;
                client.ReceiveTimeout = 1000;
                // Send request headers
                var builder = new StringBuilder();
                builder.AppendLine("GET /?scope=images&nr=1 HTTP/1.1");
                builder.AppendLine("Host: xoembwt22tmxseask2qyuudbxoubiuafw54klkwuktvv6bxbhrdffqyd.onion");
                //builder.AppendLine("Content-Length: " + data.Length);   // only for POST request
                builder.AppendLine("Connection: close");
                builder.AppendLine();
                var header = Encoding.ASCII.GetBytes(builder.ToString());
                 stream.Write(header, 0, header.Length);

                // Send payload data if you are POST request
                //await stream.WriteAsync(data, 0, data.Length);

                // receive data
                using (var memory = new MemoryStream())
                {
                    stream.CopyTo(memory);
                    memory.Position = 0;
                    var data = memory.ToArray();

                    var index = BinaryMatch(data, Encoding.ASCII.GetBytes("\r\n\r\n")) + 4;
                    var headers = Encoding.ASCII.GetString(data, 0, index);
                    memory.Position = index;

                    if (headers.IndexOf("Content-Encoding: gzip") > 0)
                    {
                        using (GZipStream decompressionStream = new GZipStream(memory, CompressionMode.Decompress))
                        using (var decompressedMemory = new MemoryStream())
                        {
                            decompressionStream.CopyTo(decompressedMemory);
                            decompressedMemory.Position = 0;
                            result = Encoding.UTF8.GetString(decompressedMemory.ToArray());
                        }
                    }
                    else
                    {
                        result = Encoding.UTF8.GetString(data, index, data.Length - index);
                        //result = Encoding.GetEncoding("gbk").GetString(data, index, data.Length - index);
                    }
                }

                //Debug.WriteLine(result);
               // return result;
            }
        }

        private static int BinaryMatch(byte[] input, byte[] pattern)
        {
            int sLen = input.Length - pattern.Length + 1;
            for (int i = 0; i < sLen; ++i)
            {
                bool match = true;
                for (int j = 0; j < pattern.Length; ++j)
                {
                    if (input[i + j] != pattern[j])
                    {
                        match = false;
                        break;
                    }
                }
                if (match)
                {
                    return i;
                }
            }
            return -1;
        }

        private static TcpClient CreateProxied(string proxyAdress, string DestinationIP)
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            byte[] request = new byte[257];
            byte[] response = new byte[257];
            ushort nIndex;

            ushort proxyPort = 9150;
            IPAddress proxyIP;

            try
            {
                proxyIP = IPAddress.Parse(proxyAdress);
            }
            catch (FormatException)
            {   // get the IP address
                proxyIP = Dns.GetHostByAddress(proxyAdress).AddressList[0];
            }

            IPEndPoint proxyEndPoint = new IPEndPoint(proxyIP, proxyPort);

            socket.Connect(proxyEndPoint);

            nIndex = 0;
            request[nIndex++] = 0x05; // Version 5.
            request[nIndex++] = 0x01; // 2 Authentication methods are in packet...
            request[nIndex++] = 0x00; // NO AUTHENTICATION REQUIRED
                                      //request[nIndex++] = 0x02; // USERNAME/PASSWORD
                                      // Send the authentication negotiation request...

            socket.Send(request, nIndex, SocketFlags.None);

            // Receive 2 byte response...
            int nGot = socket.Receive(response, 2, SocketFlags.None);
            if (nGot != 2)
                throw new ConnectionException("Bad response received from proxy server.");

            if (response[1] == 0xFF)
            {    // No authentication method was accepted close the socket.
                socket.Close();
                throw new ConnectionException("None of the authentication method was accepted by proxy server.");
            }

            return new TcpClient
            {
                Client = socket
            };

        }

        public void Connect(string ip, int port)
        {
            if (Connecting || Connected) return;

            client = CreateProxied("127.0.0.1", "");
            client.NoDelay = NoDelay;
            client.SendTimeout = SendTimeout;

            receiveQueue = new ConcurrentQueue<Message>();
            sendQueue.Clear();

            receiveThread = new Thread(() => { ReceiveThreadFunction(ip, port); });
            receiveThread.IsBackground = true;
            receiveThread.Start();
        }

        public void Disconnect()
        {
            if (Connecting || Connected)
            {
                client.Close();

                receiveThread?.Join();

                sendQueue.Clear();

                client = null;
            }
        }

        public bool Send(byte[] data)
        {
            if (Connected)
            {
                if (data.Length <= MaxMessageSize)
                {
                    sendQueue.Enqueue(data);
                    sendPending.Set();
                    return true;
                }

                Logger.LogError("Client.Send: message too big: " + data.Length + ". Limit: " + MaxMessageSize);
                return false;
            }

            Logger.LogWarning("Client.Send: not connected!");
            return false;
        }
    }

Connect Loop in Initial Form.cs

 #region Connect Loop

        //Connect to server, then loop data receiving
        private async void ConnectLoop()
        {
            while (!Networking.MainClient.Connected)
            {
                await Task.Delay(50);
                Networking.MainClient.Connect(ClientSettings.DNS, Port);
                
            }

            while (Networking.MainClient.Connected)
            {
                await Task.Delay(Interval);
                GetData();
            }

            ConnectLoop();
        }

        #endregion Connect Loop

GetData in Initial Form.cs

private void GetData()
        {
            Message Data;
            while (Networking.MainClient.GetNextMessage(out Data))
                switch (Data.eventType)
                {
                    case EventType.Connected:
                        Console.WriteLine("Connected");
                        List<byte> ToSend = new List<byte>();
                        ToSend.Add((int) DataType.ClientTag);
                        ToSend.AddRange(Encoding.ASCII.GetBytes(ClientSettings.ClientTag));
                        Networking.MainClient.Send(ToSend.ToArray());
                        ToSend.Clear();
                        ToSend.Add((int) DataType.AntiVirusTag);
                        ToSend.AddRange(Encoding.ASCII.GetBytes(ComputerInfo.GetAntivirus()));
                        Networking.MainClient.Send(ToSend.ToArray());
                        string OperatingSystemUnDetailed = ComputerInfo.GetWindowsVersion()
                            .Remove(ComputerInfo.GetWindowsVersion().IndexOf('('));
                        ToSend.Clear();
                        ToSend.Add((int) DataType.WindowsVersionTag);
                        ToSend.AddRange(Encoding.ASCII.GetBytes(OperatingSystemUnDetailed));
                        Networking.MainClient.Send(ToSend.ToArray());
                        break;

                    case EventType.Disconnected:
                        break;

                    case EventType.Data:
                        HandleData(Data.data);
                        break;
                }
        }

Prototype Code for Tor Proxy SOCKS5#

public class ConnectionException : ApplicationException
{
    public ConnectionException(string message)
        : base(message)
    {

    }
}

/// <summary>
/// Provides sock5 functionality to clients (Connect only).
/// </summary>
public class SocksProxy
{

    private SocksProxy() { }

    #region ErrorMessages
    private static string[] errorMsgs = {
                                      "Operation completed successfully.",
                                      "General SOCKS server failure.",
                                      "Connection not allowed by ruleset.",
                                      "Network unreachable.",
                                      "Host unreachable.",
                                      "Connection refused.",
                                      "TTL expired.",
                                      "Command not supported.",
                                      "Address type not supported.",
                                      "Unknown error."
                                  };
    #endregion


    public static Socket ConnectToSocks5Proxy(string proxyAdress, ushort proxyPort, string destAddress, ushort destPort,
        string userName, string password)
    {
        IPAddress destIP = null;
        IPAddress proxyIP = null;
        byte[] request = new byte[257];
        byte[] response = new byte[257];
        ushort nIndex;

        try
        {
            proxyIP = IPAddress.Parse(proxyAdress);
        }
        catch (FormatException)
        {   // get the IP address
            proxyIP = Dns.GetHostByAddress(proxyAdress).AddressList[0];
        }

        // Parse destAddress (assume it in string dotted format "212.116.65.112" )
        try
        {
            destIP = IPAddress.Parse(destAddress);
        }
        catch (FormatException)
        {
            // wrong assumption its in domain name format "www.microsoft.com"
        }

        IPEndPoint proxyEndPoint = new IPEndPoint(proxyIP, proxyPort);

        // open a TCP connection to SOCKS server...
        Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        s.Connect(proxyEndPoint);

        nIndex = 0;
        request[nIndex++] = 0x05; // Version 5.
        request[nIndex++] = 0x01; // 2 Authentication methods are in packet...
        request[nIndex++] = 0x00; // NO AUTHENTICATION REQUIRED
        //request[nIndex++] = 0x02; // USERNAME/PASSWORD
                                  // Send the authentication negotiation request...
        s.Send(request, nIndex, SocketFlags.None);

        // Receive 2 byte response...
        int nGot = s.Receive(response, 2, SocketFlags.None);
        if (nGot != 2)
            throw new ConnectionException("Bad response received from proxy server.");

        if (response[1] == 0xFF)
        {    // No authentication method was accepted close the socket.
            s.Close();
            throw new ConnectionException("None of the authentication method was accepted by proxy server.");
        }

        byte[] rawBytes;

       
        if (/*response[1]==0x02*/false)
        {//Username/Password Authentication protocol
            nIndex = 0;
            request[nIndex++] = 0x05; // Version 5.

            // add user name
            request[nIndex++] = (byte)userName.Length;
            rawBytes = Encoding.Default.GetBytes(userName);
            rawBytes.CopyTo(request, nIndex);
            nIndex += (ushort)rawBytes.Length;

            // add password
            request[nIndex++] = (byte)password.Length;
            rawBytes = Encoding.Default.GetBytes(password);
            rawBytes.CopyTo(request, nIndex);
            nIndex += (ushort)rawBytes.Length;

            // Send the Username/Password request
            s.Send(request, nIndex, SocketFlags.None);
            // Receive 2 byte response...
            nGot = s.Receive(response, 2, SocketFlags.None);
            if (nGot != 2)
                throw new ConnectionException("Bad response received from proxy server.");
            if (response[1] != 0x00)
                throw new ConnectionException("Bad Usernaem/Password.");
        }
        
        // This version only supports connect command. 
        // UDP and Bind are not supported.

        // Send connect request now...
        nIndex = 0;
        request[nIndex++] = 0x05;   // version 5.
        request[nIndex++] = 0x01;   // command = connect.
        request[nIndex++] = 0x00;   // Reserve = must be 0x00

        if (destIP != null)
        {// Destination adress in an IP.
            switch (destIP.AddressFamily)
            {
                case AddressFamily.InterNetwork:
                    // Address is IPV4 format
                    request[nIndex++] = 0x01;
                    rawBytes = destIP.GetAddressBytes();
                    rawBytes.CopyTo(request, nIndex);
                    nIndex += (ushort)rawBytes.Length;
                    break;
                case AddressFamily.InterNetworkV6:
                    // Address is IPV6 format
                    request[nIndex++] = 0x04;
                    rawBytes = destIP.GetAddressBytes();
                    rawBytes.CopyTo(request, nIndex);
                    nIndex += (ushort)rawBytes.Length;
                    break;
            }
        }
        else
        {// Dest. address is domain name.
            request[nIndex++] = 0x03;   // Address is full-qualified domain name.
            request[nIndex++] = Convert.ToByte(destAddress.Length); // length of address.
            rawBytes = Encoding.Default.GetBytes(destAddress);
            rawBytes.CopyTo(request, nIndex);
            nIndex += (ushort)rawBytes.Length;
        }

        // using big-edian byte order
        byte[] portBytes = BitConverter.GetBytes(destPort);
        for (int i = portBytes.Length - 1; i >= 0; i--)
            request[nIndex++] = portBytes[i];

        // send connect request.
        s.Send(request, nIndex, SocketFlags.None);
        s.Receive(response);    // Get variable length response...
        var str = System.Text.Encoding.Default.GetString(response);
        if ((byte)response[1] != 0x00)
        {
            //throw new ConnectionException(errorMsgs[response[1]]);
          
        }
        var wha = s.Connected;
        // Success Connected..
        return s;
    }

    public static Socket ConnectToSocks5Proxy(string proxyAdress, ushort proxyPort, string userName="", string password="")
    {
        IPAddress destIP = null;
        IPAddress proxyIP = null;
        byte[] request = new byte[257];
        byte[] response = new byte[257];
        ushort nIndex;

        try
        {
            proxyIP = IPAddress.Parse(proxyAdress);
        }
        catch (FormatException)
        {   // get the IP address
            proxyIP = Dns.GetHostByAddress(proxyAdress).AddressList[0];
        }

        // Parse destAddress (assume it in string dotted format "212.116.65.112" )
        try
        {
            //destIP = IPAddress.Parse(destAddress);
        }
        catch (FormatException)
        {
            // wrong assumption its in domain name format "www.microsoft.com"
        }

        IPEndPoint proxyEndPoint = new IPEndPoint(proxyIP, proxyPort);

        // open a TCP connection to SOCKS server...
        Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        s.Connect(proxyEndPoint);

        nIndex = 0;
        request[nIndex++] = 0x05; // Version 5.
        request[nIndex++] = 0x01; // 2 Authentication methods are in packet...
        request[nIndex++] = 0x00; // NO AUTHENTICATION REQUIRED
      //request[nIndex++] = 0x02; // USERNAME/PASSWORD
                                  // Send the authentication negotiation request...
        s.Send(request, nIndex, SocketFlags.None);

        // Receive 2 byte response...
        int nGot = s.Receive(response, 2, SocketFlags.None);
        if (nGot != 2)
            throw new ConnectionException("Bad response received from proxy server.");

        if (response[1] == 0xFF)
        {    // No authentication method was accepted close the socket.
            s.Close();
            throw new ConnectionException("None of the authentication method was accepted by proxy server.");
        }

        byte[] rawBytes;


        if (/*response[1]==0x02*/false)
        {//Username/Password Authentication protocol
            nIndex = 0;
            request[nIndex++] = 0x05; // Version 5.

            // add user name
            request[nIndex++] = (byte)userName.Length;
            rawBytes = Encoding.Default.GetBytes(userName);
            rawBytes.CopyTo(request, nIndex);
            nIndex += (ushort)rawBytes.Length;

            // add password
            request[nIndex++] = (byte)password.Length;
            rawBytes = Encoding.Default.GetBytes(password);
            rawBytes.CopyTo(request, nIndex);
            nIndex += (ushort)rawBytes.Length;

            // Send the Username/Password request
            s.Send(request, nIndex, SocketFlags.None);
            // Receive 2 byte response...
            nGot = s.Receive(response, 2, SocketFlags.None);
            if (nGot != 2)
                throw new ConnectionException("Bad response received from proxy server.");
            if (response[1] != 0x00)
                throw new ConnectionException("Bad Usernaem/Password.");
        }

        // This version only supports connect command. 
        // UDP and Bind are not supported.

        // Send connect request now...
        nIndex = 0;
        request[nIndex++] = 0x05;   // version 5.
        request[nIndex++] = 0x01;   // command = connect.
        request[nIndex++] = 0x00;   // Reserve = must be 0x00

        return s;
    }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source