Scanning LAN using ping and Gnu Parallel - parallel-processing

I am trying to scan my LAN using ping, invoking using Gnu Parallel. The code ideally will simply report those IP addresses which are up and strip out any verbose reporting. The original code (without parallel) which works well is:
for ip in $(seq 1 254) ; do ping -t5 -c 1 192.168.0.$ip > /dev/null ; [ $? -eq 0 ] && echo "192.168.0.$ip UP" & done
However it is annoying with all the job completed messages (set +m is not helpful). Running sequentially takes too long to poll each IP address.
The Parallel code so far which works (take out the --dry-run to execute) is:
seq 1 254 | parallel --dry-run ping -t5 -c 1 192.168.0.{}
but trying to do
seq 1 254 | parallel --dry-run ping -t5 -c 1 192.168.0.{} ';' [ $? -eq 0 ] && echo "192.168.0.{} UP"
or variations of that, fail to achieve the objective. Can anyone help with the test part here?

Cracked it - hope this is helpful to others.
seq 1 254 | parallel ' ping -t5 -c 1 192.168.0.{} >/dev/null && echo 192.168.0.{} UP ' 2>/dev/null

From the Gnu documentation, a parallel timeout rather than ping timeout seems to be more efficient
seq 1 254 | parallel --timeout 2 -j64 -keep-order ' ping -c 1 192.168.0.{} >/dev/null && echo 192.168.0.{} UP ' 2>/dev/null

Related

Cannot exit bash script at the end of the for cycle

I wrote a simple IP scanner based on ping (see below), but it has a problem.
#!/bin/bash
counter = 0
for ip in 192.168.44.{1..254}; do
ping -c 1 -W 1 $ip | grep "64 bytes" &
let counter++
if [[ "$counter" -eq 254 ]];
then
exit 0;
fi
done
First of all, the for cycle appears to be launching multiple threads and the only output to the terminal are the ping answers. However, when the script finishes pinging all the machines in the network, it never exits, as you can see in the next screenshot:
and I have to press Enter to finally end it.
I've also tried to place an exit 0 after the done statement, but it still does not work. How can I make the script exit when the for cycle ends?
Note: I've found this implementation to be the fastest to find the existing machines in a LAN, but if anyone has a suggestion of a more appropriate code, I would appreciate it.
Assuming that you do not want to use a dedicated network scanning tools, You can use bash or xargs to iterate over all addresses in parallel:
Bash:
#! /bin/bash -x
for ip in 192.168.44.{1..254}; do
ping -c 1 -W 1 $ip | grep "64 bytes" &
done
# Wait for all children to finish
wait
Or with xargs, with the advantage that you can control the number of parallel addresses being pinged (20 in this example) - to avoid overloading your server with large number of concurrent process.
echo 192.168.44.{1..254} | xargs --max-args=1 -P20 ping -c 1 -W 1
You can try this:
#!/bin/bash
for ip in 192.168.44.{1..254}; do
ping -c 1 -W 1 $ip | grep "64 bytes" &
done
wait
If CTRLc is acceptable:
parallel -j0 --lb ping ::: 192.168.1.{1..250}

Omit error from shell script output

I'd like to omit the error from this IF statement if ICMP echo fails.
Example code:
if ping -q -c 1 -W 1 1.2.3.4 >/dev/null; then
echo -e "PING OK"
else
echo -e "PING NOK"
fi
It works perfectly if the ping is successful or you run the command outside of a script, but gives the below output if there is no response.
PING 1.2.3.4 (1.2.3.4): 56 data bytes
--- 1.2.3.4 ping statistics ---
1 packets transmitted, 0 packets received, 100.0% packet loss
PING NOK
I've seen answers for this out there quoting 2>/dev/null, but this then displays the entire ping query in the output, whether successful or not! Example with 2>/dev/null as below.
PING 1.2.3.4 (1.2.3.4): 56 data bytes
--- 1.2.3.4 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 26.134/26.134/26.134/0.000 ms
PING OK
This is a bit of a n00b question, but I'm a networking chap, not a developer :)
Thanks in advance!!
The «classic» solution:
if ping -q -c 1 -W 1 1.2.3.4 >/dev/null 2>&1; then
echo -e "PING OK"
else
echo -e "PING NOK"
fi
A somewhat more modern (and not POSIX-compliant!) approach, available since BASH 4:
if ping -q -c 1 -W 1 1.2.3.4 &>/dev/null; then
echo -e "PING OK"
else
echo -e "PING NOK"
fi
Both of these mean «redirect both STDOUT and STDERR to /dev/null», but the first one does it sequentially, first redirecting STDOUT and then redirecting STDERR to STDOUT.
You can use the exit status [ Check this ] too..
ping -q -c 1 -W 1 1.2.3.4 >/dev/null 2>&1
[ $? -eq 0 ] && echo "Ping OK" || echo "Ping NOK"

I have a test env with bash 4 and loops work but the same loops do not run within the production env using bash 3

for i in `cat ip_list` ; do
ping -c 1 $i 2&>1 > /dev/nul && echo $i good || echo $i bad ;
done
This loop works in bash 4 but not in bash 3... what should I change in the loop for the older RedHat 5 machines running version 3?
Three problems,
/dev/nul should be /dev/null
2&>1 should be 2>&1
some systems don't support -c option in command ping. Please confirm your command by manually run the command, if you get any error message.
ping -c 1 IP_ADDRESS
If you successfully run the ping -c command, then the code can be replaced by
for i in `cat ip_list` ; do
ping -c 1 $i 2>&1 > /dev/null && echo $i good || echo $i bad ;
done

Making bash script to check connectivity and change connection if necessary. Help me improve it?

My connection is flaky, however I have a backup one. I made some bash script to check for connectivity and change connection if the present one is dead. Please help me improve them.
The scripts almost works, except for not waiting long enough to receive an IP (it cycles to next step in the until loop too quick). Here goes:
#!/bin/bash
# Invoke this script with paths to your connection specific scripts, for example
# ./gotnet.sh ./connection.sh ./connection2.sh
until [ -z "$1" ] # Try different connections until we are online...
do
if eval "ping -c 1 google.com"
then
echo "we are online!" && break
else
$1 # Runs (next) connection-script.
echo
fi
shift
done
echo # Extra line feed.
exit 0
And here is an example of the slave scripts:
#!/bin/bash
ifconfig wlan0 down
ifconfig wlan0 up
iwconfig wlan0 key 1234567890
iwconfig wlan0 essid example
sleep 1
dhclient -1 -nw wlan0
sleep 3
exit 0
Here's one way to do it:
#!/bin/bash
while true; do
if ! [ "`ping -c 1 google.com; echo $?`" ]; then #if ping exits nonzero...
./connection_script1.sh #run the first script
sleep 10 #give it a few seconds to complete
fi
if ! [ "`ping -c 1 google.com; echo $?`" ]; then #if ping *still* exits nonzero...
./connection_script2.sh #run the second script
sleep 10 #give it a few seconds to complete
fi
sleep 300 #check again in five minutes
done
Adjust the sleep times and ping count to your preference. This script never exits so you would most likely want to run it with the following command:
./connection_daemon.sh 2>&1 > /dev/null & disown
Have you tried omitting the -nw option from the dhclient command?
Also, remove the eval and quotes from your if they aren't necessary. Do it like this:
if ping -c 1 google.com > /dev/null 2>&1
Trying using ConnectTimeout ${timeout} somewhere.

How to test an Internet connection with bash?

How can an internet connection be tested without pinging some website?
I mean, what if there is a connection but the site is down? Is there a check for a connection with the world?
Without ping
#!/bin/bash
wget -q --spider http://google.com
if [ $? -eq 0 ]; then
echo "Online"
else
echo "Offline"
fi
-q : Silence mode
--spider : don't get, just check page availability
$? : shell return code
0 : shell "All OK" code
Without wget
#!/bin/bash
echo -e "GET http://google.com HTTP/1.0\n\n" | nc google.com 80 > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "Online"
else
echo "Offline"
fi
Ping your default gateway:
#!/bin/bash
ping -q -w 1 -c 1 `ip r | grep default | cut -d ' ' -f 3` > /dev/null && echo ok || echo error
Super Thanks to user somedrew for their post here: https://bbs.archlinux.org/viewtopic.php?id=55485 on 2008-09-20 02:09:48
Looking in /sys/class/net should be one way
Here's my script to test for a network connection other than the loop back.
I use the below in another script that I have for periodically testing if my website is accessible. If it's NOT accessible a popup window alerts me to a problem.
The script below prevents me from receiving popup messages every five minutes whenever my laptop is not connected to the network.
#!/usr/bin/bash
# Test for network conection
for interface in $(ls /sys/class/net/ | grep -v lo);
do
if [[ $(cat /sys/class/net/$interface/carrier) = 1 ]]; then OnLine=1; fi
done
if ! [ $OnLine ]; then echo "Not Online" > /dev/stderr; exit; fi
Note for those new to bash: The final 'if' statement tests if NOT [!] online and exits if this is the case. See man bash and search for "Expressions may be combined" for more details.
P.S. I feel ping is not the best thing to use here because it aims to test a connection to a particular host NOT test if there is a connection to a network of any sort.
P.P.S. The Above works on Ubuntu 12.04 The /sys may not exist on some other distros. See below:
Modern Linux distributions include a /sys directory as a virtual filesystem (sysfs, comparable to /proc, which is a procfs), which stores and allows modification of the devices connected to the system, whereas many traditional UNIX and Unix-like operating systems use /sys as a symbolic link to the kernel source tree.[citation needed]
From Wikipedia https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard
This works on both MacOSX and Linux:
#!/bin/bash
ping -q -c1 google.com &>/dev/null && echo online || echo offline
In Bash, using it's network wrapper through /dev/{udp,tcp}/host/port:
if : >/dev/tcp/8.8.8.8/53; then
echo 'Internet available.'
else
echo 'Offline.'
fi
(: is the Bash no-op, because you just want to test the connection, but not processing.)
The top answer misses the fact that you can have a perfectly stable connection to your default gateway but that does not automatically mean you can actually reach something on the internet. The OP asks how he/she can test a connection with the world. So I suggest to alter the top answer by changing the gateway IP to a known IP (x.y.z.w) that is outside your LAN.
So the answer would become:
ping -q -w 1 -c 1 x.y.z.w > /dev/null && echo ok || echo error
Also removing the unfavored backticks for command substitution[1].
If you just want to make sure you are connected to the world before executing some code you can also use:
if ping -q -w 1 -c 1 x.y.z.w > /dev/null; then
# more code
fi
I've written scripts before that simply use telnet to connect to port 80, then transmit the text:
HTTP/1.0 GET /index.html
followed by two CR/LF sequences.
Provided you get back some form of HTTP response, you can generally assume the site is functioning.
make sure your network allow TCP traffic in and out, then you could get back your public facing IP with the following command
curl ifconfig.co
Execute the following command to check whether a web site is up, and what status message the web server is showing:
curl -Is http://www.google.com | head -1 HTTP/1.1 200 OK
Status code ‘200 OK’ means that the request has succeeded and a website is reachable.
The top voted answer does not work for MacOS so for those on a mac, I've successfully tested this:
GATEWAY=`route -n get default | grep gateway`
if [ -z "$GATEWAY" ]
then
echo error
else
ping -q -t 1 -c 1 `echo $GATEWAY | cut -d ':' -f 2` > /dev/null && echo ok || echo error
fi
tested on MacOS High Sierra 10.12.6
If your local nameserver is down,
ping 4.2.2.1
is an easy-to-remember always-up IP (it's actually a nameserver, even).
This bash script continuously check for Internet and make a beep sound when the Internet is available.
#!/bin/bash
play -n synth 0.3 sine 800 vol 0.75
while :
do
pingtime=$(ping -w 1 8.8.8.8 | grep ttl)
if [ "$pingtime" = "" ]
then
pingtimetwo=$(ping -w 1 www.google.com | grep ttl)
if [ "$pingtimetwo" = "" ]
then
clear ; echo 'Offline'
else
clear ; echo 'Online' ; play -n synth 0.3 sine 800 vol 0.75
fi
else
clear ; echo 'Online' ; play -n synth 0.3 sine 800 vol 0.75
fi
sleep 1
done
Similarly to #Jesse's answer, this option might be much faster than any solution using ping and perhaps slightly more efficient than #Jesse's answer.
find /sys/class/net/ -maxdepth 1 -mindepth 1 ! -name "*lo*" -exec sh -c 'cat "$0"/carrier 2>&1' {} \; | grep -q '1'
Explenation:
This command uses find with -exec to run command on all files not named *lo* in /sys/class/net/. These should be links to directories containing information about the available network interfaces on your machine.
The command being ran is an sh command that checks the contents of the file carrier in those directories. The value of $interface/carrier has 3 meanings - Quoting:
It seems there are three states:
./carrier not readable (for instance when the interface is disabled in Network Manager).
./carrier contain "1" (when the interface is activated and it is connected to a WiFi network)
./carrier contain "0" (when the interface is activated and it is not connected to a WiFi network)
The first option is not taken care of in #Jesse's answer. The sh command striped out is:
# Note: $0 == $interface
cat "$0"/carrier 2>&1
cat is being used to check the contents of carrier and redirect all output to standard output even when it fails because the file is not readable.
If grep -q finds "1" among those files it means there is at least 1 interface connected. The exit code of grep -q will be the final exit code.
Usage
For example, using this command's exit status, you can use it start a gnubiff in your ~/.xprofile only if you have an internet connection.
online() {
find /sys/class/net/ -maxdepth 1 -mindepth 1 ! -name "*lo*" -exec sh -c 'cat "$0"/carrier 2>&1 > /dev/null | grep -q "1" && exit 0' {} \;
}
online && gnubiff --systemtray --noconfigure &
Reference
Help testing special file in /sys/class/net/
find -exec a shell function?
shortest way: fping 4.2.2.1 => "4.2.2.1 is alive"
i prefer this as it's faster and less verbose output than ping, downside is you will have to install it.
you can use any public dns rather than a specific website.
fping -q google.com && echo "do something because you're connected!"
-q returns an exit code, so i'm just showing an example of running something you're online.
to install on mac: brew install fping; on ubuntu: sudo apt-get install fping
Ping was designed to do exactly what you're looking to do. However, if the site blocks ICMP echo, then you can always do the telnet to port 80 of some site, wget, or curl.
Checking Google's index page is another way to do it:
#!/bin/bash
WGET="/usr/bin/wget"
$WGET -q --tries=20 --timeout=10 http://www.google.com -O /tmp/google.idx &> /dev/null
if [ ! -s /tmp/google.idx ]
then
echo "Not Connected..!"
else
echo "Connected..!"
fi
For the fastest result, ping a DNS server:
ping -c1 "8.8.8.8" &>"/dev/null"
if [[ "${?}" -ne 0 ]]; then
echo "offline"
elif [[ "${#args[#]}" -eq 0 ]]; then
echo "online"
fi
Available as a standalone command: linkStatus
Pong doesn't mean web service on the server is running; it merely means that server is replying to ICMP echo.
I would recommend using curl and check its return value.
If your goal is to actually check for Internet access, many of the existing answers to this question are flawed. A few things you should be aware of:
It's possible for your computer to be connected to a network without that network having internet access
It's possible for a server to be down without the entire internet being inaccessible
It's possible for a captive portal to return an HTTP response for an arbitrary URL even if you don't have internet access
With that in mind, I believe the best strategy is to contact several sites over an HTTPS connection and return true if any of those sites responds.
For example:
connected_to_internet() {
test_urls="\
https://www.google.com/ \
https://www.microsoft.com/ \
https://www.cloudflare.com/ \
"
processes="0"
pids=""
for test_url in $test_urls; do
curl --silent --head "$test_url" > /dev/null &
pids="$pids $!"
processes=$(($processes + 1))
done
while [ $processes -gt 0 ]; do
for pid in $pids; do
if ! ps | grep "^[[:blank:]]*$pid[[:blank:]]" > /dev/null; then
# Process no longer running
processes=$(($processes - 1))
pids=$(echo "$pids" | sed --regexp-extended "s/(^| )$pid($| )/ /g")
if wait $pid; then
# Success! We have a connection to at least one public site, so the
# internet is up. Ignore other exit statuses.
kill -TERM $pids > /dev/null 2>&1 || true
wait $pids
return 0
fi
fi
done
# wait -n $pids # Better than sleep, but not supported on all systems
sleep 0.1
done
return 1
}
Usage:
if connected_to_internet; then
echo "Connected to internet"
else
echo "No internet connection"
fi
Some notes about this approach:
It is robust against all the false positives and negatives I outlined above
The requests all happen in parallel to maximize speed
It will return false if you technically have internet access but DNS is non-functional or your network settings are otherwise messed up, which I think is a reasonable thing to do in most cases
If you want to handle captive portals, you can do this oneliner:
if [[ $(curl -s -D - http://www.gstatic.com/generate_204 2>/dev/null | head -1 | cut -d' ' -f 2) == "204" ]]; then
echo 'online'
else
echo 'offline'
fi
Or if you want something more readable that can differentiate captive portals from lack of signal:
function is_online() {
# Test signal
local response
response=$(curl --silent --dump-header - http://www.gstatic.com/generate_204 2> /dev/null)
if (($? != 0)); then return 2; fi
# Test captive portal
local status=$(echo $response | head -1 | cut -d' ' -f 2)
((status == "204"))
}
is_online && echo online || echo offline

Resources