Download from http://www.nyangau.org/tunnel/tunnel.zip.


SSH has a great port forwarding capability, you can :-

$ ssh -L 1111: user@remote

and as well as getting a login to the remote system, when you make a connection to port 1111 on the local system, the connection is tunnelled over the SSH session and on the remote end, and from there a connection is made to host on port 2222.

This feature is often used when administering systems on the far side of firewalls. The administrator SSHs in to the remote system, using -L and then connects their browser to, which connects them the web interface on the remote system.

Similar functionality exists in the reverse direction, using -R. An example use of this is when logging in to remote system, and then using rsync to fetch content back from the local system.

Essentially, if you can SSH to a system, you can send any kind of traffic you like to/from it. Or, if one port is open, they might as well all be. (Note: The author recognises that there are protocols that don't forward nicely, because they have IP addresses, ports, hostnames etc inside the protocol itself, but these are the minority case).

It is for this reason, some systems administrators disable the SSH port forwarding capability, by editing /etc/ssh/sshd_config so that it has AllowTcpForwarding no. This does not actually improve security, it is merely makes things somewhat inconvenient. Port forwarding can still be done, using alternative port forwarders that pass their traffic through the SSH console connection itself.

Finally, it would be really nice to be able to port forward through Telnet as well, but it has no such option.


Tunnel is a simple Java program that can tunnel TCP connections between itself, through a go-between program, to a child copy of itself.

over no transport

The go-between program can be nothing, it which case Tunnel can be used to perform port address translation. eg:

$ java -jar tunnel.jar -L 1111:

over SSH

Or the program can be SSH. Even though the server side may have disabled port forwarding, it still works.

To use, you need Java 5 or better, and tunnel.jar. These must be available locally, and remotely :-

$ scp tunnel.jar user@remote:

To use, do something like this :-

$ java -jar tunnel.jar -L 1111: ssh user@remote

In this example, for as long as Tunnel remains running, if you connect to port 1111 locally, you'll be connected to port 8080 on the remote host.

Admittedly, its not brillantly fast. All local to remote traffic is received by the parent Tunnel, base-64 encoded and piped into the standard input of SSH, and then onto the child Tunnel process, who passes it to the remote socket. All remote to local traffic is echoed on the standard output of the child Tunnel process in base-64 encoded form, passes back through SSH, is decoded by the parent Tunnel, and passed to the local socket.

over Telnet

Tunnel can be made to work over Telnet. To do this, Telnet needs to be wrapped in an Expect script, which handles prompting for the password, and handling the login process. The Tunnel package includes telnet.exp. A typical use might be :-

$ java -jar tunnel.jar -L 1111: -b 1 telnet.exp remote user

The Telnet+Expect combination has internal buffer limitations which mean that Tunnel must be told to send smaller data packets. The -b 1 says send 1 KB packets. I find that 1 KB or 2 KB is ok, but 4 KB packets get corrupted. The default is 16 KB, which appears reliable through SSH.

It should be noted that the Expect+Telnet combination echos back everything that the parent Tunnel process types back to itself. So the parent sees what it sent, and what the child sent to it. Luckily, Tunnel annotates every packet it sent with who sent it, and can ignore this unwanted traffic.

over PuTTY

PuTTY incudes plink.exe which is much like the normal command line ssh executable. Unfortunately, it prompts for its password from standard input, and tunnel.jar will not supply this. One insecure (password on the command line) workaround is :-

C:\>java -jar tunnel.jar -L 1111: plink user@remote -pw password

Alternatively, as with Telnet, a Windows version of Expect could be used to handle entering the password.

Or maybe use the MinGW version of ssh.exe.


Running Tunnel with no arguments reveals its full usage :-

$ java -jar tunnel.jar
usage: Tunnel {-L lp:rh:rp} {-R rp:lh:lp} [-j java] [-t jar] [-b size] command
flags: -L lp:rh:rp  port forward
       -R rp:lh:lp  port backward
       -j java      remote java command (default: java)
       -t jar       remote .jar file (default: tunnel.jar)
       -b size      buffer size (between 1 and 64 KB, default is 16 KB)
       command      command (eg: ssh user@host)

You can have many port forwards or backwards in the same run of Tunnel. Note that lh or rh can be host IPs, or hostnames.

If Java 5 or later isn't on the users path in the remote system, you can pass something like -j /opt/jdk1.6.0/bin/java.

If your copy of tunnel.jar isn't in the users directory on the remote system, you can pass something like -t java/tools/tunnel.jar.

An example of -b 1 was discussed earlier regarding Telnet. Changing this value may also be necessary if other remote access clients are used, depending on their quality of implementation.

It may well be possible to use other remote access clients or terminal emulators. For example, maybe netcat could be used.

Exit the program by sending it a break (eg: ^C).


On UNIX systems, non-root users cannot bind to ports below 1024. So if you try it, you could see :-

$ java -jar tunnel.jar -R 100: ssh user@remote
user@remote's password: 
Remote: Cannot listen on port 100

You can also see this message if someone else is already listening on the port.

If the destination of a port forward cannot be resolved, when someone attempts to use it, you can see :-

$ java -jar tunnel.jar -L 1111:farfaraway:8080 ssh user@remote
user@remote's password: 
Remote: Couldn't resolve hostname in farfaraway:8080 on behalf of port=1111 connId=0
Local: Connection terminated connId=0

If the connection cannot be made, you can see :-

$ java -jar tunnel.jar -L 1111: ssh user@remote
user@remote's password: 
Remote: Couldn't connect to on behalf of port=1111 connId=0
Local: Connection terminated connId=0

In the telnet.exp Expect script, log_user 0 is used so that the login process isn't displayed. If this is not done, then you can see Local: Pre-handshake: messages. This can be helpful when diagnosing login problems :-

$ java -jar tunnel.jar -R 1111: -b 1 telnet.exp remote user
Password for user@remote:
Local: Pre-handshake: spawn telnet -c -K remote
Local: Pre-handshake: Trying
Local: Pre-handshake: 
Local: Pre-handshake: Connected to remote.dom (
Local: Pre-handshake: 
Local: Pre-handshake: Escape character is '^]'.
Local: Pre-handshake: 
Local: Pre-handshake: 
Local: Pre-handshake:     remote.dom (Linux release #1 SMP Wed Nov 21 14:10:25 EST 2007) (3)
Local: Pre-handshake: 
Local: Pre-handshake: login: user
Local: Pre-handshake: Password for user:
Local: Pre-handshake: login: Cannot resolve network address for KDC in requested realm while getting initial credentials
Local: Pre-handshake: 
Local: Pre-handshake: Last login: Sun May 24 12:14:37 from local
Local: Pre-handshake: $ java -jar tunnel.jar -R 1111: -child
Local: Pre-handshake: 

In the above, you can see the child copy of Tunnel being started, with the -child argument. A user would never run Tunnel with this argument.


Not every protocol port forwards cleanly, here are some notes :-

This documentation is written and maintained by the Tunnel author, Andy Key