How to create proxy server between application and remote host


This example will show how to create proxy-like server that will stand between application (client) and its remote server using asynchronous sockets.

Such a server gives ability to filter and/or inject packets during connection and should be invisible to the remote host. Example shows also how to use asynchronous socket functions and how to force client application to use local IP address.

The most important problem to solve before creating proxy server is to force client application to connect to our local interface instead of their default one. The easy way is to override destination IP address by setting it to the local interface (network adapter). This can be done with either active NIC (if there is more than 1 available because we still need one to communicate with the world) or virtual network adapter. The second approach works well.

To install Microsoft Loopback Adapter on Windows 7 (and Vista) open Device Manager (WinKey+Break -> Hardware -> Device Manager) and select any device then open Action menu and Add Legacy Hardware. Click Next, select manual installation from list, Next and select Network adapters. From the manufacturers list select Microsoft and then Microsoft Loopback Adapter. Click next few more times to finish installation.
For Windows XP open Control Panel and select Add Hardware. Click Next, select Yes, I have already connected the hardware. Next and scroll Installed hardware list to the bottom, Add a new hardware device, click Next. Select Install the hardware manually, click Next, choose Network adapters, Next, use Microsoft as Manufacturer and finally select Microsoft Loopback Adapter. Click Next few times to finish installation.

When virtual network adapter is installed desired IP address has to be set. This example uses google.com IP address. Open network connections and select local connection assigned to Microsoft Loopback Adapter (probably "Local connection 2" or 3). Then properties and TCP/IPv4 properties. Manually set IP address to 209.85.229.147. Leave gateway blank. Now you can test if settings are correct by simply write http://209.85.229.147/ address in any browser. Google.com page should not be available (to undo this turn off loopback adapter connection).

How proxy server works?

1.JPG

Picture above presents standard connection between client and server. Client application is using active network interface with some IP address set. During connection OS assigns port to created socket.

image1.gif

The second picture shows proxy server between client and remote server. Listener interface is hiding remote server's IP. When listener detects incoming connection proxy server will try to connect to the remote server using outgoing interface and then transmit any packets between client and server.

The code

Application has small configuration section:

public const int PACKET_BUFFSIZE = 2048; // packet buffer in bytes
private const string LOCAL_OUT_IPADDR = "192.168.1.100"; // active localhost external address (set yours)
private const string TARGET_IPADDR = "209.85.229.147"; // destination address (google.com)
private static readonly int[] TARGET_PORTS = new int[] { 
80,
    //8080
};

Remember to use your active IP as LOCAL_OUT_IPADDR. Since this example is using port 80 for local listener you have to temporary stop IIS server if you have it installed. 

Buffer size is not so important because even if some data is larger than buffer capacity it will be divided to pieces and process as few packets.

Separate thread with listener is started for every port in TARGET_PORTS array (only one, port 80 in example): 

private static void RunListener(IPAddress listenerInterface, IPAddress outgoingInterface, int listenerPort, int listNo)
{
    
TcpListener listener = new TcpListener(listenerInterface, listenerPort);
    listeners.Add(listener);
    listener.Start();
    
Console.WriteLine("listener {0} started at {1}:{2}", listNo, listenerInterface.ToString(), listenerPort);

    while (true)
    {
        
Console.WriteLine("listener {0} is waiting for a new client", listNo);
        
Socket incoming_socket = listener.AcceptSocket();
        
Console.WriteLine("listener {0}: client connected", listNo);

        // connecting remote host
        Console.WriteLine("listener {0} is connecting to remote host {1}:{2}", listNo, TARGET_IPADDR, listenerPort;
        
Socket remote_socket = ConnectSocket(new IPEndPoint(outgoingInterface, 0), TARGET_IPADDR, listenerPort);
        
if (remote_socket == null)
        {
            
Console.WriteLine("listener {0}: outgoing connection failed", listNo);
            
continue;
        }
        
Console.WriteLine("listener {0}: connected to remote host {1}:{2}", listNo, TARGET_IPADDR, listenerPort);

        // begin receive on input
        SocketStateObj iso = new SocketStateObj(incoming_socket, remote_socket);
        sockets.Add(iso);
        incoming_socket.BeginReceive(iso.buffer, 0,
 SocketStateObj.BUFF_SIZE, SocketFlags.None,
        
new AsyncCallback(Read_Callback), iso);

        // begin receive on output
        SocketStateObj oso = new SocketStateObj(remote_socket, incoming_socket);
        sockets.Add(oso);
        remote_socket.BeginReceive(oso.buffer, 0,
 SocketStateObj.BUFF_SIZE, SocketFlags.None,
        
new AsyncCallback(Read_Callback), oso);
    }
}

The above code section responsibility is to wait for incoming connection (from client application), accept it and then connect to remote host from outgoing interface. When this is done both created sockets are set to receiving data in asynchronous mode.

Socket.BeginReceive() method has a few arguments from which the most important are AsyncCallback callback and object state. Callback is a delegate to function handling received data and state is an object passed to that function. It is good to create your own method used as state argument:

public class SocketStateObj
{
    
public static readonly int BUFF_SIZE = Program.PACKET_BUFFSIZE;
    
public Socket in_socket = null;
    
public Socket out_socket = null;
    
public byte[] buffer = new byte[BUFF_SIZE];

    public SocketStateObj(Socket in_socket)
    {
        
this.in_socket = in_socket;
    }

    public SocketStateObj(Socket in_socket, Socket out_socket)
    {
        
this.in_socket = in_socket;
        
this.out_socket = out_socket;
    }
}

Read_Callback() function from this example is responsible for handling packets data. It is retrieving SocketStateObj object from AsyncState object and finishing receive:

(...)
SocketStateObj so = (SocketStateObj)ar.AsyncState;
s1 = so.in_socket;
s2 = so.out_socket;
int count = s1.EndReceive(ar);
(...)

Socket s1 (input one) is then set again to receive and s2 (output) used to transmit data to destination:

(...)
s1.BeginReceive(so.buffer, 0, 
SocketStateObj.BUFF_SIZE, SocketFlags.None,
                        
new AsyncCallback(Read_Callback), so);
(...)
s2.Send(tmpbuff, 0, count, 
SocketFlags.None);
(...)

Read_Callback() function here is also a place where packets can be filter, resend or inject (this is marked in attached code). In this example only packet information is retrieved and shown on console.

How to verify if example works?

If configuration steps are performed correctly and all necessary interfaces are present in system http://209.85.229.147/ address is active in browser, google.com is available and application window will looks like in following screen:

image2.gif

Of course google.com address and a browser as a client application was only for this example purpose. Almost any application can be used if we want to monitor it or automate some tasks.

For debugging you can use Wireshark packet analyzer (http://www.wireshark.org/ ) to check if all packets are correctly transmit and some of built in Windows commands like ipconfig, netstat -n and netstat -a.

See attached code sample for complete solution.

Up Next
    Ebook Download
    View all
    Learn
    View all