#!/bin/bash
# Network testing script v 1.8
# (c) 2005,2006 Javier Fernandez-Sanguino
#
# This script will test your system's network configuration using basic
# tests and providing both information (INFO messages), warnings (WARN)
# and possible errors (ERR messages) by checking:
# - Interface status
# - Availability of configured routers, including the default route
# - Proper host resolution, including DNS checks
# - Proper network connectivity, including ICMP and web connections to
# a remote web server (the web server used for the tests can be configured,
# see below)
#
# Some of the network tests are described in more detail at
# http://ubuntuforums.org/archive/index.php/t-25557.html
#
# The script does not need special privileges to run as it does not
# do any system change. It also will not fix the errors by itself.
#
# Additional software requirements:
# * ip from the iproute package. (could probably be rewrittent to
# use ifconfig only or to parse /proc)
# * ping from the iputils-ping package or the netkit-ping package.
# * nc from the netcat package.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# You can also find a copy of the GNU General Public License at
# http://www.gnu.org/licenses/licenses.html#TOCLGPL
#
# TODO
# - Works only on Linux, can this be generalised for other UNIX systems
# (probably not unless rewritten in C)
# - Does not check for errors properly, use -e and test intensively
# so that expected errors are trapped
# (specially for tools that are not available, like netcat)
# - If the tools are localised to languages != english the script might
# break
# - Ask 'host' maintainer to implement error codes as done with
# dlint
# - Should be able to check if DNS server is in the same network, if
# it doesn't answer to pings, check ARP in that case.
# - DHCP checks?
# - Other internal services tests? (LDAP if using pam...)
# - Generate summary of errors in the end (pretty report?)
# - Check if packets are being dropped by local firewall? (use dmesg
# and look for our tests)
# - Support wireless interfaces? (use iwconfig)
# - Check for more than one web server (have CHECK_HOSTS be a number
# of hosts and determine a metric to spout an error) ?
# - Use traceroute or tcptraceroute to see if there is network connectivity?
# (traceroute is usually blocked by firewalls but tcptraceroute might
# be an alternative to using nc)
# - Use mii-tool (requires root privileges)
# - Use ping -s XXXX to detect invalid MTUs
# - Use arpping to detect another host with our same IP address
# - Check other TODOs inline in the code
while getopts ":hsv:" Option
do
case $Option in
h ) cat <<- EOF
Usage: $0 [-s][-v <num>]
-s Also log messages to local3 syslog facility
-v 0 Silent run
-v 1 Show only error messages
-v 2 Show error and warning messages
-v 3 Fully verbose (default)
EOF
exit 0;;
v ) VERB=$OPTARG;;
s ) LOG=1;;
esac
done
# BEGIN configuration
# Configure to your needs, these values will be used when
# checking DNS and Internet connectivity
# DNS name to resolve.
# These are default values which can be overriden by the environment.
[ -z "$CHECK_HOST" ] && CHECK_HOST=www.debian.org
[ -z "$CHECK_IP_ADRESS" ] && CHECK_IP_ADRESS=194.109.137.218
# Web server to check for
[ -z "$CHECK_WEB_HOST" ] && CHECK_WEB_HOST=www.debian.org
[ -z "$CHECK_WEB_PORT" ] && CHECK_WEB_PORT=80
# END configuration
export CHECK_HOST CHECK_IP_ADRESS CHECK_WEB_HOST CHECK_WEB_PORT
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LC_ALL=C
export PATH LC_ALL
# error reporting and logging functions
info () {
[ "$VERB" -gt 2 ] && echo "INFO: $1"
[ "$VERB" -gt 2 ] && [ "$LOG" ] && logger -p local3.info "$0 INFO: $1"
}
warn () {
[ "$VERB" -gt 1 ] && echo "WARN: $1"
[ "$VERB" -gt 1 ] && [ "$LOG" ] && logger -p local3.warn "$0 WARN: $1"
}
err () {
[ "$VERB" -gt 0 ] && echo "ERR: $1" >&2
[ "$VERB" -gt 0 ] && [ "$LOG" ] && logger -p local3.err "$0 ERR: $1"
}
# Check if all commands we need are available
# NOTE: if using nslookup add "nslookup dnsutils"
( echo -e "netstat net-tools\nifconfig net-tools\n\
ping netkit-ping|inetutils-ping|iputils-ping\n\
arp net-tools\nip iproute\nhost host|bind9-host\nmktemp debianutils\n\
nc netcat" |
while read cmd package; do
if ! `which $cmd 2>/dev/null >&2`; then
err "$cmd is not available! (please install $package)"
exit 1
fi
done ) || exit 1
# Recommended programs
( echo -e "ethtool ethtool" |
while read cmd package; do
if ! `which $cmd 2>/dev/null >&2`; then
warn "$cmd is not available (consider installing $package)"
exit 1
fi
done )
# Default route for programs
ETHTOOL=/usr/sbin/ethtool
MIITOOL=/sbin/mii-tool
# Other needs
# We need /proc/net
if [ ! -d /proc/net ] ; then
err "/proc is not available! Please mount it ('mount -t /proc')"
exit 1
fi
# Extract the interface of our default route
defaultif=`netstat -nr |grep ^0.0.0.0 | awk '{print $8}' | head -1`
defaultroutes=`netstat -nr |grep ^0.0.0.0 | wc -l`
if [ -z "$defaultif" ] ; then
defaultif=none
warn "This system does not have a default route"
elif [ "$defaultroutes" -gt 1 ] ; then
warn "This system has more than one default route"
else
info "This system has exactly one default route"
fi
# Check loopback
check_local () {
# Is there a loopback interface?
if [ -n "`ip link show lo`" ] ; then
# OK, can we ping localhost
if ! check_host localhost 1; then
# Check 127.0.0.1 instead (not everybody uses this IP address however,
# although its the one commonly used)
if ! check_host 127.0.0.1 1; then
err "Cannot ping localhost (127.0.0.1), loopback is broken in this system"
else
err "Localhost is not answering but 127.0.0.1, check /etc/hosts and verify localhost points to 127.0.0.1"
fi
else
info "Loopback interface is working properly"
fi
else
err "There is no loopback interface in this system"
status=1
fi
status=0
return $status
}
check_if_link_miitool () {
ifname=$1
[ ! -x "$MIITOOL" ] && return 0
status=0
if $MIITOOL $ifname 2>&1| grep -q "no link"; then
status=1
fi
return $status
}
check_if_link_ethtool () {
# Check if the interface has link
# Note: Unlike other sections of the script we need to be root
# to test this
ifname=$1
[ ! -x "$ETHTOOL" ] && return 0
status=0
LINK=`$ETHTOOL $ifname 2>&1| grep "Link detected"`
# If ethtool fails to print out the link line we break off
# notice that ethtool cannot get the link status out of all
# possible network interfaces
[ -z "$LINK" ] && return
if ! echo $LINK | grep -q "Link detected: yes" ; then
status=1
fi
return $status
}
check_if_link_iplink () {
ifname=$1
status=0
[ ! -x /sbin/ip ] && return 0
if /sbin/ip link show $ifname 2>&1 | grep -q "NO-CARRIER"; then
status=1
fi
return $status
}
check_if_link() {
status=-1
iface=$1
# Use ethtool if installed (preferable to mii-tool)
# If none are installed we will test using 'ip link show'
if [ "`id -u`" -eq 0 ] ; then
if [ -x "$ETHTOOL" ] ; then
check_if_link_ethtool $iface
status=$?
elif [ -x "$MIITOOL" ]; then
check_if_link_miitool $iface
status=$?
fi
fi
# If no test has done use ip link
if [ $status -eq -1 ]; then
check_if_link_iplink $iface
status=$?
fi
return $status
}
# Check network interfaces
check_if () {
ifname=$1
status=0
[ -z "$ifname" ] && return 1
# Check if the interface has a link
case "$ifname" in
eth*) check_if_link $ifname ; status=$?;;
*) ;;
esac
# Print results
if [ $status -ne 0 ] ; then
if [ "$ifname" = "$defaultif" ] ; then
err "The $ifname interface that is associated with your default route has no link!"
else
warn "Interface $ifname does not have link"
fi
fi
# Find IP addresses for $ifname
inetaddr=`ip addr show $ifname | grep inet | awk '{print $2}'`
if [ -z "$inetaddr" ] ; then
warn "The $ifname interface does not have an IP address assigned"
status=1
else
# TODO: WARN if more than 2 IP addresses?
echo $inetaddr | while read ipaddr; do
info "The $ifname interface has IP address $ipaddr assigned"
done
fi
# Lookup TX and RX statistics
# TODO: This is done using ifconfig but could use /proc/net/dev for
# more readibility or, better, 'netstat -i'
txpkts=`ifconfig $ifname | awk '/RX packets/ { print $2 }' |sed 's/.*://'`
rxpkts=`ifconfig $ifname | awk '/RX packets/ { print $2 }' |sed 's/.*://'`
txerrors=`ifconfig $ifname | awk '/TX packets/ { print $3 }' |sed 's/.*://'`
rxerrors=`ifconfig $ifname | awk '/RX packets/ { print $3 }' |sed 's/.*://'`
# TODO: Check also frames and collisions, to detect faulty cables
# or network devices (cheap hubs)
if [ "$txpkts" -eq 0 ] && [ "$rxpkts" -eq 0 ] ; then
err "The $ifname interface has not tx or rx any packets. Link down?"
status=1
elif [ "$txpkts" -eq 0 ]; then
warn "The $ifname interface has not transmitted any packets."
elif [ "$rxpkts" -eq 0 ] ; then
warn "The $ifname interface has not received any packets."
else
info "The $ifname interface has tx and rx packets."
fi
# TODO: It should be best if there was a comparison with tx/rx packets.
# a few errors are not uncommon if the card has been running for a long
# time. It would be better if a relative comparison was done (i.e.
# less than 1% ok, more than 20% warning, over 80% major issue, etc.)
if [ "$txerrors" -ne 0 ]; then
warn "The $ifname interface has tx errors."
fi
if [ "$rxerrors" -ne 0 ]; then
warn "The $ifname interface has rx errors."
fi
return $status
}
check_netif () {
status=0
ip link show | egrep '^[[:digit:]]' |
while read ifnumber ifname status extra; do
ifname=`echo $ifname |sed -e 's/:$//'`
# TODO: this is redundant with the check if_link test
# (although faster since using it would make us call 'ip'
# twice.
if [ -n "`echo $status | grep NO-CARRIER`" ] ; then
if [ "$ifname" = "$defaultif" ] ; then
err "The $ifname interface that is associated with your default route is down!"
status=1
elif [ "$ifname" = "lo" ] ; then
err "Your lo interface is down, this might cause issues with local applications (but not necessarily with network connectivity)"
else
warn "The $ifname interface is down"
fi
else
# Check network routes associated with this interface
info "The $ifname interface is up"
check_if $ifname
check_netroute $ifname
fi
done
return $status
}
check_netroute () {
ifname=$1
[ -z "$ifname" ] && return 1
netstat -nr | grep "${ifname}$" |
while read network gw netmask flags mss window irtt iface; do
# For each gw that is not the default one, ping it
if [ "$gw" != "0.0.0.0" ] ; then
if ! check_router $gw ; then
err "The default route is not available since the default router is unreachable"
fi
fi
done
}
check_router () {
# Checks if a router is up
router=$1
[ -z "$router" ] && return 1
status=0
# First ping the router, if it does not answer then check arp tables and
# see if we have an arp. We use 5 packets since it is in our local network.
ping -n -q -c 5 "$router" >/dev/null 2>&1
if [ "$?" -ne 0 ]; then
warn "Router $router does not answer to ICMP pings"
# Router does not answer, check arp
routerarp=`arp -n | grep "^$router" | grep -v incomplete`
if [ -z "$routerarp" ] ; then
err "We cannot retrieve a MAC address for router $router"
status=1
fi
fi
if [ "$status" -eq 0 ] ; then
info "The router $router is reachable"
fi
return $status
}
check_host () {
# Check if a host is reachable
# TODO:
# - if the host is in our local network (no route needs to be used) then
# check ARP availability
# - if the host is not on our local network then check if we have a route
# for it
host=$1
[ -z "$host" ] && return 1
# Use 10 packets as we expect this to be outside of our network
COUNT=10
[ -n "$2" ] && COUNT=$2
status=0
ping -n -q -c $COUNT "$host" >/dev/null 2>&1
if [ "$?" -ne 0 ]; then
warn "Host $host does not answer to ICMP pings"
status=1
else
info "Host $host answers to ICMP pings"
fi
return $status
}
check_dns () {
# Check the nameservers defined in /etc/resolv.conf
status=1
nsfound=0
nsok=0
tempfile=`mktemp -t tmptestnet.XXXXXX` || { err "Cannot create temporary file! Aborting! " ; exit 1; }
trap " [ -f \"$tempfile\" ] && /bin/rm -f -- \"$tempfile\"" 0 1 2 3 13 15
cat /etc/resolv.conf | grep -v ^# | grep nameserver |
awk '/nameserver/ { for (i=2;i<=NF;i++) { print $i ; } }' >$tempfile
for nameserver in `cat $tempfile`; do
nsfound=$(( $nsfound + 1 ))
info "This system is configured to use nameserver $nameserver"
check_host $nameserver 5
if check_ns $nameserver ; then
nsok=$(( $nsok +1 ))
else
status=$?
fi
done
#Could also do:
#nsfound=`wc -l $tempfile | awk '{print $1}'`
/bin/rm -f -- "$tempfile"
trap 0 1 2 3 13 15
if [ "$nsfound" -eq 0 ] ; then
err "The system does not have any nameserver configured"
else
if [ "$status" -ne 0 ] ; then
if [ "$nsfound" -eq 1 ] ; then
err "There is one nameserver configured for this system but it does not work properly"
else
err "There are $nsfound nameservers configured for this system and none of them works properly"
fi
else
if [ "$nsfound" -eq 1 ] ; then
info "The nameserver configured for this system works properly"
else
info "There are $nsfound nameservers is configured for this system and $nsok are working properly"
fi
fi
fi
return $status
}
check_ns () {
# Check the nameserver using host
# TODO: use nslookup?
# nslookup $CHECK_HOST -$nameserver
nameserver=$1
[ -z "$nameserver" ] && return 1
status=1
CHECK_RESULT="$CHECK_HOST .* $CHECK_IP_ADDRESS"
# Using dnscheck:
dnscheck=`host -t A $CHECK_HOST $nameserver 2>&1 | tail -1`
if [ -n "`echo $dnscheck |grep NXDOMAIN`" ] ; then
err "Dns server $nameserver does not resolv properly"
elif [ -n "`echo $dnscheck | grep \"timed out\"`" ] ; then
err "Dns server $nameserver is not available"
elif [ -z "`echo $dnscheck | egrep \"$CHECK_RESULT\"`" ] ; then
warn "Dns server $nameserver did not return the expected result for $CHECK_HOST"
else
info "Dns server $nameserver resolved correctly $CHECK_HOST"
status=0
fi
# Using dlint
# dlint $CHECK_HOST @$nameserver >/dev/null 2>&1
# if [ $? -eq 2 ] ; then
# err "Dns server $nameserver does not resolv properly"
# elif [ $? -ne 0 ]; then
# err "Unexpected error when testing $nameserver"
# else
# info "Dns server $nameserver resolved correctly $CHECK_HOST"
# status=0
# fi
return $status
}
check_conn () {
# Checks network connectivity
if ! check_host $CHECK_WEB_HOST >/dev/null ; then
warn "System does not seem to reach Internet host $CHECK_WEB_HOST through ICMP"
else
info "System can reach Internet host $CHECK_WEB_HOST"
fi
status=0
# Check web access, using nc
# TODO:
# - this could also implement proxy checks (if the http_proxy environment is
# defined?)
# - could also check against a valid content copy (otherwise it might be
# fooled by transparent proxies)
echo -e "HEAD / HTTP/1.0\n\n" |nc -w 20 $CHECK_WEB_HOST $CHECK_WEB_PORT >/dev/null 2>&1
if [ $? -ne 0 ] ; then
err "Cannot access web server at Internet host $CHECK_WEB_HOST"
status=1
else
info "System can access web server at Internet host $CHECK_WEB_HOST"
fi
return $status
}
# TODO: checks could be conditioned, i.e. if there is no proper
# interface setup don't bother with DNS and don't do some Inet checks
# if DNS is not setup properly
check_local || exit 1
check_netif || exit 1
check_dns || exit 1
check_conn || exit 1
exit 0
# Set our locale environment, just in case ethtool gets translated
LC_ALL=C
export LC_ALL