sábado, 6 de junio de 2009

SSH Tunnels: Using a service from a nated (twice) box


Recently I have being managing a box using a 3rd party application that allowed me to handle a windows box where I could use putty to get SSH access to a linux box. It had to be done this way because both my box and the linux box are nated, so they can't reach each other. Let me say it was a real PITA. The keyboard layouts were getting on my nerves. Some important keys didn't work sometimes... or at all (like ; or @ or ', etc). After a while I was encouraged enough to dig for a solution to get access to the SSH service of the linux box directly (or almost) instead of depending on this mess I was using.

First, let me introduce SSH tunnels before I dig into the actual solution to my problem.

SSH Tunnels

SSH tunnels are used between an ssh client and a server so that there is one parallel trusted (encrypted) connection using SSH as its transport.

When the tunnel is set up, there will be a passive listening side and one active connecting side. On the passive end we set up a port so that the tunnel waits for connections of clients to this port. When a client connects to this port, on the active side there is another connection to (potentially) another host/port and so the tunnel connects the client that used the passive port to the new connection on the active side.

The tunnels can be set up so that either our client is the listening side or the active side. But, it will never be both in a single side. It's either listening or connecting, and the tunneled connection is always established from the listening side to the active side.

So, how do this work? Well, let's do some simple examples.

L Tunnel (client side is the listening end)

On a local tunnel, we set up a port on our side and the SSH server will be the connecting side.

Let's say we want to get access to a HTTP service that is on the SSH server, but we want to use one encrypted transport for the transmission.

Say we will use our local port 8080, and on the other end the HTTP service is listening on port 80, the user to connect to the SSH service is sshuser and the host is sshhost. So, we set up the connection like this:

ssh -nNT -L 8080:localhost:80 sshuser@sshhost

After the tunnel is set up, we can use a web browser to use the http server:


Ok, let's explain the details so we can get the devil out of the equation.

-nNT is used so that SSH doesn't start a SSH terminal session besides the tunnel (as I don't want to use it).

-L 8080:localhost:80 Here is where the tunnel is set up. The first parameter (8080) is the port we want to set up on the listening end (our host for a L Tunnel). Then the interesting part, localhost:80... with this we are telling the active side (on the SSH server for a L Tunnel) that when a client connects to our listening port (8080) we want the other end to connect to host localhost (localhost to the other end, the SSH server, that is) port 80 (http service).

After running that command on our box, we can see this with netstat:

tcp 0 0* LISTEN 3310/ssh

As you can see, we set up a listening port on our host on port 8080 and it's only available to processes running on our host (I guess it's possible to fool this a little with a little nating, but it's out of the scope of this article).

Now, we just have to use this port on our host to use the HTTP service on the other end. That's why we say http://localhost:8080.

In this case we used the HTTP service of the same SSH server we used to set up the tunnel. But we could use the HTTP service of yet another host that's accessible to the SSH server. Say there is a host that's accessible to the SSH server through IP (as you can see, it's a private network that could be only accessible to the SSH server and not us from our host). In that case:

ssh -nNT -L 8080: sshuser@sshhost

Then we use our browser:

R Tunnel (ssh server is the listening end)

Say we want to set up port 2000 on the other end of the tunnel so that when clients connect to it, we will let those clients use our HTTP service on our host. We do basically the same we did before:

ssh -nNT -R 2000:localhost:80 sshuser@sshhost

As you see, the only real change is that we said -R instead of -L. All it does is invert the direction the tunnel is set up (listening side on the SSH server).

After we set it up, on the other end we can use netstat to check if we are listening:

tcp 0 0* LISTEN

Then we should be able to browse with a client from the other end of the connection by using port 2000:


As in the case of L Tunnels, the order of the parameters of the tunnel is always the same: port on the listening side:server host on the active side:port on the active side.

And just like in the case of the L Tunnel, we could use a R Tunnel to connect to a host different from the active host of the tunnel. So say I want to enable access to a remote desktop service of a windows box that's on my private network accessible (to me, that is) through IP Let's say I'll use port 3000 on the other side:

ssh -nNT -R 3000: sshuser@sshhost

Then on the other side:
rdesktop localhost:3000

And it's done!

Now, let's work on our problem.

Access to a service on a host that's nated from a box that's nated too

Well.... as both boxes are nated, then it's impossible to get them in touch with each other.... directly, that is. But I bet you can use a box that has a SSH service that's accessible to the original two boxes, can't you? I bet you do! And then, we can do this:

On the side of the box that has the SSH service we want to get access to:

ssh -nNT -R 2000:localhost:22 sshuser@sshhost

What we do there is set up port 2000 on the middle box so that when a client connects to it, it will be connecting to the SSH service of the host we are running the command from. In other words, we have forwarded the ssh service of this host to port 2000 of the middle box.

Then, on the box we want to run SSH from to get access to the other box:

ssh -nNT -L 4000:localhost:2000 sshuser@sshhost

What we do is set up port 4000 or our host so that when a client connects to it, there will be a connection on the middle box to its port 2000 (which is the forwarded SSH service of the ending box). In other words we have forwarded the SSH service of the ending box to our port 4000.

Then we can use a ssh client to get in touch with the service we are interested in:

ssh -p 4000 remoteuser@localhost

And it's done! What do you think?

5 comentarios:

  1. Used it before, and it is a jolly nice way of doing it.

  2. Another handy trick:

    ssh supports 'socks proxy' which means you and port forward your localhost http to a destination remote host to handle proxying http requests.


    $ssh -D 8080 username@remote_host_or_ip

    In Firefox->Preferences->Advanced->Network->Settings:

    Select 'Manual proxy configuration',
    Socks host:
    Port: 8080
    Socks Version: Socks 5

    Save and close and reopen FF and your browser activity will proxy over ssh to your remote host.

    Dietrich T. Schmitz

  3. It can also be useful with a laptop to set up foxyproxy, a firefox addon that lets you switch between proxy setups as you move between networks.

  4. Excelente post.

    P.D. por que no posteas en castellano?



  5. Brilliant. Exactly what I needed!