In this tutorial, we’ll learn how to ping a host until it’s alive. Specifically, we’ll learn how to write a script that blocks until the host responds to a network request.
2. Pinging a Host Until It’s Alive
When working in Linux, sometimes we find ourselves needing to block a script until a host is “alive”. In this context, we say that a host is “alive” when it starts responding to the network connection requests.
There’re some scenarios where this script can be handy. For example, imagine we have an automated test that should be run when a server is deployed. But, if we run the script while the server is starting up, the tests will be reported as failures. The alternative is to have the script wait for a static time, but that makes the test flaky since the time it takes for the server to start up and be ready is not constant. To ensure we run the tests when the server is ready, we can put in a script to block the tests until the target host is ready.
Let’s look at how we can do that with a bit of shell scripting and the command line tool ping or nc.
3. Probing a Host Using the ping Command
The ping command is a command in Linux that sends an ICMP packet to a host. The basic idea is that if a host is responding to the ICMP packet, we can be sure that the host is up and running.
We can get the ping command by installing the iputils-ping package. For example, in Ubuntu Linux, we can install the ping command using apt-get install:
$ apt-get install -y iputils-ping
3.2. While Loop With Exit Condition
The idea is to run a ping command continuously against the target host until we get a successful exit code of 0. Specifically, we can start a while loop that’ll continue if the ping command returns non zero exit code:
$ while ! ping -c1 targethost.com &>/dev/null; do echo "host not responding"; done
The exclamation symbol negates the exit code of the ping command. This makes the while loop terminate when the ping command returns an exit code of 0. Additionally, we use the -c1 option to send only a single ICMP packet. Without this, the ping command will start sending the ICMP packets in its own loop until it receives a SIGINT.
Finally, we print a diagnostic message in the do section of the while loop.
If we don’t want to print anything to the standard output, we can use the null command instead:
$ while ! ping -c1 targethost.com &>/dev/null; do :; done
Note that we’ll have to have at least the null command in the do section. Leaving it empty or omitting the do block is a syntax error.
3.3. Reacting to SIGINT
The script above has a problem: it doesn’t terminate when we send the SIGINT signal using CTRL + C. This is because when we send the SIGINT, both the Bash and the ping commands receive it. For Bash, it will process the SIGINT signal asynchronously. That is, it will only die of a SIGINT if whatever process it’s running also die of a SIGINT.
However, upon receiving the SIGINT, the ping command will print the statistics and exit with a status code of 0. When Bash sees that the underlying process, that is, the ping command, exits normally, it ignores the SIGINT and continues with the loop. This causes the loop to not terminate on a SIGINT signal.
The problem can be somewhat alleviated by running a sleep 1 command in the do section:
$ while ! ping -c1 targethost.com &>/dev/null; do sleep 1; done;
The trick here is that we are putting a 1-second window in each loop where we can terminate the loop using SIGINT. When we send a SIGINT signal to the loop while the sleep command is running, the sleep command will exit with a status code that indicates that it is killed by SIGINT. When the Bash sees that, it reacts to the SIGINT by terminating the loop.
3.4. Trapping SIGINT
We can further improve the solution by first making the pinging script a background job. Then, we trap the SIGINT signal and issue a kill command to the background job using its process ID:
$ CANCELLED=false $ while ! ping -c1 targethost.com &>/dev/null; do :; done & $ trap "kill $!; CANCELLED=true" SIGINT $ wait $! $ trap - SIGINT $ echo "Done pinging, CANCELLED=$ping_cancelled"
The first line sets a variable CANCELLED so that we can keep track of whether or not the loop is terminated due to SIGINT. Then, we run the while loop as usual, but with the ampersand operator at the end to make it a background process. Then, we put in a trap command that reacts to SIGINT by running the kill command on the background process ID. We get the background process ID using the $! operator, which returns the process ID of the most recent background job.
With the trap setup, we then run the wait command to wait on the background process to complete. When the wait command returns, either due to SIGINT or due to the background process exits, we’ll first need to remove the trap SIGINT. Then, we can check on the variable CANCELLED to see if the loop exits due to SIGINT or condition.
One downside of the ping command is that it works with ICMP packets, which are very often dropped by firewall rules. Additionally, the ping command is unable to tell if a specific port is accepting the connection.
If we need to target a specific port’s responsiveness, we can use the nc command.
4. The nc Command
The nc command is a Linux command that sends and receives network packets to and from the host using TCP or UDP packets. What this means is that we can use the nc command to check if a specific port is open and respond to the connection requests.
4.1. Checking if a Port Is Reachable
To check if a host and port are reachable, we can use the nc command followed by the host and port:
$ nc -z targethost.com 80
The -z option tells the nc command to attempt to open the port without sending any data, which is what we need to test the reachability of the host’s port. Similar to the ping command, the nc command returns an exit code of 0 if the port is successfully opened and 1 if the nc command timed out, waiting for the port to respond.
4.2. Loop Until the Port Is Alive
To run the nc command against a port until it’s responding, we can put the nc command in a while loop with the condition being that the nc command returns a 0 exit code:
$ while ! nc -z targethost.com 22; do echo "port not responding"; done
The command above runs the nc command with the -z option against port 22 of the targethost.com. Similarly, we condition our while loop on the negation of the exit code of the nc command. In other words, we’ll continue the loop when the command fails.
Contrary to the ping command, the nc command doesn’t suffer the same SIGINT problem and, therefore, will react to the SIGINT signal by terminating the loop immediately.
In this tutorial, we’ve explained how in some scenarios, having a script to block until the host is ready and accepting network connection is valuable. Then, we’ve shown how we can achieve that using the ping command. Furthermore, we show how the ping command suffers from the SIGINT termination and its workaround. Finally, we demonstrated how we could use the nc command to block until a specific port of the target host is alive.