Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I am looking for an in-depth explanation of the following ProxyCommand, down to the nuts and bolts of its operation, please. Can you kindly completely dissect it for me and improve on it if you can? For readability, if nothing else.

ProxyCommand ssh gatewayserver 'exec 3<>/dev/tcp/targetserver/22; cat <&3 & cat >&3;kill $!'
share|improve this question
Nice! We don't have nc/netcat installed, so this was useful. – haridsv Jan 9 '13 at 7:50
up vote 4 down vote accepted

(I don't have a /dev/tcp device on my system; however bash seems to have some built-in handling for it, allocating a tcp socket connected to the following /host/port part.)

So, your ssh proxy command securely runs a shell on gatewayserver which does:

exec 3<>/dev/tcp/targetserver/22

i.e. attach a socket on filedescriptor 3 (connected to the targetserver/port). Then:

cat <&3 & cat >&3; kill $!

which is a way to have a bidirectional redirection (using two separate processes) between the couple of filedescriptors 0(input) and 1(output), and filedescriptor 3(input and output). The kill $! is there to kill the background process cat <&3 after the other process cat >&3 has returned.

All this is just some equivalent of a more standard:

ProxyCommand ssh gatewayserver "tcpconnect targetserver port"

using /dev/tcp (or bash) features instead of the tcpconnect command.

Some more details:

The proxy command used by ssh is there to define how to connect to the remote host targetserver (encryption is not really needed there because ssh protocol will be used over this channel). In our case, we want to establish the connection to this target host trough gatewayserver (probably because a firewall prevents to connect to targetserver directly.

So a process

ssh gatewayserver 'exec 3<>/dev/tcp/targetserver/22; cat <&3 & cat >&3;kill $!'

is started and:

  • filedescriptor(fd) 1 (a.k.a. standard output) will be used by an ssh client to send data to the target host.
  • fd 0 (a.k.a. standard input) will be used by an ssh client to read data from the remote host.

The ssh gatewayserver is used to first connect to the gateway which will be the first hop. A new shell is started on this host, and this ssh instance will relay fd 0/fd 1 of the process on the origin host to fd 0/fd 1 of the shell running on gateway host. The command executed by this shell is:

exec 3<>/dev/tcp/targetserver/22; cat <&3 & cat >&3;kill $!

The exec without a command won't execute anything, it will just apply the following redirections the shell itself. Usual redirections are:

  • n>file to redirect fd n to file (open for writting only, n is 1 if ommited).
  • n<file to redirect fd n to file (open for reading only, n is 0 if ommited).
  • n<>file to redirect fd n to file (open for reading and writting).
  • when specified as n>&m or n<&m or n<>&m, the fd n is redirected to the file previously pointed to by fd m.

Here the following is used:

exec 3<>/dev/tcp/targetserver/22

This will redirect a newly created fd 3 to some very special file /dev/tcp/targetserver/22 (which is not really a file, but something bash understands natively). Here, bash creates a socket (special file which uses the tcp protocol) to talk to targetserver on port 22 (Where we expect to find a sshd server), and this file is open (read&write) on fd 3.

Now we need to "pump" data on fd 0 (data from client) and send it to fd 3 (connected to the target server). We also need to assure backward communication by "pumping" data on fd 3 and sending it back to fd 1 (result for client). Those two "pumps" are set up using two cat processes:

  • cat <&3 (which reads from the shell's fd 3 and write to the shell's fd 1.)
  • cat >&3 (which reads from the shell's fd 0 and write to the shell's fd 3.)

Both cats must be run in parallel, so one needs to be backgrounded. Here, we want the one that reads on fd 0 (which will probably be a tty) be the one left in the foreground. This gives:

cat <&3 &  #run in the background
cat >&3; kill $!

The kill $! is there to kill the background process ($! expands to the pid of the last backgrounded process). This way when the client hangs up, the foreground process terminates, the kill is performed, and the last process is terminated too.

That's it! We've made the bridge:

origin host —(ssh)→ gateway —(pumps+socket)→ targetserver(port 22)

An ssh user@targetserver on the origin host will be able to connect to the target host through this bridge!

share|improve this answer
I appreciate you taking the time to go through this. I really do. I still don't understand exactly how this works, though. I meant for the answer to have much more detail in it, both in terms of setting up the socket and in setting up that "double cat". – Robottinosino Aug 28 '11 at 17:13
@Robot: see the details and feel free to ask for more. – Stéphane Gimenez Aug 28 '11 at 20:05
Whoa. Amazing explanation. Thanks a million! – Robottinosino Aug 28 '11 at 20:54

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.