2 # -*- coding: utf-8 -*-
4 ntptrace - trace peers of an NTP server
6 Usage: ntptrace [-n | --numeric] [-m number | --max-hosts=number]
7 [-r hostname | --host=hostname] [--help | --more-help]
11 See the manual page for details.
14 # Copyright the NTPsec project contributors
16 # SPDX-License-Identifier: BSD-2-Clause
18 from __future__
import print_function
27 except ImportError as e
:
29 "ntptrace: can't find Python NTP library.\n")
30 sys
.stderr
.write("%s\n" % e
)
35 info
= ntp_read_vars(0, [], host
)
36 if info
is None or 'stratum' not in info
:
39 info
['offset'] = round(float(info
['offset']) / 1000, 6)
40 info
['syncdistance'] = \
41 (float(info
['rootdisp']) + (float(info
['rootdelay']) / 2)) / 1000
46 def get_next_host(peer
, host
):
47 info
= ntp_read_vars(peer
, ["srcadr"], host
)
53 def ntp_read_vars(peer
, vars, host
):
54 obsolete
= {'phase': 'offset',
55 'rootdispersion': 'rootdisp'}
61 outvars
= {}.fromkeys(vars)
64 outvars
['status_line'] = {}
66 cmd
= ["ntpq", "-n", "-c", "rv %s %s" % (peer
, ",".join(vars))]
71 # sadly subprocess.check_output() is not in Python 2.6
72 proc
= subprocess
.Popen(
74 stdout
=subprocess
.PIPE
,
75 stderr
=subprocess
.STDOUT
)
76 out
= proc
.communicate()[0]
77 output
= out
.decode('utf-8').splitlines()
78 except subprocess
.CalledProcessError
as e
:
79 print("Could not start ntpq: %s" % e
.output
, file=sys
.stderr
)
82 print("Could not start ntpq: %s" % e
.strerror
, file=sys
.stderr
)
86 if re
.search(r
'Connection refused', line
):
89 match
= re
.search(r
'^asso?c?id=0 status=(\S{4}) (\S+), (\S+),', line
,
92 outvars
['status_line']['status'] = match
.group(1)
93 outvars
['status_line']['leap'] = match
.group(2)
94 outvars
['status_line']['sync'] = match
.group(3)
96 iterator
= re
.finditer(r
'(\w+)=([^,]+),?\s?', line
)
97 for match
in iterator
:
100 val
= re
.sub(r
'^"([^"]+)"$', r
'\1', val
)
103 if do_all
or key
in outvars
:
109 usage
= r
"""ntptrace - trace peers of an NTP server
110 USAGE: ntptrace [-<flag> [<val>] | --<name>[{=| }<val>]]... [host]
112 -n, --numeric Print IP addresses instead of hostnames
113 -m, --max-hosts=num Maximum number of peers to trace
114 -r, --host=str Single remote host
115 -?, --help Display usage information and exit
116 --more-help Pass the extended usage text through a pager
117 -V, --version Output version information and exit
119 Options are specified by doubled hyphens and their name or by a single
120 hyphen and the flag character.""" + "\n"
122 bin_ver
= "ntpsec-@NTPSEC_VERSION_EXTENDED@"
123 ntp
.util
.stdversioncheck(bin_ver
)
126 (options
, arguments
) = getopt
.getopt(
127 sys
.argv
[1:], "m:nr:?V",
128 ["help", "host=", "max-hosts=", "more-help", "numeric", "version"])
129 except getopt
.GetoptError
as err
:
130 sys
.stderr
.write(str(err
) + "\n")
137 for (switch
, val
) in options
:
138 if switch
== "-m" or switch
== "--max-hosts":
139 errmsg
= "Error: -m parameter '%s' not a number\n"
140 maxhosts
= ntp
.util
.safeargcast(val
, int, errmsg
, usage
)
141 elif switch
== "-n" or switch
== "--numeric":
143 elif switch
== "-r" or switch
== "--host":
145 elif switch
== "-?" or switch
== "--help" or switch
== "--more-help":
146 print(usage
, file=sys
.stderr
)
148 elif switch
== "-V" or switch
== "--version":
149 print("ntptrace %s" % ntp
.util
.stdversion())
160 info
= get_info(host
)
166 host
= ntp
.util
.canonicalize_dns(host
)
168 print("%s: stratum %d, offset %f, synch distance %f" %
169 (host
, int(info
['stratum']), info
['offset'], info
['syncdistance']),
171 if int(info
['stratum']) == 1:
172 print(", refid '%s'" % info
['refid'], end
='')
175 if (int(info
['stratum']) == 0 or int(info
['stratum']) == 1 or
176 int(info
['stratum']) == 16):
179 if re
.search(r
'^127\.127\.\d{1,3}\.\d{1,3}$', info
['refid']):
182 if hostcount
== maxhosts
:
185 next_host
= get_next_host(info
['peer'], host
)
187 if next_host
is None:
189 if re
.search(r
'^127\.127\.\d{1,3}\.\d{1,3}$', next_host
):