#!/usr/bin/env bash # # Written by Sten Spans # Ported to os/x and freebsd by Nick Hilliard # # Add the following to .ssh/config to get logins to work without # confirmation: # -- # Host *.ring.nlnog.net # StrictHostKeyChecking no # User # -- # --> A lot of modifications by Arkadiusz Nowicki # debug=0 ping="ping" nodes=50 hinfo="" while getopts "6vitdn:" flag; do case $flag in 6) ping="ping6" ;; d) debug=1 ;; n) nodes=$OPTARG test "$nodes" -eq 0 && unset nodes ;; v) verbose=1 ;; i) minfo=1 ;; t) thop=1 ;; *) echo "Usage: $(basename $0) [-6ivtd] [-n ] host" exit 1 ;; esac done shift $((OPTIND-1)) if [ $# -lt 1 ]; then echo "No arguments were given" echo "Usage: $(basename $0) [-6vitd] [-n ] host" echo -e "\t-v \t\t print RTT for each server" echo -e "\t-t \t\t print hop distance for each server" echo -e "\t-i \t\t print host information for each server" echo -e "\t-6 \t\t use IPv6 - obsolete, used only for backwards compatibility" echo -e "\t-n \t\t number of nodes in test (default $nodes; use 0 for all nodes)" echo -e "\t-d \t\t debug mode" exit 1 fi # gnu sed needs "-r" for normal regexps; bsd sed requires "-E". Solaris is # broken. sedre="-r" sed ${sedre} -e 's///' < /dev/null > /dev/null 2>&1 if [ $? != 0 ]; then sedre="-E" sed ${sedre} -e 's///' < /dev/null > /dev/null 2>&1 if [ $? != 0 ]; then echo "sed does not understand extended regular expressions. exiting." exit 1 fi fi # BSD requires fdescfs to be mounted if [ `uname -s` = 'FreeBSD' -a `mount | grep fdescfs | wc -l` -eq 0 ]; then echo 'FreeBSD requires fdescfs to be mounted. See fdescfs(5) for more details.' exit 1 fi host=$(echo "$1" | sed ${sedre} -e 's/[^[:alnum:]:.-]+//g') [ -n "${host//[^:]}" ] && ping="ping6" for cmd in $ping dig; do location=`which ${cmd}` if [ $? != 0 ]; then echo "${cmd}: command not found." exit 1 fi done # basic checks to ensure that silly stuff is not passed through. It would # help if there were a getaddrinfo() command-line tool, but this does not # exist. # 1. check for general ipv4 style syntax: N.N.N.N (0.0.0.0 - 255.255.255.255) # 2. check for general ipv6 style syntax: hex digits and : # 3. dns lookups for A and AAAA if [ `echo ${host} | grep -E "\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b" | wc -l` -ne 0 ]; then test ${debug} -ge 1 && echo DEBUG: host is ipv4 elif [ `echo -n ${host} | grep -E "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" | wc -l` -ne 0 ]; then test ${debug} -ge 1 && echo DEBUG: host is ipv6 elif [ `dig -t A +short ${host} | grep -v '^;' | wc -l` -ne 0 ]; then test ${debug} -ge 1 && echo DEBUG: ipv4 A record lookup succeeded elif [ `dig -t AAAA +short ${host} | grep -v '^;' | wc -l` -ne 0 ]; then test ${debug} -ge 1 && echo DEBUG: ipv6 A record lookup succeeded else echo Invalid hostname ${host} exit 1 fi if [ -n "${thop}" ]; then trace_cmd="'\$(traceroute -I -N1 -w1 -q1 $host|wc -l)'" fi if [ -n "${minfo}" ]; then hinfo=":'\$(grep Location: /etc/motd|cut -d: -f2)'" fi ping_cmd="echo '\$(hostname)': '\$(${ping} -c1 -W1 $host | grep ^rtt)' $hinfo: $trace_cmd" ssh_cmd="ssh -q -o ConnectTimeout=3 {} ${ping_cmd}" SERVERS=$(curl -s https://api.ring.nlnog.net/1.0/nodes/active | jq -r ".results.nodes | map(select(.alive_ipv4 == 1 and .alive_ipv6 == 1)) | .[0:${nodes}][].hostname") test ${debug} -ge 1 && echo DEBUG: checking servers: ${SERVERS} declare -a results declare -a replies declare -a timeouts while read line; do server=${line%%:*} output=${line#*:} results=( ${results[@]} ${server} ) if [[ ${output} = *rtt* ]]; then time=`echo $output | cut -f6 -d/` if [ -n "${minfo}" ]; then info=`echo $output | cut -f2 -d:` hops=`echo $output | cut -f3 -d:` if [ -z "${info}" ]; then info=" No Info" fi else hops=`echo $output | cut -f2 -d:` fi replies=( ${replies[@]} ${time} ) [ -n "${verbose}" ] && [ -z "${minfo}" ] && printf "%-30s %-20s %s %s\n" ${server%%.*}: $time $hops [ -n "${verbose}" ] && [ -n "${minfo}" ] && printf "%-30s %-20s %-10s %s %s %s\n" ${server%%.*}: $time $hops "[$info ]" else timeouts=( ${timeouts[@]} $server ) [ -n "${verbose}" ] && printf "%-20s %s\n" ${server}: timeout fi done < <(echo "$SERVERS" | xargs -P10 -n1 -I{} sh -c "${ssh_cmd}||:") if [ -n "${replies[*]}" ]; then mean=$(printf '%.2f' $(printf '%s\n' "${replies[@]}" | datamash mean 1)) median=$(printf '%.2f' $(printf '%s\n' "${replies[@]}" | datamash median 1)) echo ${#replies[@]} servers: ${mean}ms average ${median}ms median fi uc=`(echo ${timeouts[*]} | tr ' ' '\n' | wc -l)` [ -z "${timeouts[*]}" ] || echo $uc unreachable via: ${timeouts[@]} connect=$(comm -23 <(echo "${SERVERS}" | sort) \ <(echo ${results[@]} | tr ' ' '\n' | sort)) cc=`(echo ${connect[*]} | tr ' ' '\n' | wc -l)` [ -z "${connect[*]}" ] || echo $cc ssh connection failed: ${connect[@]}