40

I found scripts that say they check for internet connectivity. Some check the IP address if the interface is up BUT it does not check for internet connectivity. I found some that uses ping like this: if [ 'ping google.com -c 4 | grep time' != "" ]; then but sometimes this may be unreliable as ping itself may hang for some reason (e.g. waiting for some stuck IO).

Any suggestions on the proper/reliable way to check for internet connectivity using scripts? Do I have to use some packages?

It needs to be able to check periodically with cron for example, then, do something when the connection goes down like invoke ifup --force [interface]

Aloha
  • 2,111
  • 1
  • 15
  • 22

10 Answers10

52

I highly recommend against using ping to determine connectivity. There are too many network admins that disable ICMP (the protocol it uses) due to worries about ping flood attacks originating from their networks.

Instead, I use a quick test of a reliable server on a port you can expect to be open:

if nc -zw1 google.com 443; then
  echo "we have connectivity"
fi

This uses netcat (nc) in its port scan mode, a quick poke (-z is zero-I/O mode [used for scanning]) with a quick timeout (-w 1 waits at most one second, though Apple OS X users may need to use -G 1 instead). It checks Google on port 443 (HTTPS).

I've used HTTPS rather than HTTP as an effort to protect against captive portals and transparent proxies which can answer on port 80 (HTTP) for any host. This is less likely when using port 443 since there would be a certificate mismatch, but it does still happen.

If you want to proof yourself against that, you'll need to validate the security on the connection:

test=google.com
if nc -zw1 $test 443 && echo |openssl s_client -connect $test:443 2>&1 |awk '
  $1 == "SSL" && $2 == "handshake" { handshake = 1 }
  handshake && $1 == "Verification:" { ok = $2; exit }
  END { exit ok != "OK" }'
then
  echo "we have connectivity"
fi

This checks for a connection (rather than waiting for openssl to time out) and then makes the SSL handshake, keying on the verification phase. It silently exits ("true") if the verification was "OK" or else exits with an error ("false"), then we report the finding.

The awk code analyzes the output of openssl line by line:

  1. If the first word of the line is "SSL" and the second is "Verification", set handshake to 1
  2. If handshake is set and the first word of the line is "Verification",
    then save the second word (the verification status) in ok and stop reading
  3. Exit with a value of 0 (true) if the verification status was OK, or else exit with 1 (false).
    We use != here because shell exit codes are reversed

(An awk oddity: Running exit while reading lines will simply stop reading lines and enter the END condition, from which you can truly exit.)

Adam Katz
  • 3,965
  • 7
    I respect Gillies however this is the right answer. – gwillie Jul 23 '15 at 05:32
  • 3
    add -d e.g. nc -dzw1 also so it doesn't listen for STDIN and hang indefinetily in a script. and maybe use 8.8.8.8 instead of google.com to save a lookup. nc -dzw1 8.8.8.8 443 – dza Feb 17 '17 at 13:45
  • I'm not sure about how reliable Google's DNS resolver is at serving HTTPS. The google.com server should be more reliable for HTTPS (unless you're in China, but then both are likely blocked). I've never needed -d in my scripts, perhaps because I've never had an unused pipeline. That should be safe to add. – Adam Katz Feb 17 '17 at 17:05
  • 1
    I discovered that on a school network this command was really slow .. It actually halted my script even though it had the -w 1 timer I couldn't figure out what was going on but I guess it has something to do with network configuration. Odd however. If someone has an idea please reply. I tried port 80 also. – dza Feb 23 '17 at 21:59
  • 2
    @dezza – -w 1 still costs a second when there is no connectivity, though perhaps your nc has some kind of obscure issue somwehere. If you have a recent version of nmap installed, you can instead do ncat --send-only --recv-only -w 334ms to cut that failure time to a third of nc (I've found that 334ms is a good wait time). – Adam Katz Feb 24 '17 at 19:39
  • @AdamKatz It only happened at that particular network. When I tried from home everything was fine which puzzles me. Thanks for the nmap suggestion :) I will try that out. Neat tips :) – dza Feb 24 '17 at 21:34
  • On FreeBSD with nmap/ncat 7.40 I got Ncat: Operation timed out. even if I increased the timeout slowly up to 4000ms with ncat --send-only --recv-only -w 334ms 8.8.8.8 443 and on nmap/ncat 6.40 Ubuntu it worked. – dza Feb 24 '17 at 23:47
  • 1
    @dezza – I do not know why that is happening to you on both nmap's ncat and netcat (nc) for that system. There might be something odd happening in your network or on that BSD system. Feel free to create a new unix.stackexchange question and get more than just my eyes on that problem. If you do, please link it in comments here and link this thread to your new question. – Adam Katz Feb 27 '17 at 18:27
  • @AdamKatz not the same system. netcat hang was on my laptop at another network (it worked at home) .. The BSD worked with everything but ncat on version 7.40 – dza Feb 27 '17 at 19:57
  • It's just a guess but I think gnu netcat is the only build that can do that timeout properly. – dza Nov 18 '17 at 22:02
  • 3
    @dezza a bit late... but fyi the timeout "not working/the script freezing" appears to be a bug in nc. See this link https://stackoverflow.com/a/26872879/957573 The timeout didn't work for me either in macOS 10.13.4, and i had to install "gnu netcat 0.7.1" with Homebrew, to work around this. – Motsel Apr 28 '18 at 20:58
  • very important point on the reason why to use port 443 – kaptan Nov 05 '19 at 23:37
  • On macOS to get it to timeout in 1 second, I had to use -G 1 instead of -w 1. Apple's nc seems to not use the -w timeout at all for a zero i/o portscan and it just hangs when there's no connection. Like this: nc -zG 1 google.com 443 – Chris Apr 24 '20 at 03:15
  • @Chris – Interesting. That departs from the BSD syntax (Apple OS X typically uses the BSD tools). I'll add that to my answer. – Adam Katz Apr 24 '20 at 04:26
  • bash: nc: command not found Archlinux base installation . – Salem F Aug 16 '21 at 17:45
  • @SalemF – I couldn't tell you why Arch doesn't include netcat (nc) by default. Most distributions do, though it's not required by the LSB or POSIX. Arch has a number of different options, see https://wiki.archlinux.org/title/Network_tools#Netcat – Adam Katz Aug 16 '21 at 19:16
  • This is a great answer, and I really like the fact the OP has maintained it so well. Unfortunately, Apple has not been great at all... man nc still shows a "June 25, 2001" date. That doesn't necessarily mean that it's been that long since they did any maintenance of nc itself, but they have a sad reputation for letting things rot. And Apple's nc emits this annoying string that has to be dealt with in scripting: Connection to google.com port 443 [tcp/https] succeeded!. – Seamus Mar 17 '24 at 03:59
  • 1
    @Seamus – We only care about the exit code, so you can pipe its output to oblivion: nc -zG 1 google.com 443 >/dev/null 2>&1 – Adam Katz Mar 18 '24 at 14:51
  • True, but it does have to be dealt with. Whereas socat lends itself to more compact implementations. – Seamus Mar 18 '24 at 20:21
  • Using Debian Popularity Contest statistics, Socat is installed on 29% of Debian systems overall. Netcat (nc) is installed on 90%. Testing network connectivity shouldn't require downloading a new package. – Adam Katz Mar 18 '24 at 21:07
  • Not only that, but macOS ships with nc, not socat, and @Seamus uses macOS. – huyz Mar 19 '24 at 20:48
  • @huyz: Didja' ever hear of a thing called "MacPorts"? Why would anyone allow Apple to dictate their choice of tools when you have a competent package manager available? – Seamus Mar 19 '24 at 22:00
  • 1
    "To test your network connectivity, please use the network to download a new package manager, download its lists, and then download and install this utility rather than piping the output of an already-installed program into /dev/null." – Adam Katz Mar 19 '24 at 22:06
35

Testing IPv4 connectivity

If your network lets pings through, try pinging 8.8.8.8 (a server run by Google).

if ping -q -c 1 -W 1 8.8.8.8 >/dev/null; then
  echo "IPv4 is up"
else
  echo "IPv4 is down"
fi

Testing IP connectivity and DNS

If you only want the test to succeed when DNS is also working, use a host name.

if ping -q -c 1 -W 1 google.com >/dev/null; then
  echo "The network is up"
else
  echo "The network is down"
fi

Testing web connectivity

Some firewalls block pings. Some places have a firewall that blocks all traffic except via a web proxy. If you want to test web connectivity, you can make an HTTP request.

case "$(curl -s --max-time 2 -I http://google.com | sed 's/^[^ ]*  *\([0-9]\).*/\1/; 1q')" in
  [23]) echo "HTTP connectivity is up";;
  5) echo "The web proxy won't let us through";;
  *) echo "The network is down or very slow";;
esac
  • You may want to incorporate validation of a physical connection (OSI layer 1) prior to any OSI layer 3 checks using ethtool; $ ethtool <dev> | awk '$0 ~ /link detected/{print $3}' – jas- May 06 '18 at 02:46
  • Can you explain the purpose of adding this please >/dev/null – Amine Harbaoui Aug 24 '18 at 09:09
  • @AmineHarbaoui – >/dev/null redirects standard output to /dev/null, the null device, which disposes of it since it is not desired in this case (all we care about is the exit values of the commands). Instead, more applicable output is taken from the echo lines. – Adam Katz Jan 05 '19 at 16:37
  • Testing IP connectivity (not legacy IPv4!) : testdomain="2001:148f:fffe::1" ## CZ.NIC DNS; and then do test for: nc -zw2 $testdomain 443 >> /dev/null 2>&1 ; ## z w/o traffic, w short wait / attempt. 443 for https ( general availability!) quite unreadable here w/o format but still works ;-) – opinion_no9 Mar 30 '24 at 00:33
11

I made a script that uses multiple ways to check internet connection (ping, nc, and curl, thanks to Adam Katz, Gilles, and Archemar). I hope someone finds this useful. Feel free to edit it to your liking/optimize it.

Checks your gateway, DNS, and internet connection (using curl, nc, and ping). Put this in a file then make it executable (Usually sudo chmod +x filename)

#!/bin/bash

GW=`/sbin/ip route | awk '/default/ { print $3 }'`
checkdns=`cat /etc/resolv.conf | awk '/nameserver/ {print $2}' | awk 'NR == 1 {print; exit}'`
checkdomain=google.com

#some functions

function portscan
{
  tput setaf 6; echo "Starting port scan of $checkdomain port 80"; tput sgr0;
  if nc -zw1 $checkdomain  80; then
    tput setaf 2; echo "Port scan good, $checkdomain port 80 available"; tput sgr0;
  else
    echo "Port scan of $checkdomain port 80 failed."
  fi
}

function pingnet
{
  #Google has the most reliable host name. Feel free to change it.
  tput setaf 6; echo "Pinging $checkdomain to check for internet connection." && echo; tput sgr0;
  ping $checkdomain -c 4

  if [ $? -eq 0 ]
    then
      tput setaf 2; echo && echo "$checkdomain pingable. Internet connection is most probably available."&& echo ; tput sgr0;
      #Insert any command you like here
    else
      echo && echo "Could not establish internet connection. Something may be wrong here." >&2
      #Insert any command you like here
#      exit 1
  fi
}

function pingdns
{
  #Grab first DNS server from /etc/resolv.conf
  tput setaf 6; echo "Pinging first DNS server in resolv.conf ($checkdns) to check name resolution" && echo; tput sgr0;
  ping $checkdns -c 4
    if [ $? -eq 0 ]
    then
      tput setaf 6; echo && echo "$checkdns pingable. Proceeding with domain check."; tput sgr0;
      #Insert any command you like here
    else
      echo && echo "Could not establish internet connection to DNS. Something may be wrong here." >&2
      #Insert any command you like here
#     exit 1
  fi
}

function httpreq
{
  tput setaf 6; echo && echo "Checking for HTTP Connectivity"; tput sgr0;
  case "$(curl -s --max-time 2 -I $checkdomain | sed 's/^[^ ]*  *\([0-9]\).*/\1/; 1q')" in
  [23]) tput setaf 2; echo "HTTP connectivity is up"; tput sgr0;;
  5) echo "The web proxy won't let us through";exit 1;;
  *)echo "Something is wrong with HTTP connections. Go check it."; exit 1;;
  esac
#  exit 0
}


#Ping gateway first to verify connectivity with LAN
tput setaf 6; echo "Pinging gateway ($GW) to check for LAN connectivity" && echo; tput sgr0;
if [ "$GW" = "" ]; then
    tput setaf 1;echo "There is no gateway. Probably disconnected..."; tput sgr0;
#    exit 1
fi

ping $GW -c 4

if [ $? -eq 0 ]
then
  tput setaf 6; echo && echo "LAN Gateway pingable. Proceeding with internet connectivity check."; tput sgr0;
  pingdns
  pingnet
  portscan
  httpreq
  exit 0
else
  echo && echo "Something is wrong with LAN (Gateway unreachable)"
  pingdns
  pingnet
  portscan
  httpreq

  #Insert any command you like here
#  exit 1
fi
Aloha
  • 2,111
  • 1
  • 15
  • 22
  • Nice ! Thank you ! What should we set gateway $GW to ? – Ciprian Tomoiagă Jun 08 '17 at 09:45
  • 1
    @CiprianTomoiaga No need, /sbin/ip route | awk '/default/ { print $3 }' gets the gateway address from the (hopefully) primary interface. If you want, you may set the gateway IP address yourself. – Aloha Dec 04 '17 at 10:28
  • Thanks for this! What I miss though is the option to store internet disruptions in a txt file and an automated email to my ISP. – rhand Oct 02 '18 at 01:57
4

there are many IPs on internet, a light approach is to ping some of them

 if ping -c 4 google.com ; then OK ; else KO ; fi
 if ping -c 4 facebook.com ; then OK ; else KO ; fi
 if ping -c 4 nsa.gov ; then OK ; else KO ; fi # <- this one might not reply

a more complete answer might be getting pages using wget

 wget google.com -o google.txt
 if parse google.txt ; then OK ; else KO ; fi

where

  • parse is a program you write that ensure google.txt is not a (too old) cached version of google.com
Archemar
  • 31,554
2

thanks to your contributions from each user and other web, I managed to complete this script in 3 days. and I will leave it free for its use.

this script automates the renewal of the ip address when connection is lost, it does so persistently.

#!/bin/bash

# Autor: John Llewelyn
# FB: fb.com/johnwilliam.llewelyn
# Twitter: twitter.com/JWLLEWELYN
# TLF: +584-1491-011-15
# Its use is free.
# Description: Connection Monitor for ADSL modem.
# Requirements:
# Copy this code or save to /home/administrator/ConnectionMonitor.sh
# It requires the installed packages fping beep and cron
# Comment the blacklist pcspkr snd-pcsp in /etc/modprobe.d/blacklist.conf
# Give execute permissions: chmod +x /home/administrator/ConnectionMonitor.sh
# Add this line in crontab -e with root user
# @reboot sleep 120 && /home/administrator/MonitorDeConexion.sh

#################################################################################
# SETTINGS
TEST="8.8.8.8"       # TEST PING
ADAPTER1="enp4s0"    # EXTERNAL ETHERNET ADAPTER

# Report
LOGFILE=/home/administrator/Documentos/ReportInternet.log

# Messages
MESSAGE1="Restoring Connectivity..."
MESSAGE2="Wait a moment please..."
MESSAGE3="No Internet connectivity."
MESSAGE4="Yes, there is Internet connectivity."
#################################################################################

# Time and Date
TODAY=$(date "+%r %d-%m-%Y")

# Show IP Public Address
IPv4ExternalAddr1=$(ip addr list $ADAPTER1 |grep "inet " |cut -d' ' -f6|cut -d/ -f1)
IPv6ExternalAddr1=$(ip addr list $ADAPTER1 |grep "inet6 " |cut -d' ' -f6|cut -d/ -f1)

# Alarm
alarm() {
    beep -f 1500 -l 200;beep -f 1550 -l 200;beep -f 1500 -l 200;beep -f 1550 -l 200;beep -f 1500 -l 200;beep -f 1550 -l 200;beep -f 1500 -l 200;beep -f 1550$
}

# Restoring Connectivity
resolve() {
    clear
    echo "$MESSAGE1"
    sudo ifconfig $ADAPTER1 up;sudo dhclient -r $ADAPTER1;sleep 5;sudo dhclient $ADAPTER1
    echo "$MESSAGE2"
    sleep 120
}

# Execution of work
while true; do
    if [[ "$(fping -I $ADAPTER1 $TEST | grep 'unreachable' )" != "" ]]; then
        alarm
        clear
        echo "================================================================================" >> ${LOGFILE}
        echo "$MESSAGE3 - $TODAY"                                                               >> ${LOGFILE}
        echo "$MESSAGE3 - $TODAY"
        echo "================================================================================" >> ${LOGFILE}
        sleep 10
        resolve
    else
        clear
        echo "================================================================================"   >> ${LOGFILE}
        echo "$MESSAGE4 - $TODAY - IPv4 Addr: $IPv4ExternalAddr1 - IPv6 Addr: $IPv6ExternalAddr1" >> ${LOGFILE}
        echo "$MESSAGE4 - $TODAY - IPv4 Addr: $IPv4ExternalAddr1 - IPv6 Addr: $IPv6ExternalAddr1"
        echo "================================================================================"   >> ${LOGFILE}
        sleep 120
    fi
done

pastebin: https://pastebin.com/wfSkpgKA

  • What would make this answer better: (1) Explaining how the script works.  (It looks like the user has to edit the script if his network interface is called anything other than eth0, but this is not mentioned.)  (2) Using English.  (3) Putting all shell variables (e.g., "$HOST", "$LINE1" and "$LOG") into double quotes.  (4) Either set LINE2 or don’t use it.  (I suspect that you got LINE1 / LINE2 confused with inet4 / inet6.) … (Cont’d) – G-Man Says 'Reinstate Monica' Dec 04 '17 at 04:02
  • (Cont’d) …  (5) Actually displaying the current time when you say you are displaying the current time, rather than capturing the time when the script starts and displaying it throughout the lifetime of the script.  (6) I think there was something else, but I don’t see it now. – G-Man Says 'Reinstate Monica' Dec 04 '17 at 04:02
  • It is in Spanish because it is in my language, but I can correct it in English. The $ HOST is the address to try. $ LINE1 is the Internet connection that is connected by the eth0 adapter. $ LINE2 is the Internet connection that is connected by the eth1 adapter optionally if you have 2 Internet lines, but it is recommended that you leave it disabled. The date, if I verify that it maintains the same time and date since I start the script, I have to correct that problem. This weekend I correct the problem. – John Llewelyn Dec 05 '17 at 21:32
  • Ok G-Man, I made some changes, I still need to correct date and improve some things. – John Llewelyn Jan 05 '18 at 08:21
2

I am using the following script:

#!/bin/bash

_beep() { ( \speaker-test --frequency 400 --test sine -l 1 -p 1 -P 2 >& /dev/null )& # pink / wav pid=$! \sleep 0.5s \kill -9 $pid \wait $pid 2>/dev/null }

con=0 while [ true ]; do if ! ping -c1 -W 2 google.com >& /dev/null; then echo "No internet..." con=0 _beep else if [[ $con -ne 1 ]] then echo "Connected." con=1 fi sleep 1 fi

done

Here, I am declaring a function beep that is getting called when the ping to the Google server is failing.

Doi
  • 21
1

Any suggestions on the proper/reliable way to check for internet connectivity using scripts? Do I have to use some packages?

Here's the bash code I use to check Internet connectivity:

while :
do
    socat /dev/null TCP4:google.com:443 && break
    sleep 60
done

This code puts socat in an infinite loop. It repeats until socat returns a $? value of 0, at which point, the break is executed, and the infinite loop is exited for continuation of the script.

This works on my network as I have nothing cached on the WAN side of my firewall. If you know a server more reliable than google.com:443 - you should use that. The /dev/null is included in the socat invocation b/c socat requires two points for a connection. I've found this an effective solution for getting past short-term outages.

Seamus
  • 2,925
0

@PNDA suggested getting data from ethtool, which I like. But, I prefer piping to grep and using a more simple awk command that non-bash people can figure out faster. The time difference between the two is negligible.

Using: Ubuntu Bionic 18.04

Network interface discovery:

root@srv:~# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:1e:67:96:a3:97 brd ff:ff:ff:ff:ff:ff
    inet 10.0.1.101/8 brd 10.255.255.255 scope global eno1
       valid_lft forever preferred_lft forever
    inet6 fe80::21e:67ff:fe96:a397/64 scope link 
       valid_lft forever preferred_lft forever
3: rename3: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
    link/ether 00:1e:67:96:a3:96 brd ff:ff:ff:ff:ff:ff

ethtool output:

root@srv:~# ethtool eno1
Settings for eno1:
        Supported ports: [ TP ]
        Supported link modes:   10baseT/Half 10baseT/Full 
                                100baseT/Half 100baseT/Full 
                                1000baseT/Full 
        Supported pause frame use: No
        Supports auto-negotiation: Yes
        Supported FEC modes: Not reported
        Advertised link modes:  10baseT/Half 10baseT/Full 
                                100baseT/Half 100baseT/Full 
                                1000baseT/Full 
        Advertised pause frame use: No
        Advertised auto-negotiation: Yes
        Advertised FEC modes: Not reported
        Speed: 1000Mb/s
        Duplex: Full
        Port: Twisted Pair
        PHYAD: 1
        Transceiver: internal
        Auto-negotiation: on
        MDI-X: on (auto)
        Supports Wake-on: pumbg
        Wake-on: g
        Current message level: 0x00000007 (7)
                               drv probe link
        Link detected: yes

Command example:

ethtool eno1 | grep "ink detected" | awk '{print $3}'

Choose your own adventure:

ethtool {{network adapter}} | grep "ink detected" | awk '{print $3}'

Output:

If there is a link:

root@srv:~# ethtool eno1 | grep "ink detected" | awk '{print $3}'
yes

If there is not a link:

root@srv:~# ethtool rename3 | grep "ink detected" | awk '{print $3}'
no
0

If you want to avoid false positives due to the presence of a Captive Portal you'll have to check the output of an HTTP connection against a website of predictable content/answer.

Something like this:

#!/bin/sh

detection_out=$(wget -q http://detectportal.firefox.com/success.txt --timeout=10 -O - 2> /dev/null)

test "$detection_out" = "success"

exit $?

This checks if the content of http://detectportal.firefox.com/success.txt is exactly identical to success.

The output is provided as an exit code: if the exit code is zero, the connection is available, otherwise it is either not available for some reason or blocked by a captive portal.

I took this script from here: https://github.com/libremesh/lime-packages/pull/712/files

0

If you need for you script or alias oneline command to check Internet connectivity on Linux or macOS, you can use this:

[ $(wget --quiet --spider https://google.com &>/dev/null; echo $?) -ne 0 ] && echo "No Internet" && exit

adapted from: 9 commands to check if connected to internet with shell script examples | GoLinuxCloud

  • Your [ $(wget --quiet --spider https://google.com &>/dev/null; echo $?) -ne 0 ] is better written just as ! wget --quiet --spider https://google.com &>/dev/null – Chris Davies Apr 17 '23 at 12:47