From 09aa44cf2e8a48d176fc0635258ad8ff997b41c0 Mon Sep 17 00:00:00 2001 From: Packit Date: Sep 16 2020 08:07:29 +0000 Subject: ntpstat-0.5 base --- diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..13857e2 --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..246b1d7 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +NAME = ntpstat + +prefix = /usr/local +bindir = $(prefix)/bin +mandir = $(prefix)/share/man +man1dir = $(mandir)/man1 + +all: + +install: + mkdir -p $(bindir) $(man1dir) + install $(NAME) $(bindir) + install -p -m 644 $(NAME).1 $(man1dir) diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..97a1c5b --- /dev/null +++ b/NEWS @@ -0,0 +1,9 @@ +0.5 (2017-03-20) +- add -m option to specify maximum acceptable error + +0.4 (2017-07-11) +- include system offset in reported distance with chrony +- fix parsing of delay and dispersion from chronyc output + +0.3 (2017-03-15) +- initial release diff --git a/README b/README new file mode 100644 index 0000000..4bcd87d --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +ntpstat 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. diff --git a/ntpstat b/ntpstat new file mode 100755 index 0000000..04b7e7b --- /dev/null +++ b/ntpstat @@ -0,0 +1,188 @@ +#!/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 diff --git a/ntpstat.1 b/ntpstat.1 new file mode 100644 index 0000000..c38b4ea --- /dev/null +++ b/ntpstat.1 @@ -0,0 +1,40 @@ +.TH NTPSTAT 1 "" "ntpstat" +.SH NAME +ntpstat \- Print NTP synchronisation status + +.SH SYNOPSIS +\fBntpstat\fR [\fB-m\fR \fIMAXERROR\fR] + +.SH DESCRIPTION +\fBntpstat\fR is a script which prints a brief summary of the system clock's +synchronisation status when the \fBntpd\fR or \fBchronyd\fR daemon is running. +It prints the time source (NTP server or reference clock) to which the system +clock is currently synchronised, its stratum, how often is the server polled, +and the maximum estimated error of the clock. The script uses the \fBntpq\fR or +\fBchronyc\fR program to obtain the information from the daemon. + +Following the NTPv4 specification (RFC 5905), if the time source becomes +unreachable and there are no other sources that could be selected, the status +of the clock will still be \*(lqsynchronised\*(rq, but the maximum error will +be slowly increasing. + +\fBntpstat\fR exits with a status of 0 if the clock is synchronised, 1 if the +clock is not synchronised, 2 if the status could not be determined, e.g. +when the daemon is not running, or 3 if an invalid command-line option was +specified. + +.SH OPTIONS +.TP 8 +\fB-m\fR \fIMAXERROR\fR +Specify a maximum acceptable error of the clock in milliseconds. If the +clock is synchronised, but its maximum estimated error is larger than +\fIMAXERROR\fR, or is unknown, \fBntpstat\fR will exit with a status of 1. +.TP 8 +\fB-h\fR +Print a help message. + +.SH SEE ALSO +.BR ntpd (8), +.BR ntpq (8), +.BR chronyd (8), +.BR chronyc (1)