65.9K
CodeProject 正在变化。 阅读更多。
Home

Golabi 代理服务器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (10投票s)

2011年9月16日

CPOL

2分钟阅读

viewsIcon

70256

downloadIcon

3742

这是一个可以加密您的浏览器请求的代理服务器和客户端。

softwareuut

引言

Golabi 代理是一种保留代理。此代理对浏览器的请求进行加密,以绕过互联网过滤。

背景

在某些国家/地区,该国的互联网服务提供商会对请求进行某种检查,以阻止对网站的未经授权的请求。例如,Facebook 就是被阻止的网站之一!

这些请求检查有一个很大的弱点!它们无法检查加密的请求和响应。因此,我们使用 AES 算法加密请求。AES 算法的密钥可以通过一些秘密 IP 和秘密端口发送。但在本项目中,我们使用一个密钥作为秘密密钥。

Using the Code

在这个项目中,我们有两个方面 - 服务器端和客户端。

首先,我们在客户端计算机上使用客户端 Golabi 代理接收来自浏览器的请求。接下来,我们加密请求。之后,服务器端接收加密的请求并解密它。在服务器端,我们的应用程序(服务器 Golabi 代理)可以无限制地访问互联网。它发出请求并接收响应。然后响应返回给客户端和浏览器。

这个项目只是一个想法的开始!它有一些问题,但可以通过一些改进来解决。

client.png

Server.png

客户端处理程序

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);
            }
        }

服务器端处理程序

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);
            }
        }

代码内部

该项目使用多个 TCP 连接,打开许多客户端并向服务器发送请求。然后服务器等待接受连接。

服务器端有一个线程,准备好在特定端口上接受连接。服务器和客户端之间的连接通过套接字连接建立,套接字以二进制格式发送和接收信息。这些二进制数据将逐字节发送。

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();
            }
        }

我们可以添加一些延迟

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)

套接字有一个很大的问题。你不知道什么时候停止。它只有一个读取方法。我们做了一个约定,在发送信息后,我们会发送这个字符串:“farhadserverend”。之后,客户端就知道发送信息完成了。现在,停止客户端并释放它!

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

为互联网速度较慢的国家/地区(如伊朗)的队列

在某些国家/地区,由于互联网速度较慢,我们存在网络问题。因此,在这个类中,我们为浏览器请求创建了一个队列。这些请求将由客户端应用程序随时检查。它们有 5 秒的响应超时时间。如果 5 秒内没有响应,它将被释放!

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();
        }

加密

我们使用 AES 算法加密请求

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);
        }

解密

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);
        }

历史

  • 2011 年 9 月 15 日:初始版本
© . All rights reserved.