Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Golabi Proxy Server

4.79/5 (10 votes)
20 Sep 2011CPOL2 min read 69K   3.7K  
This is a proxy server with a client that can encrypt your browser request.
softwareuut

Introduction

Golabi proxy is a kind of reserved proxy. This proxy has encryption in requests of browsers for passing through filterings of internet.

Background

In some countries, the ISPs of that country have some kind of request checking to block unauthorized requests to websites. For example, Facebook is one of those websites that is blocked!

These request checkings have a big weakness! They cannot check encrypted request and responses. Because of that, we encrypt requests with AES algorithm. The keys of AES algorithm can be sent with some secret IP and secret ports. But in this project, we use one key as secret.

Using the Code

In this project, we have 2 sides - server side and client side.

First, we receive the requests from browser in the client computer with client Golabi proxy. Next, we encrypt the request. After that, the server side receives the encrypted request and decrypts it. In the server side, our application (server Golabi proxy) has unlimited access to internet. It makes a request and receives a response. Then response returns back to client and browser.

This project is the start of an idea! It has a few problems, but it can be fixed by some improvements.

client.png

Server.png

Client Side Handler

C#
private void Handler()
        {
            bool recvRequest = true;
            string EOL = "\r\n";

            string requestPayload = "";
            List<string> requestLines = new List<string>();
            byte[] requestBuffer = new byte[1];
            byte[] responseBuffer = new byte[1];

            requestLines.Clear();

            try
            {
                //State 0: Handle Request from Client
                while (recvRequest && _ContinueRecive)
                {
                    if (this.clientSocket.Receive(requestBuffer) == 0)
                    {
                        break;
                    }
                    string fromByte = UTF8Encoding.UTF8.GetString(requestBuffer);
                    requestPayload += fromByte;
                    if (requestPayload.EndsWith(EOL + EOL))
                    {
                        recvRequest = false;
                    }
                }

                //State 1: Creating a new socket to connect to server
                Socket destServerSocket = new Socket
		(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                destServerSocket.Connect(this._Info.ServerIP,this._Info.ServerPort);
                destServerSocket.ReceiveTimeout = 12000;
                destServerSocket.SendTimeout = 12000;

                //State 2: Sending New Request Information to Destination Server 
                //and Relay Response to Client
                if (this._Info.EncryptionEnabled)
                {
                    destServerSocket.Send(ASCIIEncoding.ASCII.GetBytes
			(Encryption.Encrypt(requestPayload, this._Info.Key) + 
			"farhadserverfinish"));
                }
                else
                {
                    destServerSocket.Send(ASCIIEncoding.ASCII.GetBytes
			(requestPayload + "farhadserverfinish"));
                }

                if (false)
                {
                    while (destServerSocket.Receive(responseBuffer) != 0)
                    {
                        int tmp = responseBuffer[0];
                        tmp -= 2;
                        if (tmp < 0)
                        {
                            tmp += 256;
                        }
                        responseBuffer[0] = (byte)tmp;
                        this.clientSocket.Send(responseBuffer);
                    }
                }
                else
                {
                    while (destServerSocket.Receive(responseBuffer) != 0)
                    {
                        this.clientSocket.Send(responseBuffer);
                    }
                }
                destServerSocket.Disconnect(false);
                this.clientSocket.Disconnect(false);
            }
            catch (Exception ex)
            {
                this._Info.MessageCenter.setMessage(ex.Message);
            }
        }

Server Side Handler

C#
private void Handler()
        {
            bool recvRequest = true;
            string EOL = "\r\n";

            string requestPayload = "";
            string requestTempLine = "";
            List<string> requestLines = new List<string>();
            byte[] requestBuffer = new byte[1];
            byte[] responseBuffer = new byte[1];

            requestLines.Clear();

            try
            {
                while (recvRequest)
                {
                    this.clientSocket.Receive(requestBuffer);
                    string fromByte = UTF8Encoding.UTF8.GetString(requestBuffer);
                    requestPayload += fromByte;
                    requestTempLine += fromByte;

                    if (requestPayload.EndsWith("farhadserverfinish"))
                    {
                        recvRequest = false;
                    }
                }

                string recivedDec;

                if (this._Info.EncryptionEnabled)
                {
                    recivedDec = Encryption.Decrypt(requestPayload.Replace
				("farhadserverfinish", ""), this._Info.Key);
                }
                else
                {
                    recivedDec = requestPayload.Replace("farhadserverfinish", "");
                }

                requestPayload = "";
                requestTempLine = "";

                int counter = 0;
                //State 0: Handle Request from Client
                while (counter < recivedDec.Length)
                {
                    string fromByte = recivedDec[counter].ToString();
                    counter++;
                    requestPayload += fromByte;
                    requestTempLine += fromByte;

                    if (requestTempLine.EndsWith(EOL))
                    {
                        requestLines.Add(requestTempLine.Trim());
                        requestTempLine = "";
                    }

                    if (requestPayload.EndsWith(EOL + EOL))
                    {
                        break;
                    }

                }
                //State 1: Rebuilding Request Information and Create Connection 
                //to Destination Server
                if (requestLines.Count == 0)
                {
                    return;
                }

                string remoteHost = requestLines[0].Split(' ')[1].Replace
					("http://", "").Split('/')[0];
                string requestFile = requestLines[0].Replace("http://", "").Replace
					(remoteHost, "");
                requestLines[0] = requestFile;

                this._Info.MessageCenter.setMessage(string.Format("Request to {0}", 
					remoteHost));

                requestPayload = "";
                foreach (string line in requestLines)
                {
                    requestPayload += line;
                    requestPayload += EOL;
                }

                Socket destServerSocket = new Socket(AddressFamily.InterNetwork, 
					SocketType.Stream, ProtocolType.Tcp);
                destServerSocket.Connect(remoteHost, 80);
                destServerSocket.ReceiveTimeout = 12000;
                destServerSocket.SendTimeout = 12000;

                //State 2: Sending New Request Information to Destination Server 
                //and Relay Response to Client
                destServerSocket.Send(ASCIIEncoding.ASCII.GetBytes(requestPayload));

                if(false)
                while (destServerSocket.Receive(responseBuffer) != 0)
                {
                    int tmp = responseBuffer[0];
                    tmp += 2;
                    if (tmp > 255)
                    {
                        tmp -= 256;
                    }
                    responseBuffer[0] = (byte)tmp;
                    this.clientSocket.Send(responseBuffer);
                }

                while (destServerSocket.Receive(responseBuffer) != 0)
                {
                    this.clientSocket.Send(responseBuffer);
                }

                destServerSocket.Disconnect(false);
                this.clientSocket.Disconnect(false);
            }
            catch (Exception ex)
            {
                this._Info.MessageCenter.setMessage(ex.Message);
            }
        }

Inside the Code

This project uses multiple TCP connections and opens many clients and sends requests to the server. Then the server waits for accepting the connection.

There is a thread in the server side that is ready to accept connection on a specific port. The connection between server and client establishes throw socket connection, sockets send and receive information with binary format. These binaries would be sent byte after byte.

C#
private void startAcc()
        {
            ConnectionInfo info = new ConnectionInfo
		(ServerIP.Text, ServerPort.Text, ListenIP.Text, 
		ListenPort.Text,KeyInput.Text,this,Encrypt.Checked);

            proxyListener = new ProxyTCPListener(info);

            proxyListener.StartServer();

            while (true)
            {
                proxyListener.AcceptConnection();
            }
        }

We can add some delay:

C#
private void startAcc()
        {
            ConnectionInfo info = new ConnectionInfo
		(ServerIP.Text, ServerPort.Text, ListenIP.Text, 
		ListenPort.Text,KeyInput.Text,this,Encrypt.Checked);

            proxyListener = new ProxyTCPListener(info);

            proxyListener.StartServer();

            while (true)
            {
                proxyListener.AcceptConnection();
            }
            Thread.Sleep(50);
        }

Sockets

Sockets have a big problem. You don't know when to stop. It has just a reading method. We made a con that after sending information, we send this string: "farhadserverend". After that, the client knows that sending information is finished. Now, stop client and dispose it!

C#
destServerSocket.Send(ASCIIEncoding.ASCII.GetBytes
	(Encryption.Encrypt(requestPayload, this._Info.Key) + "farhadserverfinish"));

Queue for the Countries that have Low Speed Internet (Like IRAN)

In some countries because of low speed internet, we have network problems. So in this class, we made a queue for browser requests. These requests will be checked every time from client application. They have 5 seconds for response timeout. If the connection does not respond in 5 seconds, it will be disposed!

C#
class ProxyTCPListener : IDisposable
    {
        private TcpListener _Listener;
        private ConnectionInfo _Info;
        private List<clientconnection> _Clients;

        public ProxyTCPListener(ConnectionInfo info)
        {
            this._Info = info;
            
            this._Listener = new TcpListener(this._Info.LocalIP, this._Info.LocalPort);

            this._Clients = new List<clientconnection>();
        }

        public void StartServer()
        {
            this._Listener.Start();
        }

        public void AcceptConnection()
        {
            if (this._Listener.Pending())
            {
                Socket newClient = this._Listener.AcceptSocket();
                this._Info.MessageCenter.setMessage("ClientQueued...");
                ClientConnection _Client = new ClientConnection(newClient, this._Info);
                _Clients.Add(_Client);
            }
            Thread.Sleep(200);
        }

        public void QueueCheck()
        {
            if (_Clients.Count > 0)
            {
                if (_Clients[0]._ContinueRecive == false && 
				_Clients[0]._finished == true)
                {
                    _Clients.RemoveAt(0);
                }
            }
            if (_Clients.Count > 0)
            {
                if (_Clients[0]._ContinueRecive == false && 
				_Clients[0]._finished == false)
                {
                    this._Info.MessageCenter.setMessage("ClientAccepted...");
                    _Clients[0].StartHandling();
                }
            }
        }

        public void Dispose()
        {
            foreach (ClientConnection client in _Clients)
            {
                client.StopHandling();
            }
            _Listener.Stop();
        }

Encryption

We encrypt the request with AES algorithm:

C#
public static string Encrypt(string clearText, string Password)
        {
            // First we need to turn the input string into a byte array.
            byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(clearText);

            // Then, we need to turn the password into Key and IV
            // We are using salt to make it harder to guess our key
            // using a dictionary attack -
            // trying to guess a password by enumerating all possible words.
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password,
                new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d,
                0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            // Now get the key/IV and do the encryption using the
            // function that accepts byte arrays.
            // Using PasswordDeriveBytes object we are first getting
            // 32 bytes for the Key
            // (the default Rijndael key length is 256bit = 32bytes)
            // and then 16 bytes for the IV.
            // IV should always be the block size, which is by default
            // 16 bytes (128 bit) for Rijndael.
            // If you are using DES/TripleDES/RC2 the block size is
            // 8 bytes and so should be the IV size.
            // You can also read KeySize/BlockSize properties off
            // the algorithm to find out the sizes.
            byte[] encryptedData = Encrypt(clearBytes,
                     pdb.GetBytes(32), pdb.GetBytes(16));

            // Now we need to turn the resulting byte array into a string.
            // A common mistake would be to use an Encoding class for that.
            //It does not work because not all byte values can be
            // represented by characters.
            // We are going to be using Base64 encoding that is designed
            //exactly for what we are trying to do.
            return Convert.ToBase64String(encryptedData);
        }

Decryption

C#
public static string Decrypt(string cipherText, string Password)
        {
            // First we need to turn the input string into a byte array.
            // We presume that Base64 encoding was used
            byte[] cipherBytes = Convert.FromBase64String(cipherText);

            // Then, we need to turn the password into Key and IV
            // We are using salt to make it harder to guess our key
            // using a dictionary attack -
            // trying to guess a password by enumerating all possible words.
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(Password,
                new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65,
                0x64, 0x76, 0x65, 0x64, 0x65, 0x76});

            // Now get the key/IV and do the decryption using
            // the function that accepts byte arrays.
            // Using PasswordDeriveBytes object we are first
            // getting 32 bytes for the Key
            // (the default Rijndael key length is 256bit = 32bytes)
            // and then 16 bytes for the IV.
            // IV should always be the block size, which is by
            // default 16 bytes (128 bit) for Rijndael.
            // If you are using DES/TripleDES/RC2 the block size is
            // 8 bytes and so should be the IV size.
            // You can also read KeySize/BlockSize properties off
            // the algorithm to find out the sizes.
            byte[] decryptedData = Decrypt(cipherBytes, pdb.GetBytes(32), 
					pdb.GetBytes(16));

            // Now we need to turn the resulting byte array into a string.
            // A common mistake would be to use an Encoding class for that.
            // It does not work
            // because not all byte values can be represented by characters.
            // We are going to be using Base64 encoding that is
            // designed exactly for what we are trying to do.
            return System.Text.Encoding.Unicode.GetString(decryptedData);
        }

History

  • 15th September, 2011: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)