Wednesday, April 8, 2009

Episode #20: Ping Beep of Death

Ed says:

Paul threw down a challenge to Hal and me in e-mail just a few minutes ago:

"I want a string of commands that will ping a host and for each time a packet is missing (i.e. timeout) send a beep to the console."

The solution to this one in Windows includes some useful constructs, so let's have at it with this fu:

C:\> for /L %i in (1,0,2) do @(ping -n 1 HostIPaddr || echo ^G)
& ping -n 2
This command starts out with a FOR /L loop, set with an iterator variable of %i that I won't use, counting from 1 to 2 in steps of 0. In other words, it'll run forever, or until someone kills it. At each iteration through the loop, I turn off command echo (@), and ping the target host one time (ping -n 1). Pretty pedestrian so far.

Now we get to some interesting stuff. If the ping command fails to get a response, I'll have it run the echo command to make a beep. As we've seen in earlier episodes, we can use cmd1 && cmd2 to make the shell run cmd2 only if cmd1 succeeded. Likewise, we can use cmd1 || cmd2 to make the system run cmd2 only if cmd1 fails. This is more efficient than checking the %errorlevel% environment variable with an IF statement, but I digress. Of course, as always cmd1 & cmd2 means run cmd2 regardless of the success or failure of cmd1.

If the ping fails, we have "echo ^G". Note that it looks like I typed Shift-6 G, but I didn't. That little ^G is created by holding down the CTRL key and hitting G. It's just displayed like ^G. It's how we can make the echo command ring the system beep. After this, I simply introduce a 1-second delay by pinging localhost twice (first ping instantly, second ping one second later). So, there you have it.

BTW, if you want prettier output from this, you can dump various Standard Output to nul as follows:

C:\> for /L %i in (1,0,2) do @(ping -n 1 HostIPaddr > nul || echo ^G)
& ping -n 2 > nul

Hal Chimes In:

We can easily do the Unix equivalent of what Ed's doing in the Windows command shell:

$ while :; do ping -c 1 -w 1 HostIPaddr >/dev/null || echo -e \\a; sleep 2; done

Note the "echo -e \\a" syntax for emitting an "alert" (^G or BEL).

However, in its default mode the standard Unix ping command will run continuously. So it seems like you should be able to get some joy just by piping the output of ping into another command, without having to resort to a loop. Mr. Bucket had the clever idea of filtering the output of ping through awk and then sending the resulting misses to the "say" command on his OS X machine (because talking is much cooler than beeping):

$ ping x.x.x.x 2>&1 | awk -F: '/sendto:/ {print $3}' | say

The above command seems like it should work, but it wasn't bringing the noise. A confused Mr. Bucket emailed me for clarification.

What's happening here is that Mr. Bucket's clever idea is getting kneecapped by the standard buffering behavior of shell pipes. If you run that first ping command by itself and have the output go to your terminal, then you see the results of each ICMP packet, one line at a time. However, the minute you pipe the output of the ping command into another command, the shell switches to page buffering the output between the two commands. In other words, the awk command gets the output of the ping command in 4096 byte chunks, and the output of awk has to fill up 4096 bytes before the "say" command gets anything. If Mr. Bucket had let the command run long enough, eventually he would have gotten a burst of talking from the "say" command, but only long after the actual failed ping events.

Unfortunately, there is no magic shell variable you can set to fix this problem globally. However, applications in the Unix environment can force their output to be unbuffered if the application developer chooses to add this functionality to their program. Sometimes the software does this automatically-- the tee program in most Unix environments unbuffers its output by default (using setvbuf(3)). Sometimes you need to add an extra command-line option, like with the awk command on my Ubuntu system (which is really mawk):

$ ping x.x.x.x 2>&1 | awk -W interactive -F: '/sendto:/ {print $3}' | ...

That'll get you line-buffered output that you can pipe into another program. The GNU grep utility has the "--line-buffered" option, which is similar.

Paul Chimes In:

I have to confess, I really thought this would be something pretty easy. The page buffering thing threw me through a loop too (pun intended). I also discovered a much better way to do this in OS X:

$ ping -A

This command will beep (ASCII 0x07) "when no packet is received before the next packet is transmitted." so says the man pages. This is exactly what I was looking for to monitor hosts while I scan them with various security tools (like Nessus) and alert me if they crash or become unresponsive.