0

I'm trying to make a proxy server with C#. Here's my code:

static void Main(string[] args)
{
    TcpListener server = null;
    try
    {
        // Set the TcpListener on port 13000.
        Int32 port = 13000;
        IPAddress localAddr = IPAddress.Parse("127.0.0.1");

        // TcpListener server = new TcpListener(port);
        server = new TcpListener(localAddr, port);

        // Start listening for client requests.
        server.Start();

        // Buffer for reading data
        Byte[] bytes = new Byte[256];
        String data = null;

        WebRequest request;
        WebResponse response;




        // Enter the listening loop. 
        while (true)
        {
            Console.Write("Waiting for a connection... ");

            // Perform a blocking call to accept requests. 
            // You could also user server.AcceptSocket() here.
            TcpClient client = server.AcceptTcpClient();
            Console.WriteLine("Connected!");


            data = null;

            // Get a stream object for reading and writing
            NetworkStream stream = client.GetStream();

            int i;
            String[] input;


            // Loop to receive all the data sent by the client. 
            while (stream.DataAvailable)
            {
                data = null;
                i = stream.Read(bytes, 0, bytes.Length);
                // Translate data bytes to a ASCII string.
                data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
                Console.WriteLine(String.Format("Received: {0}", data));

                input = data.Split();

                Console.WriteLine("\n\r\n input[1]" + input[1] + "\n");

                Stream dataStream;
                StreamReader reader;
                string responseFromServer;

                try
                {
                    request = WebRequest.Create(input[1]);

                    response = request.GetResponse();
                    // Process the data sent by the client.
                    data = data.ToUpper();



                    dataStream = response.GetResponseStream();
                    // Open the stream using a StreamReader for easy access.
                    reader = new StreamReader(dataStream);
                    // Read the content.
                    responseFromServer = reader.ReadToEnd();
                    // Display the content
                    Console.WriteLine(responseFromServer);
                    // Clean up the streams and the response.

                    byte[] msg = System.Text.Encoding.ASCII.GetBytes(responseFromServer);

                    // Send back a response.
                    stream.Write(msg, 0, msg.Length);
                    // Console.WriteLine("Sent: {0}", data);
                    //stream.Write();



                    reader.Close();
                    response.Close();
                }
                catch (System.UriFormatException e)
                {
                    Console.WriteLine("Exception due to" + e.Data);
                    Console.WriteLine("Input[1] = " + input[1]);

                }



                data = null;

            }

            // Shutdown and end connection
            client.Close();
        }
    }
    catch (SocketException e)
    {
        Console.WriteLine("SocketException: {0}", e);
    }
    finally
    {
        // Stop listening for new clients.
        server.Stop();
    }


    Console.WriteLine("\nHit enter to continue...");
    Console.Read();
}

It does not work for ssl requests but seems to work for http. However, it does not load any images. I'm using Firefox as the browser. Any ideas why? Also is this the best way to make a proxy server? Are there any other methods?

Uwe Keim
  • 36,867
  • 50
  • 163
  • 268
user3927312
  • 764
  • 1
  • 6
  • 26
  • Create and configure your computer to be a SOCKS5 proxy, that's a thousand times better than trying to hack a C# proxy server. Also the "not loading any pictures" might be due to binary content being transfered, but I see a lot ASCII text conversions in your code. – Maximilian Gerhardt Sep 06 '15 at 11:20
  • I'm actually trying to use this for some other purpose. I don't need to use a proxy as such. I'm just trying to build one. – user3927312 Sep 06 '15 at 11:22
  • I can definetly tell you why SSL Is not working. SSL is encrypted traffic, so there's not ever a cleartext HTTP request sent. However, you're trying to create your own `WebRequest` with the ASCII decoded input data (but there is no ASCII data being sent), that just won't work. My idea of a proxy server is more of a program that relays all the traffic to the target on his behalf, and returns the stream back, then we're back to some kind of HTTP-proxy. – Maximilian Gerhardt Sep 06 '15 at 11:36
  • Yeah I knew about the ssl but first I need it to work with the http images. – user3927312 Sep 06 '15 at 11:38
  • Have you tried using fiddler or wireshark or charles. I suspect it will do what you want... – Aron Sep 06 '15 at 14:20

2 Answers2

3

After a bit of testing, I wrote my own code.

using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Text;

namespace SharpProxy
{
    class MainClass
    {
        private static void StartAcceptingClient(IAsyncResult ar)
        {
            var tcpClient = server.EndAcceptTcpClient(ar);
            server.BeginAcceptTcpClient(new AsyncCallback(StartAcceptingClient), null);

            // Read the data stream from the client.
            NetworkStream stream = tcpClient.GetStream();
            byte[] buffer = new byte[256];
            Console.WriteLine("====== GOT A NEW TCP CLIENT ====== " + tcpClient.Client.RemoteEndPoint.ToString());

            int read = stream.Read(buffer, 0, 1);
            MemoryStream saved = new MemoryStream();
            saved.Write(buffer, 0, read);
            bool isValid = false;
            while (read > 0 )
            {
                read = stream.Read(buffer, 0, 1);
                saved.Write(buffer, 0, read);

                //Check if the last four bytes were a double \r\n.
                var aBytes = saved.ToArray();
                int len = aBytes.Length;
                if (aBytes.Length >= 4 && aBytes[len - 1] == '\n' && aBytes[len - 2] == '\r' && aBytes[len - 3] == '\n' && aBytes[len - 4] == '\r')
                {
                    isValid = true;
                    break;
                }
            }
            Console.WriteLine("End of receive.");
            string originalRequest = Encoding.ASCII.GetString(saved.ToArray());
            byte[] origBytes = saved.ToArray();
            saved.Close();
            Console.WriteLine(originalRequest);
            if (!isValid)
            {
                Console.WriteLine("This wasn't a valid request");
                return;
            }
            //Find the hoster and do our own request.
            string host = originalRequest.Split(new char[] { '\n' }).First(line => line.StartsWith("Host:"));
            host = host.Substring(5).Trim(); //Cut of rest.
            Console.WriteLine("The host is: " + host);
            //Do our own request.
            try
            {
                Socket sProxy = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                sProxy.Connect(host, 80);
                sProxy.Send(origBytes);
                //Now route everything between the tcpclient and this socket...

                //create the state object
                var state = new ProxyState() { ourSocket = sProxy, incomingClient = stream };
                sProxy.BeginReceive(state.ReceiveBuffer, 0, state.ReceiveBuffer.Length, SocketFlags.None, new AsyncCallback(Receiver), state);

                stream.BeginRead(state.SendBuffer, 0, state.SendBuffer.Length, new AsyncCallback(SendToHTTPServer), state);
            }
            catch (Exception) { Console.WriteLine("Exception while doing our own request"); }
        }

        static TcpListener server = null;
        public static void Main(string[] args)
        {
            try
            {
                // Set the TcpListener on port 13000.
                Int32 port = 13000;
                IPAddress localAddr = IPAddress.Parse("0.0.0.0");
                // TcpListener server = new TcpListener(port);
                server = new TcpListener(localAddr, port);
                // Start listening for client requests.
                server.Start();
                Console.WriteLine("Server started on " + server.LocalEndpoint.ToString());
                server.BeginAcceptTcpClient(new AsyncCallback(StartAcceptingClient), null);

                while (true)
                    Thread.Sleep(10);   
            }
            catch (Exception) { Console.WriteLine("Setting up the server failed"); }
        }

        private static void SendToHTTPServer(IAsyncResult ar)
        {
            try
            {
                ProxyState back = (ProxyState)ar.AsyncState;
                int rec = back.incomingClient.EndRead(ar);
                //Push this content to the server
                back.ourSocket.Send(back.SendBuffer.Take(rec).ToArray());
                back.incomingClient.BeginRead(back.SendBuffer, 0, back.SendBuffer.Length, new AsyncCallback(SendToHTTPServer), back);
            }
            catch (Exception e) { Console.WriteLine("Exc. when sending to server: " + e.ToString()); }
        }

        static void Receiver(IAsyncResult state)
        {
            try
            {    
                ProxyState back = (ProxyState)state.AsyncState;
                int rec = back.ourSocket.EndReceive(state);
                //Set up the back and forth connections
                back.incomingClient.Write(back.ReceiveBuffer, 0, rec);
                back.ourSocket.BeginReceive(back.ReceiveBuffer, 0, back.ReceiveBuffer.Length, SocketFlags.None, new AsyncCallback(Receiver), back);
            }
            catch (Exception e) { Console.WriteLine("Exc. when receiving from client: " + e.ToString()); }
        }


        //Every proxy connection has an end an and a beginning, plus a 
        //Sending buffer and a receive buffer
        class ProxyState
        {
            public NetworkStream incomingClient { get; set; }
            public Socket ourSocket { get; set; }
            private byte[] buffReceive = new byte[512];
            private byte[] buffSend = new byte[512];
            public byte[] ReceiveBuffer { get { return buffReceive; } set { buffReceive = value; } }
            public byte[] SendBuffer { get { return buffSend; } set { buffSend = value; } }
        }
    }
}

Here's how it works: I listen on a port, and wait for a HTTP request. This is ended by a double carriage return and a linefeed, a \r\n\r\n. As soon as that happens, I try to parse the original host from the request with a Linq statement. I open my own socket to the server, and make use of the asynchronous callbacks. Basically, you need to write everything that comes from the proxy-iniator to the HTTP-Server, and everything that the HTTP-Server sends back needs to be pushed back to the original client aswell. This is why I set up my own state-object, which just saves the incomming client and the Socket, which is connected to the original HTTP server. And as such, communication can happen and I act as a proxy server.

Here's a screenshot with all the connections done right:

clonk

proxy settings

This proxy server is far from perfect, but the basic concept should be clear. This gave me some inspiration.

Maximilian Gerhardt
  • 4,872
  • 3
  • 20
  • 42
1

You use a streamreader on binary image data, that is not going to work. Not every binary is a valid ASCII encoded string. You should read the response as binary, and write it to the other stream as binary as well. You can try to convert it to ascii to print it to the console, but do not use the converted text to respond, because all the non-valid ascii characters will be converted to ?-s. I did modify your code to first read the response in a MemoryStream, and write that back. The data written to the console is still converted, but not user anywhere else.

static void Main(string[] args)
{
    TcpListener server = null;
    try
    {
        // Set the TcpListener on port 13000.
        Int32 port = 13000;
        IPAddress localAddr = IPAddress.Parse("127.0.0.1");

        // TcpListener server = new TcpListener(port);
        server = new TcpListener(localAddr, port);

        // Start listening for client requests.
        server.Start();

        // Buffer for reading data
        Byte[] bytes = new Byte[256];
        String data = null;

        WebRequest request;
        WebResponse response;




        // Enter the listening loop. 
        while (true)
        {
            Console.Write("Waiting for a connection... ");

            // Perform a blocking call to accept requests. 
            // You could also user server.AcceptSocket() here.
            TcpClient client = server.AcceptTcpClient();
            Console.WriteLine("Connected!");


            data = null;

            // Get a stream object for reading and writing
            NetworkStream stream = client.GetStream();

            int i;
            String[] input;


            // Loop to receive all the data sent by the client. 
            while (stream.DataAvailable)
            {
                data = null;
                i = stream.Read(bytes, 0, bytes.Length);
                // Translate data bytes to a ASCII string.
                data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
                Console.WriteLine(String.Format("Received: {0}", data));

                input = data.Split();

                Console.WriteLine("\n\r\n input[1]" + input[1] + "\n");

                Stream dataStream;
                StreamReader reader;
                string responseFromServer;

                try
                {
                    request = WebRequest.Create(input[1]);

                    response = request.GetResponse();
                    // Process the data sent by the client.
                    data = data.ToUpper();



                    dataStream = response.GetResponseStream();

                    MemoryStream ms = new MemoryStream();
                    dataStream.CopyTo(ms);

                    ms.Position = 0;
                    // Open the stream using a StreamReader for easy access.
                    reader = new StreamReader(ms);
                    // Read the content.
                    responseFromServer = reader.ReadToEnd();
                    // Display the content
                    Console.WriteLine(responseFromServer);
                    // Clean up the streams and the response.

                    byte[] msg = ms.ToArray();

                    // Send back a response.
                    stream.Write(msg, 0, msg.Length);
                    // Console.WriteLine("Sent: {0}", data);
                    //stream.Write();



                    reader.Close();
                    response.Close();
                }
                catch (System.UriFormatException e)
                {
                    Console.WriteLine("Exception due to" + e.Data);
                    Console.WriteLine("Input[1] = " + input[1]);

                }



                data = null;

            }

            // Shutdown and end connection
            client.Close();
        }
    }
    catch (SocketException e)
    {
        Console.WriteLine("SocketException: {0}", e);
    }
    finally
    {
        // Stop listening for new clients.
        server.Stop();
    }


    Console.WriteLine("\nHit enter to continue...");
    Console.Read();
}
Tamas Hegedus
  • 24,663
  • 9
  • 48
  • 87
  • If the response from the server is in binary which is stored in the `datastream` variable, why does the `stream.Write()` method not accept it directly? – user3927312 Sep 13 '15 at 10:18
  • You could use it directly by `dataStream.CopyTo(stream)`. However you can read only once the `dataStream`, hence I save it in a memory stream, and after that print it on the console as a string and respond as well. – Tamas Hegedus Sep 14 '15 at 11:44