diff --git a/root/defaults/tcpping b/root/defaults/tcpping index 3e5c61f..5edeae9 100755 --- a/root/defaults/tcpping +++ b/root/defaults/tcpping @@ -1,12 +1,14 @@ #!/bin/sh # # tcpping: test response times using TCP SYN packets -# URL: http://www.vdberg.org/~richard/tcpping.html +# URL: https://github.com/deajan/tcpping +# Former URL: http://www.vdberg.org/~richard/tcpping.html # -# uses tcptraceroute from http://michael.toren.net/code/tcptraceroute/ +# uses recent versions of traceroute supporting TCP sessions # -# (c) 2002-2005 Richard van den Berg under the GPL -# http://www.gnu.org/copyleft/gpl.html +# (c) 2002-2024 Richard van den Berg , Orsiris de Jong +# under the GPL http://www.gnu.org/copyleft/gpl.html +# # # 2002/12/20 v1.0 initial version # 2003/01/25 v1.1 added -c and -r options @@ -18,66 +20,291 @@ # 2007/01/19 v1.6 catch non-root tcptraceroute # 2008/02/10 v1.7 make -C work when reverse lookup fails, courtesy of Fabrice Le Dorze # 2010/06/04 v1.8 make -C work when ipaddress doesn't reply, courtesy of Yann Beulque +# From v2.0 upwards, maintainer is Orsiris de Jong +# 2018/04/25 v2.0 make tcpping work with recent traceroute binary (tested with version 2.0.22) +# added language agonstic traceroute support +# added FreeBSD 11 traceroute and csh support +# added support for other ttl than 255 +# added -z debug parameter which shows what is actually sent to traceroute +# drop tcptraceroute prerequisite +# removed elder options (-l, -p which is defined as ending optional argument) +# multiple small improvements (local variables, better readability) +# 2018/07/11 v2.1 added preflight checks for traceroute and bc binaries +# comparaisons are now strict postix, thanks to https://github.com/agail +# 2018/11/26 v2.2 added checks for root privileges, courtesy of Jim Conner +# added -Z parameter for sudo +# added -M parameter for protocol +# removed bc dependancy +# fixed bogus -w parameter +# 2019/09/25 v2.3 allow -w parameter to take floats under linux +# meantime between checks is now lowered if -w parameter is less than 1 second on linux +# added -o parameter, which outputs statistics similar to ping +# Simpify main loop +# fix ambiguous output redirect in csh for preflight check +# 2020/04/19 v2.4 fixed -r parameter, which also accepts floats now, and added -r auto option, which calculates +# a wait interval based on timeToWait (-w) parameter +# Do not wait additional interval time when ping fails before running next try +# Remove _checkSite function (merged into _testSite) to avoid code duplicartion, and lower execution time +# Set default wait interval (-r) parameter to auto +# Set default timeToWait parameter to 1 second +# 2024/09/04 v2.5 Fixed set -o pipefail only exists on bash +# Aloow _DEBUG parameter needed to be set in environment +# Fixed -n parameter together with -C parameter did not produce expected output +# Improved traceroute binary detection +# Send error messages to stderr +# Added zsh support +# Added fallback support for tcptraceroute, courtesy of Damien Mascord +# 2024/10/24 v2.6 Added support for optional /etc/tcpping.conf file +# Various shellcheck fixes - -ver="v1.8" +ver="v2.6-dev" format="%Y%m%d%H%M%S" d="no" c="no" C="no" -ttl=255 +f_ttl=255 +m_ttl=255 seq=0 -q=1 -r=1 -w=3 -topts="" +numberOfQueries=1 +interval=auto +timeToWait=1 +topt="" +SUDO_COMMAND="" +LOCAL_OS= +METHOD=tcp +summary="no" +x=0 +traceRouteBinary="traceroute" + +# Make sure traceroute output is language agnostic +export LANG=C + +# Make sure we get exit codes from piped commands +# dash < 0.5.12 doesn't support this, so we can't just activate it since there's no easy way to check dash version without making use of repo management +[ -n "$BASH_VERSION" ] && set -o pipefail + +# ZSH doesn't split words by default, so we need to set it for script to run in bash/dash/zsh +[ -n "$ZSH_VERSION" ] && setopt sh_word_split + +# Load optional configuration file from /etc or current directory +[ -f "/etc/tcpping.conf" ] && . "/etc/tcpping.conf" +[ -f "./tcpping.conf" ] && . "./tcpping.conf" + +# Apply extra arguments from configuration file if exist +[ -n "${TCPPING_EXTRA_ARGS}" ] && set -- ${TCPPING_EXTRA_ARGS} ${@} + +# Set _DEBUG to false unless already set +[ "$_DEBUG" = "" ] && _DEBUG=false usage () { name=`basename $0` - echo "tcpping $ver Richard van den Berg " + echo "tcpping $ver Richard van den Berg , Orsiris de Jong " echo echo "Usage: $name [-d] [-c] [-C] [-w sec] [-q num] [-x count] ipaddress [port]" echo - echo " -d print timestamp before every result" - echo " -c print a columned result line" - echo " -C print in the same format as fping's -C option" - echo " -w wait time in seconds (defaults to 3)" - echo " -r repeat every n seconds (defaults to 1)" - echo " -x repeat n times (defaults to unlimited)" + echo " -d print timestamp before every result" + echo " -c print a computer parsable result line" + echo " -C print in the same format as fping's -C option" + echo " -w wait time in seconds (defaults to 1). Linux supports float values, ie '.2'" + echo " -r repeat every n seconds (defaults to auto). Linux supports float values, ie '.2'" + echo " also accepts 'auto' value which works with -x in order to make total execution time lower than interval * repeats" + echo " -x repeat n times (defaults to unlimited)" + echo " -f first ttl (defaults to 255), see traceroute man" + echo " -m max ttl (defaults to 255), see traceroute man" + echo " -nNFASEisfm see traceroute man" + echo " -M method (tcp, udp, icmp...), see traceroute man" + echo " --sport define source port, see traceroute man" + echo " -z show what command is actually sent to traceroute (debug)" + echo " -Z run traceroute with sudo" + echo " -o Output ping like statistics" echo - echo "See also: man tcptraceroute" + echo "Default port is 80" + echo "See also: man traceroute" echo } -_checksite() { - ttr=`tcptraceroute -f ${ttl} -m ${ttl} -q ${q} -w ${w} $* 2>&1` - if echo "${ttr}" | egrep -i "(bad destination|got roo)" >/dev/null 2>&1; then - echo "${ttr}" - exit +getLocalOS() { + local localOsVar + + # There is no good way to tell if currently running in BusyBox shell. Using sluggish way. + if ls --help 2>&1 | grep -i "BusyBox" > /dev/null; then + localOsVar="BusyBox" + else + # Detecting the special ubuntu userland in Windows 10 bash + if grep -i Microsoft /proc/sys/kernel/osrelease > /dev/null 2>&1; then + localOsVar="Microsoft" + else + localOsVar="`uname -spior 2>&1`" + if [ $? != 0 ]; then + localOsVar="`uname -v 2>&1`" + if [ $? != 0 ]; then + localOsVar="`uname`" + fi + fi + fi + fi + + case $localOsVar in + # Android uname contains both linux and android, keep it before linux entry + *"Android"*) + LOCAL_OS="Android" + ;; + *"Linux"*) + LOCAL_OS="Linux" + ;; + *"BSD"*) + LOCAL_OS="BSD" + ;; + *"MINGW32"*|*"MINGW64"*|*"MSYS"*) + LOCAL_OS="msys" + ;; + *"CYGWIN"*) + LOCAL_OS="Cygwin" + ;; + *"Microsoft"*) + LOCAL_OS="WinNT10" + ;; + *"Darwin"*) + LOCAL_OS="MacOSX" + ;; + *"BusyBox"*) + LOCAL_OS="BusyBox" + ;; + *) + echo >&2 "Running on unknown local OS [$localOsVar]." + ;; + esac +} + +checkEnvironment() { + if ! type awk > /dev/null 2>&1; then + echo >&2 "awk binary not found. Please install it first" + exit 2 + fi + + if type traceroute > /dev/null 2>&1; then + traceRouteBinary=`type traceroute | awk '{print $(NF)}'` + if [ `${traceRouteBinary} -M 2>&1 | grep -c unrecog` -eq 0 ]; then + traceRouteImplementation="traceroute" + return + else + if [ "${_DEBUG}" = true ]; then + echo >&2 "traceroute does not support -M parameter, switching to tcptraceroute" + fi + fi + fi + + if type tcptraceroute > /dev/null 2>&1; then + traceRouteBinary=`type tcptraceroute | awk '{print $(NF)}'` + traceRouteImplementation="tcptraceroute" + return + else + echo >&2 "traceroute binary not found. Switching to tcptraceroute also failed." + exit 1 fi } -_testsite() { - myseq="${1}" +# Check if site can be reached via TCP SYN + +# Measure latency via TCP SYN / UDP / ICMP +_testSite() { + local host="${1}" + local port="${2:-80}" + local myseq="${3}" + + local args= + local i=1 + local traceroute_arg_n=false + for givenArgs in "${@}"; do + if [ $i -gt 3 ]; then + args="$args $givenArgs" + if [ "${givenArgs}" = "-n" ]; then + traceroute_arg_n=true + fi + fi + i=`expr $i + 1` + done + + local traceRoute= + local traceRouteCommand= + local rtt= + shift [ "${c}" = "yes" ] && nows=`date +${format}` [ "${d}" = "yes" ] && nowd=`date` - ttr=`tcptraceroute -f ${ttl} -m ${ttl} -q ${q} -w ${w} $* 2>/dev/null` - host=`echo "${ttr}" | awk '{print $2 " " $3}'` - rtt=`echo "${ttr}" | sed 's/.*] //' | awk '{print $1}'` + + if [ "$traceRouteImplementation" = "tcptraceroute" ]; then + if [ "${METHOD}" != "tcp" ] && [ "$LOCAL_OS" != "BSD" ] && [ "$LOCAL_OS" != "MacOSX" ]; then + echo echo >&2 "Cannot use ${traceRouteBinary} with method ${METHOD}. Please use traceroute" + exit 23 + fi + traceRouteCommand="$SUDO_COMMAND${traceRouteBinary} -f ${f_ttl} -m ${m_ttl} -q ${numberOfQueries} -w ${timeToWait} ${args} ${host} ${port}" + else + traceRouteCommand="$SUDO_COMMAND${traceRouteBinary} ${METHOD_PARAMETER} ${METHOD} -f ${f_ttl} -m ${m_ttl} -q ${numberOfQueries} -w ${timeToWait} ${args} -p ${port} ${host}" + fi + if [ "${_DEBUG}" = true ]; then + echo "$traceRouteCommand" + fi + + # BSD traceroute (and tcptraceroute) outputs header line to stderr while outputting results to stdout, whereas linux versions output everything to stdout + if [ "$traceRouteImplementation" = "tcptraceroute" ] || [ "$LOCAL_OS" = "BSD" ] || [ "$LOCAL_OS" = "MacOSX" ]; then + traceRoute=`$traceRouteCommand 2>/dev/null` + else + # Remove first line from result when using traceroute to avoid header + traceRoute=`$traceRouteCommand 2>/dev/null | awk 'NR>1'` + fi + result=$? + + if [ "$traceRouteImplementation" = "tcptraceroute" ] && [ $myseq -eq 0 ]; then + if [ $result -ne 0 ]; then + if [ "`id -u`" -ne 0 ]; then + echo >&2 "Unable to run '$traceRouteCommand' command. Please try run $0 with -Z parameter or 'sudo $0'" + exit 20 + else + echo >&2 "Unable to run '$traceRouteCommand' command. Please submit an issue in 'https://github.com/deajan/tcpping/issues' with the output of the command." + exit 21 + fi + fi + + if echo "${traceRoute}" | grep -Ei "(bad destination|got roo|not known|cannot handle)" >/dev/null 2>&1; then + echo >&2 "${traceRoute}" + exit 22 + fi + fi + + if [ "$traceRouteImplementation" = "tcptraceroute" ]; then + rtt=`echo "${traceRoute}" | sed 's/.*] //' | awk '{print $1}'` + # if RTT is 255, try again with ACK method + if [ "$rtt" = "255" ]; then + if [ "${_DEBUG}" = true ]; then + echo >&2 "SYN method failed, attempting ACK method..." + fi + traceRouteCommand="$SUDO_COMMAND${traceRouteBinary} -A -f ${f_ttl} -m ${m_ttl} -q ${numberOfQueries} -w ${timeToWait} ${args} ${host} ${port}" + traceRoute=`$traceRouteCommand 2>/dev/null` + rtt=`echo "${traceRoute}" | sed 's/.*] //' | awk '{print $1}'` + fi + else + rtt=`echo "${traceRoute}" | awk '{print $4}'` + fi + if [ "${traceroute_arg_n}" = true ]; then + rtt=`echo "${traceRoute}" | awk '{print $3}'` + else + rtt=`echo "${traceRoute}" | awk '{print $4}'` + fi not=`echo "${rtt}" | tr -d ".0123456789"` + [ "${d}" = "yes" ] && echo "$nowd" if [ "${c}" = "yes" ]; then - if [ "x${rtt}" != "x" -a "x${not}" = "x" ]; then + if [ "${rtt}" != "" ] && [ "${not}" = "" ]; then echo "$myseq $nows $rtt $host" else - echo "$myseq $nows $max $host" + echo "$myseq $nows $maxRtt $host" fi elif [ "${C}" = "yes" ]; then if [ "$myseq" = "0" ]; then - echo -n "$1 :" + echo -n "$host :" fi - if [ "x${rtt}" != "x" -a "x${not}" = "x" ]; then + if [ "${rtt}" != "" ] && [ "${not}" = "" ]; then if [ $rtt != "255" ]; then echo -n " $rtt" else @@ -90,60 +317,127 @@ _testsite() { echo fi else - echo "${ttr}" | sed -e "s/^.*\*.*$/seq $myseq: no response (timeout)/" -e "s/^$ttl /seq $myseq: tcp response from/" + echo "${traceRoute}" | sed -e "s/^.*\*.*$/seq $myseq: no response (timeout)/" -e "s/^$m_ttl /seq $myseq: tcp response from/" fi -# echo "${ttr}" + + echo "${traceRoute}" | awk '($1 == "*" || $2 == "*"){ exit 1 }' + return $? +} + +_quit() { + echo "" + echo "${pingCount} packets transmitted, `expr ${pingCount} - ${pingFailCount}` received." + exit } -while getopts dhq:w:cr:nNFSAEi:f:l:m:p:s:x:C opt ; do +while getopts Zdhzq:w:cr:nNFSAEi:f:l:m:p:s:x:CM:o opt ; do case "$opt" in d|c|C) eval $opt="yes" ;; - q|w|r|x) eval $opt="$OPTARG" ;; + q|x) eval $opt="$OPTARG" ;; + r) interval="$OPTARG" ;; n|N|F|S|A|E) topt="$topt -$opt" ;; - i|l|p|s) topt="$topt -$opt $OPTARG" ;; - f|m) ttl="$OPTARG" ;; + i|s) topt="$topt -$opt $OPTARG" ;; + w) timeToWait="$OPTARG" ;; + f) f_ttl="$OPTARG" ;; + m) m_ttl="$OPTARG" ;; + M) METHOD="$OPTARG" ;; + Z) SUDO_COMMAND="sudo " ;; + z) _DEBUG=true ;; + o) summary="yes" ;; ?) usage; exit ;; esac done +checkEnvironment + shift `expr $OPTIND - 1` -if [ "x$1" = "x" ]; then +if [ "$1" = "" ]; then usage exit fi -max=`echo "${w} * 1000" | bc` +if [ "${summary}" = "yes" ]; then + trap _quit QUIT INT +fi + +# Use awk to multiply possible float in timeToWait +maxRtt=`awk 'BEGIN{printf "%.2f\n", ('${timeToWait}'*1000)}'` if [ `date +%s` != "%s" ]; then format="%s" fi -_checksite ${topt} $* +getLocalOS -if [ "$x" = "" ]; then - while [ 1 ] ; do - _testsite ${seq} ${topt} $* & - pid=$! - if [ "${C}" = "yes" ]; then - wait $pid - fi - seq=`expr $seq + 1` - sleep ${r} - done +if [ "$LOCAL_OS" = "BSD" ] || [ "$LOCAL_OS" = "MacOSX" ]; then + METHOD_PARAMETER="-P" else - while [ "$x" -gt 0 ] ; do - _testsite ${seq} ${topt} $* & - pid=$! - if [ "${C}" = "yes" ]; then - wait $pid - fi - seq=`expr $seq + 1` - x=`expr $x - 1` - if [ "$x" -gt 0 ]; then - sleep ${r} - fi - done + METHOD_PARAMETER="-M" +fi + +i=1 +for args in "${@}"; do + if [ $i -eq $# ]; then + lastArg=$args + elif [ $i -eq `expr $# - 1` ]; then + beforeLastArg=$args + fi + i=`expr $i + 1` +done + +case $lastArg in + ''|*[!0-9]*) + # Last argument is not numeric, assuming it's an FQDN or IP + host=$lastArg + ;; + *) + # Last argument is numeric, assuming it's a port number + host=$beforeLastArg + port=$lastArg + ;; +esac + +# Try to compute interval value so whole execution takes less time than (repeats * interval) +# Remove an arbitrary second for pre execution script work +# Use an arbitrary factor of 0.7 to cope with shell execution time + +if [ "$interval" = "auto" ]; then + interval=`awk 'BEGIN { print ('$timeToWait'*.7)}'` fi +pingCount=0 +pingFailCount=0 + +while [ $pingCount -lt $x ] || [ $x -eq 0 ]; do + result="" + _testSite "${host}" "${port}" ${seq} ${topt} & + pid=$! + + if [ "${C}" = "yes" ]; then + wait $pid + result=$? + fi + seq=`expr $seq + 1` + if [ $seq -gt 0 ]; then + _DEBUG=false + fi + if [ "$result" = "" ]; then + wait $pid > /dev/null 2>&1 + result=$? + fi + + pingCount=`expr $pingCount + 1` + if [ $result -ne 0 ]; then + pingFailCount=`expr $pingFailCount + 1` + if [ $result -eq 20 ] || [ $result -eq 21 ] || [ $result -eq 22 ]; then + exit $result + fi + elif [ "$pingCount" -lt $x ] || [ $x -eq 0 ]; then + sleep "${interval}" + fi +done +if [ "$summary" = "yes" ]; then + _quit +fi exit