3 # $Id: ntpsweep.in,v 1.3 2007/06/24 16:55:15 kardel Exp $
7 # Copyright (C) 1999,2000 Hans Lambermont and Origin B.V.
9 # Permission to use, copy, modify and distribute this software and its
10 # documentation for any purpose and without fee is hereby granted,
11 # provided that the above copyright notice appears in all copies and
12 # that both the copyright notice and this permission notice appear in
13 # supporting documentation. This software is supported as is and without
14 # any express or implied warranties, including, without limitation, the
15 # implied warranties of merchantability and fitness for a particular
16 # purpose. The name Origin B.V. must not be used to endorse or promote
17 # products derived from this software without prior written permission.
19 # Hans Lambermont <ntpsweep@lambermont.dyndns.org>
21 require 5.0; # But actually tested on 5.004 ;)
22 use Getopt::Long; # GetOptions()
26 (my $program = $0) =~ s%.*/(.+?)(.pl)?$%$1%;
28 # Hardcoded paths/program names
29 my $ntpdate = "ntpdate";
35 my ($help, $single_host, $showpeers, $maxlevel, $strip, $askversion);
36 my $res = GetOptions("help!" => \$help,
37 "host=s" => \$single_host,
38 "peers!" => \$showpeers,
39 "maxlevel=s" => \$maxlevel,
41 "version!" => \$askversion);
48 if ($help || ((@ARGV != 1) && !$single_host)) {
50 This is $program, version $version
51 Copyright (C) 1999,2000 Hans Lambermont and Origin B.V. Disclaimer inside.
54 $program [--help|--peers|--strip <string>|--maxlevel <level>|--version] \\
55 <file>|[--host <hostname>]
58 $program prints per host given in <file> the NTP stratum level, the
59 clock offset in seconds, the daemon version, the operating system and
60 the processor. Optionally recursing through all peers.
64 Print this short help text and exit.
66 Print version ($version) and exit.
68 Specify hosts file. File format is one hostname or ip number per line.
69 Lines beginning with # are considered as comment.
71 Speficy a single host, bypassing the need for a hosts file.
73 Recursively list all peers a host synchronizes to.
74 An '= ' before a peer means a loop. Recursion stops here.
76 Traverse peers up to this level (4 is a reasonable number).
78 Strip <string> from hostnames.
81 $program myhosts.txt --strip .foo.com
82 $program --host some.host --peers --maxlevel 4
87 my $hostsfile = shift;
88 my (@hosts, @known_hosts);
89 my (%known_host_info, %known_host_peers);
94 open (HOSTS, $hostsfile) ||
95 die "$program: FATAL: unable to read $hostsfile: $!\n";
97 next if /^\s*(#|$)/; # comment/empty
104 # translate IP to hostname if possible
107 my($addr, $name, $aliases, $addrtype, $length, @addrs);
108 $addr = pack('C4', split(/\./, $ip));
109 ($name, $aliases, $addrtype, $length, @addrs) = gethostbyaddr($addr, 2);
111 # return lower case name
118 # item_in_list($item, @list): returns 1 if $item is in @list, 0 if not
120 my($item, @list) = @_;
123 return 1 if ($item eq $i);
128 sub scan_host($;$;$) {
129 my($host, $level, @trace) = @_;
132 my $daemonversion = "";
138 if (&item_in_list($host, @known_hosts)) {
142 open(NTPDATE, "$ntpdate -bd $host 2>/dev/null |") ||
143 die "Cannot open ntpdate pipe: $!\n";
145 /^stratum\s+(\d+).*$/ && do {
148 /^offset\s+([0-9.-]+)$/ && do {
154 # got answers ? If so, go on.
157 my $ntpqparams = "-c 'rv 0 processor,system,daemon_version'";
158 open(NTPQ, "$ntpq $ntpqparams $host 2>/dev/null |") ||
159 die "Cannot open ntpq pipe: $!\n";
161 /daemon_version="(.*)"/ && do {
164 /system="([^"]*)"/ && do {
167 /processor="([^"]*)"/ && do {
173 # Shorten daemon_version string.
174 $daemonversion =~ s/(;|Mon|Tue|Wed|Thu|Fri|Sat|Sun).*$//;
175 $daemonversion =~ s/version=//;
176 $daemonversion =~ s/(x|)ntpd //;
177 $daemonversion =~ s/(\(|\))//g;
178 $daemonversion =~ s/beta/b/;
179 $daemonversion =~ s/multicast/mc/;
181 # Shorten system string
182 $system =~ s/UNIX\///;
183 $system =~ s/RELEASE/r/;
184 $system =~ s/CURRENT/c/;
186 # Shorten processor string
187 $processor =~ s/unknown//;
190 # got answers ? If so, go on.
191 if ($daemonversion) {
192 # ntpq again, find out the peers this time
194 my $ntpqparams = "-pn";
195 open(NTPQ, "$ntpq $ntpqparams $host 2>/dev/null |") ||
196 die "Cannot open ntpq pipe: $!\n";
198 /^No association ID's returned$/ && do {
207 /^( |x|\.|-|\+|#|\*|o)([^ ]+)/ && do {
208 push(@peers, ip2name($2));
217 # Add scanned host to known_hosts array
218 push(@known_hosts, $host);
220 $known_host_info{$host} = sprintf("%2d %9.3f %-11s %-12s %s",
221 $stratum, $offset, substr($daemonversion,0,11),
222 substr($system,0,12), substr($processor,0,9));
224 # Stratum level 0 is consider invalid
225 $known_host_info{$host} = sprintf(" ?");
227 $known_host_peers{$host} = [@peers];
230 if ($stratum || $known_host) { # Valid or known host
231 my $printhost = ' ' x $level . $host;
232 # Shorten host string
234 $printhost =~ s/$strip//;
236 # append number of peers in brackets if requested and valid
237 if ($showpeers && ($known_host_info{$host} ne " ?")) {
238 $printhost .= " (" . @{$known_host_peers{$host}} . ")";
240 # Finally print complete host line
242 substr($printhost,0,32), $known_host_info{$host});
243 if ($showpeers && (eval($maxlevel ? $level < $maxlevel : 1))) {
247 foreach $peer (@{$known_host_peers{$host}}) {
248 if (&item_in_list($peer, @trace)) {
249 # we've detected a loop !
250 $printhost = ' ' x ($level + 1) . "= " . $peer;
251 # Shorten host string
253 $printhost =~ s/$strip//;
256 substr($printhost,0,32));
258 if (substr($peer,0,3) ne "127") {
259 &scan_host($peer, $level + 1, @trace);
264 } else { # We did not get answers from this host
265 my $printhost = ' ' x $level . $host;
266 # Shorten host string
268 $printhost =~ s/$strip//;
270 printf("%-32s ?\n", substr($printhost,0,32));
280 scan_host($host, 0, @trace);
287 push(@hosts, $single_host);
289 &read_hosts($hostsfile);
294 Host st offset(s) version system processor
295 --------------------------------+--+---------+-----------+------------+---------