Click here to Skip to main content
13,898,662 members
Click here to Skip to main content
Add your own
alternative version


51 bookmarked
Posted 29 Jun 2006

Windows TCP Tunnel

, 12 Jul 2006
Rate this:
Please Sign up or sign in to vote.
This project shows how to forward TCP sockets from one machine to another.


Microsoft .NET contains many enhanced foundation classes that makes writing high performance socket applications easy. This project demonstrates how to use asynchronous sockets to write a high performance yet simple to use TCP tunneling application--mapping ports from one machine to many others.

I was looking for a simple TCP tunneling application but didn't find any on this site. My requirement is a high performance (low CPU utilization) application, that can run as a Windows Service, is easy to configure, and can handle many concurrent ports.


Program/Class Design

Using the code

Since WinTunnel is a Windows Service, the first step is to install it. You can pass a command line switch -install to install it as a service using the LocalSystem account. Optionally, you can pass in a user name and password via the -user and -password switches, respectively.

The program needs to read a configuration file called WinTunnel.ini in the current directory or the directory where the binary is located. The content of the configuration file is very simple. The first line defines a service name--can be anything because it is only used for printing debug messages. The second line defines where to listen for client connections. The third line defines the server connection information.

accept  = 80               <====== defines which port to accept the 
                                   client connection--
                                   either <Port> or <IP:Port>
connect =  <====== defines the target of the connection
                                   --must be in the format <IP:Port>

accept  =
connect =

Interesting issues

One interesting problem I encountered is that .NET requires the use of InstallUtil.exe to put the C# service into the Global Assembly Cache (GAC) and register with the Service Control Manager (SCM). In order to just install it by running the binary, a background process is launched that calls InstallUtil.exe with all the required parameters. The output is then redirected to a buffer and printed out on the console--see the code below.

static void launchProcess(String binary, String argument)
    System.Diagnostics.ProcessStartInfo psInfo = 
     new System.Diagnostics.ProcessStartInfo(binary, argument);

    System.Console.WriteLine(psInfo.FileName + 
                             " " + psInfo.Arguments);

    psInfo.RedirectStandardOutput = true;
    psInfo.WindowStyle = 
    psInfo.UseShellExecute = false;
    System.Diagnostics.Process ps;
    ps = System.Diagnostics.Process.Start(psInfo);
    System.IO.StreamReader msgOut = ps.StandardOutput;
    ps.WaitForExit(5000); //wait up to 5 seconds 
    if (ps.HasExited)
        //write the output

In order to debug a Service application, a debug switch was added to the application. Generally, it is hard to debug a service application because there is no console. When the debug switch is set, log messages are printed on the console. In order to properly shutdown the service application in this mode, a Win32 API call is needed to capture the Ctrl-C key press. Once it is captured, an event object is signaled to properly shutdown--see ConsoleEvent.cs for details.

//create a signal handler to detect Ctrl-C to stop the service
if (m_debug)
    m_ctrl = new ConsoleCtrl();
    m_ctrl.ControlEvent += new 
public static void consoleEventHandler(ConsoleCtrl.ConsoleEvent consoleEvent)
    if (ConsoleCtrl.ConsoleEvent.CTRL_C == consoleEvent)
        Logger.getInstance().info("Received CTRL-C from" + 
                                  " Console. Shutting down...");
        Logger.getInstance().warn("Received unknown" + 
               " event {0}.  Ignoring...", consoleEvent);

Code discussion



In order to make the application design simple, the ThreadPool class was created. It allows the application to be broken down to various tasks and executed by the Thread Pool. This design greatly reduces the need to synchronize--a big performance bottleneck. The idea of the thread pool is that an application should not create many threads. Instead, a small number of them should be created during startup, and reused over and over again until shutdown. Any work that needs to be done in the application will have to be added to a task list, and if there is a free thread, it should pick up the task and execute it.

A good analogy would be with the UPS delivery service. It will be terribly in-efficient if a new worker has to be hired every time a package needs to be delivered. Instead, UPS hires a bunch of them and asks them to deliver packages over and over again.

Similar to the package in the above analogy, a piece of work that needs to be done in the application is called a task. The task must implement the ITask interface--which has two methods: getName() and run(). The getName() method is used for debugging and returns the details of the task. The run() method is the entry point for a thread in the thread pool to perform the work.

public interface ITask 
    void run();
    String getName();

In this application, there are only three types of tasks. The first task is to listen for client connections. This is implemented by the ProxyClientListenerTask. When the task executes, it creates a socket, and then calls the asynchronous BeginAccept() method. The call requires a callback method and also an object as a parameter (this).

public void run()
    listenSocket = new Socket( AddressFamily.InterNetwork, 
                               SocketType.Stream, ProtocolType.Tcp);
    listenSocket.Listen(10); //allow up to 10 pending connections"[{0}] Waiting for client connection at {1}...", 
                m_config.serviceName, m_config.localEP.ToString());

    listenSocket.BeginAccept( new AsyncCallback(
                    ProxyClientListenerTask.acceptCallBack), this);

The callback is invoked when the client connects to the listening socket. The first thing is to retrieve the passed in object which gets the socket. EndAccept() is then called, and the second task is created.

public static void acceptCallBack(IAsyncResult ar)
    ProxyConnection conn = null;
        ProxyClientListenerTask listener = 
                 (ProxyClientListenerTask) ar.AsyncState;

        //create a new task for connecting to the server side.
        conn = m_mgr.getConnection();
        conn.serviceName = listener.m_config.serviceName;
        //accept the client connection
        conn.clientSocket = listener.listenSocket.EndAccept(ar);"[{0}] Conn#{1} Accepted new connection." + 
                    " Local: {2}, Remote: {3}.", 
            conn.clientSocket.RemoteEndPoint.ToString() );

        conn.serverEP = listener.m_config.serverEP;
        //Start listening for connection on this port again
        listener.listenSocket.BeginAccept( new AsyncCallback(
          ProxyClientListenerTask.acceptCallBack), listener);

        //now try to connect to the server
        ProxyServerConnectTask serverTask = 
                    new ProxyServerConnectTask(conn);
    catch (SocketException se)
        logger.error("[{0}] Conn# {1} Socket Error occurred" + 
                     " when accepting client socket. Error Code is: {2}",
                     conn.serviceName, conn.connNumber, se.ErrorCode);
        if (conn != null)
    catch (Exception e)
        logger.error("[{0}] Conn# {1} Error occurred when" + 
            " accepting client socket. Error is: {2}",
            conn.serviceName, conn.connNumber, e);
        if (conn != null)
        conn = null; //free reference to the object

The second task is to connect to the server that the client's connection should be mapped to. This is implemented by the ProxyServerConnectTask. When the task executes, it creates a new socket, and makes an asynchronous BeginConnect() to connect to the server.

public void run()
    m_conn.serverSocket = new Socket( AddressFamily.InterNetwork, 
                                      SocketType.Stream, ProtocolType.Tcp);
         new AsyncCallback(connectCallBack), m_conn);    

The callback is invoked when the connection to the server is established and the third task is created.

public static void connectCallBack(IAsyncResult ar)
    ProxyConnection conn = (ProxyConnection) ar.AsyncState;

                       "[{0}] ProxyConnection#{1}--connected " + 
            "to Server.  Server: {2}, Local: {3}.",

        //create task for proxying data between 
        //the client and server socket
        ProxySwapDataTask dataTask  = new ProxySwapDataTask(conn);
    {...removed error handling code...}

The third task takes both the client socket and the server socket, and swaps data back and forth between them. This is implemented by the ProxySwapDataTask. There is a callback sending and receiving data for both the client and the server socket. There are also various checks to detect socket errors. When an error occurs, both the client and the server socket are shutdown.

public void run()
    //validate that both the client side and server 
    //side sockets are ok. If so, do read/write
    if (m_conn.clientSocket == null || m_conn.serverSocket == null)
        logger.error("[{0}] ProxyConnection#{1}--Either" + 
                     " client socket or server socket is null.",
                     m_conn.serviceName, m_conn.connNumber);

    if (m_conn.clientSocket.Connected && m_conn.serverSocket.Connected)
        //Read data from the client socket
        m_conn.clientSocket.BeginReceive( m_conn.clientReadBuffer, 
               0, ProxyConnection.BUFFER_SIZE, 0,
               new AsyncCallback(clientReadCallBack), m_conn);
        //Read data from the server socket
        m_conn.serverSocket.BeginReceive( m_conn.serverReadBuffer, 
               0, ProxyConnection.BUFFER_SIZE, 0,
               new AsyncCallback(serverReadCallBack), m_conn);
        logger.error("[{0}] ProxyConnection#{1}: Either" + 
            " the client or server socket got disconnected.", 
            m_conn.serviceName, m_conn.connNumber );
    m_conn = null;

There is a callback for sending and receiving data for both the client and the server socket. There are also various checks to detect socket errors. When an error occurs, the connection is shutdown, and both the client and the server socket are closed.

End note

Unlike others, this project is the complete source of a fully functioning application. As such, there are various classes such as logging that I don't even cover in the discussion. I only try to point out the interesting things that I encountered when designing and implementing the application. Hopefully, it will be useful for someone doing something similar.


  • 2006-06-28 - Initial implementation.
  • 2006-07-10 - Enhanced to show more debug messages, added more error handling logic, and cleanup of objects. Also updated this documentation.


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

United States United States
No Biography provided

You may also be interested in...

Comments and Discussions

Questioninspect and drop connections Pin
Member 1144861312-Feb-15 15:27
memberMember 1144861312-Feb-15 15:27 
QuestionExcellent Pin
small_programmer15-Sep-14 5:21
membersmall_programmer15-Sep-14 5:21 
QuestionThank you Pin
Roohollah Yeylaghi Ashrafi4-Sep-14 23:21
memberRoohollah Yeylaghi Ashrafi4-Sep-14 23:21 
GeneralMy vote of 5 Pin
ray pixar5-May-13 21:53
memberray pixar5-May-13 21:53 
QuestionWell written article but code design is partly horrible Pin
damian98now28-Aug-12 10:13
memberdamian98now28-Aug-12 10:13 
GeneralMy vote of 5 Pin
rrossenbg25-Dec-11 0:01
memberrrossenbg25-Dec-11 0:01 
QuestionAre you available for contract work? Pin
Bill SerGio Jr.16-Mar-11 17:24
professionalBill SerGio Jr.16-Mar-11 17:24 
GeneralMy vote of 5 Pin
Randar Puust19-Oct-10 9:20
memberRandar Puust19-Oct-10 9:20 
GeneralWell done. Very clean code Pin
Jeremy Samuel10-Dec-09 3:13
memberJeremy Samuel10-Dec-09 3:13 
QuestionWhat sort of license does this code have? Pin
JordanABortz18-Nov-09 8:53
memberJordanABortz18-Nov-09 8:53 
QuestionTunnel via HTTP? Pin
Marco Kummer26-Aug-09 1:23
memberMarco Kummer26-Aug-09 1:23 
GeneralUDP Pin
CotizoS4-Jul-09 3:11
memberCotizoS4-Jul-09 3:11 
QuestionLog window Pin
Dott. Marco Zaino17-Dec-08 8:13
memberDott. Marco Zaino17-Dec-08 8:13 
GeneralBug and its stack trace Pin
Member 43231507-Oct-08 4:38
memberMember 43231507-Oct-08 4:38 
GeneralBug Pin
[JoE]12-Sep-08 10:01
member[JoE]12-Sep-08 10:01 
GeneralTCP ports forwarding in a little different way Pin
Oleksandr Kucherenko27-Apr-08 0:46
memberOleksandr Kucherenko27-Apr-08 0:46 
GeneralExcellent Pin
m_pet16-Oct-07 7:04
memberm_pet16-Oct-07 7:04 
GeneralAwesome Pin
mail57235214-Mar-07 15:18
membermail57235214-Mar-07 15:18 
QuestionAuthentication Pin
alex_fetbroyt13-Sep-06 22:00
memberalex_fetbroyt13-Sep-06 22:00 
GeneralInstallUtils Pin
Bill Seddon3-Sep-06 5:55
memberBill Seddon3-Sep-06 5:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web03 | 2.8.190306.1 | Last Updated 12 Jul 2006
Article Copyright 2006 by Han_Jun_Li
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid