Ssh tunnelling
From Granizada
This note tries to uncomplicate the art of ssh tunnelling, also known as port forwarding. I'm convinced so many people get confused because the ssh man page for the options -L and -R is badly written in a subtle way. So in addition to providing examples I include a proposed fix to the OpenSSH man page. I've also sent it to the OpenSSH project.
Dan 06:03, 11 April 2007 (CST)
After reading about -L in the OpenSSH man page for the ssh command:
-L [bind_address:]port:host:hostport
Specifies that the given port on the local (client) host is to be
forwarded to the given host and port on the remote side. This
works by allocating a socket to listen to port on the local side,
optionally bound to the specified bind_address. Whenever a con‐
nection is made to this port, the connection is forwarded over
the secure channel, and a connection is made to host port
hostport from the remote machine.
someone wrote to me (as many have before) saying:
> Sorry, I have read this 4 times and tried experimenting but I just can't > grasp it!
Which is exactly what I thought when I first met this too. That's because, in a subtle way, it is one of the most obscurely-written man pages ever. It looks like it should make sense, but the sense always slides just out of sight around the corner :-)
Here's the key to everything:
We'll illustrate this by tunnelling the SMTP mail protocol, but it works in the same way for any other TCP protocols and, with some magic, for UDP protocols as well.
Here is most simple version of the effect we want:
- Computer A (local computer) running ssh and nothing else
- Computer B (remote computer) running an smtpd mail server and the ssh daemon
- I type a particular ssh command on computer A and leave that command running...
- Then in another window I type "telnet localhost 25" and start talking to the smtpd on the far machine
This is a transparent tunnel and it is what you want to magically stretch the listening SMTP process from some remote machine securely to a local port.
So, let's implement the above scenario in one single command at computer A's root prompt, as you'll find in plenty of documentation:
# ssh -L 25:localhost:25 unprivuser@computerB
You should be able to log in via ssh in the usual way, and get a prompt that appears the same. Now in a separate window, telnet to port 25 on your local machine. You should see a prompt for the remote mail server come up, eg:
$ telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.localdomain.
Escape character is '^]'.
220 elsewhere.example.com ESMTP - Mail server ready.
Voila, computer A now answers on port 25 as if it were running an SMTP daemon, even though it isn't. And you can't eavesdrop on the traffic between the two machines because it is encrypted by the ssh protocol.
So as well as providing you with an interactive prompt on the remote machine you also got a forwarded port. As soon as you log out of your ssh session the forwarded port vanishes.
But you might well have got it right by accident! This only works if on the remote machine the same daemon is listening on the external IP address as on localhost. I think this is wrong at many levels, not least because having programs listening on all interfaces by default is very bad design. But you'll find it used quite a bit, so this example is valid.
Let's make this a bit more precise:
# ssh -L 25:computerB:25 unprivuser@computerB
But you might still have got this going by accident, because both port numbers are the same. It is also very common for the port numbers to need to be different. For example, you might not want or be able to use root privilege locally, and Unix design requires only processes owned by root can listen on ports lower than 1024.
Imagine a salesman with a laptop in Hong Kong. All outgoing company mail is required to go via the corporate SMTP server in Paris and the mail client on the laptop really can't be trusted to be clever or reliable (a very common situation.) The laptop is able to make an ssh connection to the mail system in Paris.
By forwarding port 25 of the server in Paris to a local port higher than 25, and getting the mail software to send all mail via the local port, we have achieved the goal without having insecure email crossing the Internet. And the Paris mail server is only accessible by people with ssh access, so there is no chance of spam or malware being sent.
Here's how it is done:
$ ssh -L 12345:paris.example.com:25 unprivuser@paris.example.com
-R is exactly the same only in the opposite direction, and the tip about the colons is reversed too.
Beyond the ssh Endpoint
The previous example only takes you as far as tunnelling services running on a machine that you also have ssh access to. In fact the OpenSSH lets you forward any service the remote computer can see. So in the previous example, imagine the SMTP service is not in fact on the machine called paris, but on some other machine on the same network as paris. This other machine is not at all visible to the outside world, even less so than paris is, which at least exposes the ssh port. Here's the command:
$ ssh -L 12345:lyons.example.com:25 unprivuser@paris.example.com
This says "log in to the machine paris.example.com, then connect from paris to port 25 on the machine lyons, and forward all traffic to and from there to port 12345 on the local machine."
Fixing the OpenSSH Man Page
This addresses the problem by defining the terms more clearly:
-L [bind_address:]localhostport:remotehost:remoteport
Specifies that the given localhostport on the local (client) host is
to be forwarded to the given remotehost and remoteport on the remote side.
This works by allocating a socket to listen to localhostport on the local
side, optionally bound to the specified bind_address. Whenever a con-
nection is made to the local port, the connection is forwarded over
the secure channel, and a connection is made to remotehost on remoteport
from the remote machine. Note: remotehost may be any host that the
ssh endpoint machine (ie the machine specified with ''user@hostname'') can
access, not just the endpoint machine itself.
Examples forwarding a remote SMTP port to local port, where the remote
service is running on the same machine as the ssh login:
$ ssh -Nf -L 12345:faraway.example.com:25 \
unprivuser@faraway.example.com
$ telnet localhost 12345
Trying 127.0.0.1...
Connected to localhost.localdomain.
Escape character is '^]'.
220 faraway.example.com ESMTP - Mail server ready.
Example forwarding a remote web server inside a network to a local port,
where the ssh login is on a machine that can see the remote server but
not on it. This will work for any TCP service visible to the ssh login:
$ ssh -Nf -L 9876:intranet-server.example.com:80 \
unprivuser@firewall.example.com
$ firefox localhost:9876
(web page for protected intranet displayed.)
| | This content is licensed under the Creative Commons Attribution ShareAlike License v. 2.5: http://creativecommons.org/licenses/by-sa/2.5/ |
| | GFDL: Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". (shearer.org uses but does not currently recommend the GDFL and here's the explanation why. ) |
