Download from
http://www.nyangau.org/tunnel/tunnel.zip
.
SSH has a great port forwarding capability, you can :-
$ ssh -L 1111:192.168.1.7:2222 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 196.168.1.7 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 127.0.0.1
, 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.
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:127.0.0.1:8080
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:127.0.0.1:8080 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.
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:127.0.0.1:8080 -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.
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:127.0.0.1:8080 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:127.0.0.1:8080 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:127.0.0.1:8081 ssh user@remote user@remote's password: Remote: Couldn't connect to 127.0.0.1:8081 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:127.0.0.1:8080 -b 1 telnet.exp remote user Password for user@remote: Local: Pre-handshake: spawn telnet -c -K remote Local: Pre-handshake: Trying 192.168.1.200... Local: Pre-handshake: Local: Pre-handshake: Connected to remote.dom (192.168.1.200). Local: Pre-handshake: Local: Pre-handshake: Escape character is '^]'. Local: Pre-handshake: Local: Pre-handshake: Local: Pre-handshake: remote.dom (Linux release 2.6.22.14-72.fc6 #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:127.0.0.1:8080 -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 :-
/etc/hosts
mapping
the site name to 127.0.0.1
.
This allows you to reference the site locally by the correct name,
which then also appears in the Host:
HTTP request header.
/etc/hosts
trick above may be needed for this reason.
/etc/hosts
trick (or in the case of HTTPS, ignore your
browsers warning).