Wireless routers running a port of Linux called OpenWrt  are deployed at a number of remote locations. These devices serve as wifi hotspots, which occasionally need to be manually administered from the Internet. Some of them run behind a network address translating server (NAT) and cannot be contacted from outside. Figure 1 shows the setup.
Firewalls usually block new incoming connections, but allow incoming data on existing connections. The router needs to be the one to initiate a connection to the outside. Once such a connection has been made the administrator needs to be able to issue Unix commands to the router through it.
Writing specialised C programs to run on the router and server, and developing a communication protocol will take more than a few hours, and perhaps a week to implement. Gluing together the right set of Unix commands with shell script makes for less code to write and maintain .
SSH  has a secondary function, which is to tunnel socket connections through an existing secure connection. Security is not my immediate concern, but tunnelling seems to be just what I need. However, periodically initiating ssh connections from the router requires keys to be exchanged and encrypted and decrypted each time. I don’t want to use more of the router’s limited computing power than I need to, so I will also use netcat (nc)  to determine whether administration is required in the first place. Netcat is an under-appreciated Unix utility that lets you perform all sorts of administrative tasks if you use your imagination.
The router uses netcat to poll the server every fifteen minutes to ask if it needs to be administered. It identifies itself to the server with its nas_id and receives in response the server port that it must tunnel back to its own ssh server, or an empty string if it doesn’t need administration. Each router must forward a different server port in order to avoid colliding with other routers.
tunnelled_port=‘echo $nas_id |
nc -i 1 <server> 11000‘
if test -n "$tunnelled_port"; then
ssh -R $tunnelled_port:localhost:22 <server> ...
Netcat in listening mode works well as a server socket. The server script will look in a local file named after the connecting router’s nas_id for the response to send back to the router. When I want to administer a given router, I will create such a control file on the server containing the port number that the router should tunnel.
One twist is that if the Unix pipeline is reading from netcat, how will a downstream component of the pipeline send a response back up the pipeline to netcat? The trick is to use a named pipe in this pattern:
cat pipe |
nc -l -p 11000 |
I also want to clean incoming data so that intruders don’t do any harm. With a little development, the server becomes:
while true; do
cat pipe |
nc -l -p 11000 |
( read nas_id
echo -n ‘date +%Y%m%dT%H%M%S‘ received $nas_id", "
nas_id=‘echo $nas_id | sed s/[^a-zA-Z0-9]//g‘
echo sent ‘cat $nas_id‘
cat $nas_id >pipe )
A few seconds after this script reports an incoming poll from a router, the router will have created a tunnel from the server port it was allotted, say 10002, to its own ssh service port. I can then ssh in:
server> ssh -p 10002 localhost
The router script outlined earlier keeps the tunnel open until I remove the router’s control file from the server. The full ssh command it uses is:
router> ssh -R $tunnelled_port:localhost:22 <server> \
"while test -f $nas_id; do sleep 15; done"
Figure 2 shows the router to server interaction.
There is a subtle feature of pipes, which only becomes apparent when using named pipes.
nc -l -p 11000 <pipe
will not work in the server script. I need to use,
cat pipe | nc -l -p 11000
According to the fifo(4) manual page a process opening a pipe is blocked until another process has opened the other end of the pipe. This means that the first form makes the shell block when it opens pipe for reading, before starting nc. Since nc hasn’t started, the read in the script never completes and control never reaches the last cat statement, where the pipe would have been opened for writing. Thus, nc never starts and the pipeline enters a state of deadlock.
The second form, which is what I use in the server script, fixes this. Remember that all three processes in the while loop’s pipeline run concurrently. Though the first cat statement is blocked, nc is not. nc runs, feeding socket data to the read statement. The read statement completes and control eventually reaches the last cat statement, where the shell opens the other end of pipe for writing. This unblocks the first cat statement. Andrew McNabb provides a good description of this aspect of pipes .
Netcat is one of my favourite Unix programs because it bridges two Unix abstractions — files and sockets. The file abstraction covers disk files, pipes and console input/output. The socket abstraction covers network communication. Although sockets have a similar programming interface to files, they are not files. Netcat bridges the two abstractions by feeding its console input to a socket, and its incoming socket data to console output.
It is possible to administer a firewalled device if you own it. When the device runs Unix, it is easy for it to poll the server cheaply, which becomes important in embedded systems. I was able to develop a simple client and server using bash , thanks to netcat, ssh and named pipes.