#!/usr/bin/env bash
#
# Written by Sten Spans <sten@snore.nl>
# Ported to os/x and freebsd by Nick Hilliard <nick@foobar.org>
#
# Add the following to .ssh/config to get logins to work without
# confirmation:
# --
# Host *.ring.nlnog.net
#        StrictHostKeyChecking no
#        User <yourusername>
# --
# --> 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 <numnodes>] host"
        exit 1
        ;;
    esac
done

shift $((OPTIND-1))

if [ $# -lt 1 ]; then
    echo "No arguments were given"
    echo "Usage: $(basename $0) [-6vitd] [-n <numnodes>] 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[@]}