Introduction

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.

Overview

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.

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.

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.

Code discussion

ThreadPool

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.

publicinterface 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).

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.

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.

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.

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.

History

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.

License

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.