#!/bin/bash # # This is a shell script which prints the ntpd or chronyd synchronisation # status, using the ntpq or chronyc program. It emulates the original # ntpstat program written in C by G. Richard Keech, which implemented a # subset of the mode6 protocol supported by ntpd. # # Copyright (C) 2016 Miroslav Lichvar # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. CHRONYC=("chronyc" "-n") NTPQ=("ntpq" "-c" "timeout 100" "-c" "raw") export LC_ALL=C parse_tracking_field() { local tracking=$1 name=$2 field field=$(echo "$tracking" | grep "^$name") echo "${field#* : }" } get_chronyd_state() { local output line disp delay local leap source address stratum distance poll output=$("${CHRONYC[@]}" tracking 2> /dev/null) || return 2 leap=$(parse_tracking_field "$output" "Leap status") case "$leap" in "Normal") leap="0";; "Insert second") leap="1";; "Delete second") leap="2";; "Not synchronised") leap="3";; esac address=$(parse_tracking_field "$output" "Reference ID") address=${address%)*} address=${address#*(} stratum=$(parse_tracking_field "$output" "Stratum") delay=$(parse_tracking_field "$output" "Root delay") delay=${delay% seconds} disp=$(parse_tracking_field "$output" "Root dispersion") disp=${disp% seconds} offset=$(parse_tracking_field "$output" "System time") offset=${offset% seconds*} distance=$(echo "$delay $disp $offset" | \ awk '{ printf "%.3f", ($1 / 2.0 + $2 + $3) * 1e3 }') if [ -n "$address" ]; then line=$("${CHRONYC[@]}" sources 2> /dev/null | \ grep " $address ") || return 3 poll=$(echo "$line" | awk '{ print $4 }') case "${line:0:1}" in "*"|"=") source="NTP server";; "#") source="reference clock";; *) source="unknown source";; esac fi echo "$leap,NTP server,$address,$stratum,$distance,$poll" } parse_rv_field() { local rv=$1 name=$2 field field=$(echo "$rv" | grep -o "$name=[^,]*") echo "${field#*=}" } get_ntpd_state() { local output syspeer_id disp delay local leap source address stratum distance poll output=$("${NTPQ[@]}" -c "rv 0" 2> /dev/null) || return 2 [[ $output == *"associd"*"status"* ]] || return 3 leap=$(parse_rv_field "$output" "leap") source=$(parse_rv_field "$output" "status" | \ awk '{ print and(rshift(strtonum($1), 8), 0x3f) }') case "$source" in 0) source="unspecified";; 1) source="atomic clock";; 2) source="VLF radio";; 3) source="HF radio";; 4) source="UHF radio";; 5) source="local net";; 6) source="NTP server";; 7) source="UDP/TIME";; 8) source="wristwatch";; 9) source="modem";; *) source="unknown source";; esac stratum=$(parse_rv_field "$output" "stratum") delay=$(parse_rv_field "$output" "rootdelay") disp=$(parse_rv_field "$output" "rootdisp") distance=$(echo "$delay $disp" | awk '{ printf "%.3f", $1 / 2.0 + $2 }') syspeer_id=$("${NTPQ[@]}" -c associations 2> /dev/null |\ grep 'sys\.peer' | awk '{ print $2 }') || return 4 output=$("${NTPQ[@]}" -c "rv $syspeer_id" 2> /dev/null) || return 5 if [ "$source" = "NTP server" ]; then address=$(parse_rv_field "$output" "srcadr") fi poll=$(parse_rv_field "$output" "hpoll") echo "$leap,$source,$address,$stratum,$distance,$poll" } max_distance="" while getopts "m:h" opt; do case $opt in m) max_distance=$OPTARG ;; *) echo "Usage: $0 [-m MAXERROR]" exit 3 ;; esac done if ! state=$(get_chronyd_state) && ! state=$(get_ntpd_state); then echo "Unable to talk to NTP daemon. Is it running?" >&2 exit 2 fi IFS=, read -r leap source address stratum distance poll <<< "$state" if [ "$leap" -ge 0 -a "$leap" -le 2 ]; then printf "synchronised to %s" "$source" if [ -n "$address" ]; then printf " (%s)" "$address" fi if [ -n "$stratum" ]; then printf " at stratum %d\n" "$stratum" else printf ", stratum unknown\n" fi if [ -n "$distance" ]; then printf " time correct to within %.0f ms" "$distance" if [ -n "$max_distance" ] && echo "$distance $max_distance" | awk '{ exit $1 <= $2 }'; then printf " (exceeded maximum of %s ms)\n" "$max_distance" status=1 else printf "\n" status=0 fi else printf "accuracy unknown\n" [ -n "$max_distance" ] && status=1 || status=0 fi else printf "unsynchronised\n" status=1 fi if [ -n "$poll" ]; then printf " polling server every %d s\n" "$[2**$poll]" else printf "poll interval unknown\n" fi exit $status