[Git][NTPsec/ntpsec][master] 2 commits: Add readvars() method to ntp.packet.session class.

Eric S. Raymond gitlab at mg.gitlab.com
Fri Sep 9 12:05:45 UTC 2016


Eric S. Raymond pushed to branch master at NTPsec / ntpsec


Commits:
f49f031b by Eric S. Raymond at 2016-09-09T05:07:46-04:00
Add readvars() method to ntp.packet.session class.

A convenience method for fetching the host's system variables.

- - - - -
1a7fed9b by Eric S. Raymond at 2016-09-09T08:04:51-04:00
Translate ntpsweep to Python and promote it.

- - - - -


9 changed files:

- + ntpsweep/ntp
- + ntpsweep/ntpsweep
- util/ntpsweep/ntpsweep-man.txt → ntpsweep/ntpsweep-man.txt
- pylib/packet.py
- + pylib/util.py
- util/README
- − util/ntpsweep/ntpsweep
- util/wscript
- wscript


Changes:

=====================================
ntpsweep/ntp
=====================================
--- /dev/null
+++ b/ntpsweep/ntp
@@ -0,0 +1 @@
+../pylib/
\ No newline at end of file


=====================================
ntpsweep/ntpsweep
=====================================
--- /dev/null
+++ b/ntpsweep/ntpsweep
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+"""
+ntpsweep - Print various information about given ntp servers.
+USAGE: ntpsweep [ -<flag> [<val>] | --<name>[{=| }<val>] ]... [hostfile]
+
+    -l, --host-list=str        Host to execute actions on
+                                   - may appear multiple times
+    -p, --peers                Recursively list all peers a host synchronizes to
+    -m, --maxlevel=num         Traverse peers up to this level (4 is a reasonable number)
+    -s, --strip=str            Strip this string from hostnames
+
+Options are specified by doubled hyphens and their name or by a single
+hyphen and the flag character.
+"""
+# SPDX-License-Identifier: BSD-3-clause
+#
+# Python translation by ESR of a Perl script written long ago by
+# Hans Lambermont <ntpsweep at lambermont.dyndns.org>
+#
+# It is unclear how useful this will be under modern conditions (e.g. most
+# hosts refuse to be queried, and requests to them will just time out).
+
+from __future__ import print_function
+
+import os, sys, getopt
+import ntp.packet, ntp.util
+
+def peers(host):
+    "Return a list of peer IP addrs for a specified host, None if query failed."
+    try:
+        with os.popen("ntpq -npw " + host) as rp:
+            hostlines = rp.readlines[2:]	# Drop display header
+            # Strip tally mark from first field
+            return [ln.split()[0][1:] for ln in hostlines if ln[0] in " x.-+#*o"]
+    except OSError:
+        return []
+
+def scan_host(host, level):
+    stratum = 0
+    offset = 0
+    daemonversion = ""
+    system = ""
+    processor = ""
+    peers = []
+    known_host = False
+
+    if host in known_host_info:
+        known_host = True
+    else:
+	session = ntp.packet.ntpq_session()
+        session.openhost(host)
+        sysvars = session.readvars()
+
+        # got answers ? If so, go on.
+        if type(sysvars) == type({}):
+            stratum       = sysvars['stratum']
+            offset        = sysvars['offset']
+            daemonversion = sysvars['version']
+            system        = sysvars['system']
+            processor     = sysvars['processor']
+
+            # Shorten daemon_version string.
+            #daemonversion =~ s/(|Mon|Tue|Wed|Thu|Fri|Sat|Sun).*$//
+            daemonversion = daemonversion.replace("version=", "")
+            daemonversion = daemonversion.replace("ntpd ", "")
+            daemonversion = daemonversion.replace("(", "").replace(")", "")
+            daemonversion = daemonversion.replace("beta", "b")
+            daemonversion = daemonversion.replace("multicast", "mc")
+
+            # Shorten system string. Note, the assumptions here
+            # are very old, reflecting ancient big-iron Unixes
+            system = system.replace("UNIX/", "")
+            system = system.replace("RELEASE", "r")
+            system = system.replace("CURRENT", "c")
+
+            # Shorten processor string
+            processor = processor.replace("unknown", "")
+
+        # got answers ? If so, go on.
+        if daemonversion and recurse:
+            # Consider doing something more intelligent on failure
+            # than simply returning an empty list.  Though it might
+            # be the right thing to do under modern conditions in
+            # which most hosts will refuse to be queried.
+            known_host_peers[host] = ntp_peers(host)
+
+        # Collect info on host
+        if stratum:
+            known_host_info[host] = "%2d %9.3f %-11s %-12s %s"  \
+                                    % (stratum, offset, daemonversion[:11],
+                                       system[:12], processor[0:9])
+        else:
+            # Stratum level 0 is considered invalid
+            known_host_info[host] = " ?"
+
+    if stratum or known_host: # Valid or known host
+        printhost = (' ' * level) + (ntp.util.canonicalize_dns(host) or host)
+        # Shorten host string
+        if strip:
+            printhost = printhost.replace(strip, "")
+        # append number of peers in brackets if requested and valid
+        if recurse and known_host_info[host] != " ?" and host in known_host_peers:
+            printhost += " (%d)" % len(known_host_peers[host])
+        # Finally print complete host line
+        print("%-32s %s" % (printhost[:32], known_host_info[host]))
+        if recurse and (maxlevel == 0 or level < maxlevel):
+            trace.append(host)
+            # Loop through peers
+            for peer in known_host_peers[host]:
+                if peer in trace:
+                    # we've detected a loop!
+                    printhost = (' ' * (level + 1)) + "= " + peer
+                    # Shorten host string
+                    if strip:
+                        printhost = printhost.replace(strip, "")
+                    print("%-32s" % printhost[:32])
+                else:
+                    # FIXME: Ugh! Magic-address assumption.
+                    # Needed to deal with peers running legacy NTP.
+                    # Might cause problems in the future.  First
+                    # part of the guard is an attempt to skip
+                    # NTPsec-style clock IDs.
+                    if peer[0].isdigit() and not peer.startswith("127"):
+                        scan_host(peer, level + 1)
+    else: # We did not get answers from this host
+        printhost = (' ' * level) + (ntp.util.canonicalize_dns(host) or host)
+        if strip:
+            printhost = printhost.replace(strip, "")
+        print("%-32s  ?" % printhost[:32])
+
+if __name__ == '__main__':
+    try:
+        (options, arguments) = getopt.getopt(
+            sys.argv[1:], "h:l:m:ps:?",
+            ["host=", "host-list=", "maxlevel=", "peers", "strip="])
+    except getopt.GetoptError as err:
+        sys.stderr.write(str(err) + "\n")
+        raise SystemExit(1)
+    hostlist = []
+    maxlevel = 1
+    recurse = False
+    strip = ""
+    for (switch, val) in options:
+        if switch == "-h" or switch == "--host":
+            hostlist = [val]
+        elif switch == "-l" or switch == "--host-list":
+            hostlist = val.split(",")
+        elif switch == "-m" or switch == "--maxlevel":
+            maxlevel = int(val)
+        elif switch == "-p" or switch == "--peers":
+            recurse = True
+        elif switch == "-s" or switch == "--strip":
+            strip = val
+        elif switch == "-?" or switch == "--help":
+            print(__doc__, file=sys.stderr)
+            raise SystemExit(0)
+
+    if arguments:
+        hostlist += [ln.strip() for ln in open(arguments[0]).readlines()]
+
+    if not hostlist:
+        hostlist = ["localhost"]
+
+    # Print header
+    print("""\
+Host                             st offset(s) version     system       processor
+--------------------------------+--+---------+-----------+------------+---------\
+""")
+
+    known_host_info = {}
+    known_host_peers = {}
+    trace = []
+    for host in hostlist:
+          scan_host(host, 0)
+
+    sys.exit(0);
+
+# end


=====================================
util/ntpsweep/ntpsweep-man.txt → ntpsweep/ntpsweep-man.txt
=====================================
--- a/util/ntpsweep/ntpsweep-man.txt
+++ b/ntpsweep/ntpsweep-man.txt
@@ -13,12 +13,13 @@ ntpsweep - print various informations about given NTP servers
 seconds, the daemon version, the operating system and the processor.
 Optionally recursing through all peers.
 
-`ntpsweep` relies on `ntpq` and `ntpdig` to probe servers.  This depends
-on the remote host's _restrict_ configuration allowing this.
+If no hosts are specified, `ntpsweep` reports on localhost.
 
-`ntpsweep` is a perl script, and you may need to run it as:
-
-+perl -I <directory above NTP/Util.pm> ntpsweep+
+`ntpsweep` relies on `ntpq` and Mode 6 queries to probe servers.  This
+depends on the remote host's _restrict_ configuration allowing
+queries. Nowadays effectively all public hosts set _noquery_, so this
+script is unlikely to be useful unless you have multiple specially-
+configured timeservers on a LAN.
 
 == OPTIONS ==
 
@@ -40,9 +41,7 @@ appear multiple times.
   Strip this string from hostnames.
 
 +-h+ string, +--host+=_string_::
-  Specify a single host.
-+
-_NOTE: THIS OPTION IS DEPRECATED_
+  Specify a single host.  Deprecated option for backwards compatibility.
 
 == EXIT STATUS ==
 


=====================================
pylib/packet.py
=====================================
--- a/pylib/packet.py
+++ b/pylib/packet.py
@@ -503,5 +503,30 @@ class ntpq_session:
             break
         # Return None on success, otherwise an error string
 	return res;
-
+    def readvars(self):
+        "Read system vars from the host as a dict, or return an error string."
+        self.doquery(opcode=CTL_OP_READVAR, quiet=True)
+        if self.response.startswith("*"):
+            return self.response
+        else:
+            response = self.response
+            # Trim trailing NULs from the text
+            while response.endswith("\x00"):
+                response = response[:-1]
+            response = response.rstrip()
+            items = []
+            for pair in response.split(","):
+                (var, val) = pair.split("=")
+                var = var.strip()
+                val = val.strip()
+                try:
+                    val = int(val)
+                except ValueError:
+                    try:
+                        val = float(val)
+                    except ValueError:
+                        if val[0] == '"' and val[-1] == '"':
+                            val = val[1:-1]
+                items.append((var, val))
+            return dict(items)
 # end


=====================================
pylib/util.py
=====================================
--- /dev/null
+++ b/pylib/util.py
@@ -0,0 +1,20 @@
+# Common utility functions
+
+from __future__ import print_function
+
+import socket
+
+def canonicalize_dns(hostname):
+    try:
+        ai = socket.getaddrinfo(hostname, None, 0, 0, 0, socket.AI_CANONNAME)
+    except socket.gaierror as e:
+        print('getaddrinfo failed: %s' % e.strerr, file=sys.stderr)
+        raise SystemExit(1)
+    (family, socktype, proto, canonname, sockaddr) = ai[0]
+    try:
+        name = socket.getnameinfo(sockaddr, socket.NI_NAMEREQD)
+    except socket.gaierror:
+        return canonname.lower()
+    return name[0].lower()
+
+# end


=====================================
util/README
=====================================
--- a/util/README
+++ b/util/README
@@ -32,14 +32,6 @@ ntpdate::	Wrapper script to maintain compatibility. Maps options
 		to ntpdig and calls it.
 		Tested: 20160226
 
-ntpsweep::	prints per host given in <file> the NTP stratum level, the
-		clock offset in seconds, the daemon version, the operating
-		system and the processor.
-		Needs -I <directory above NTP/Util.pm>
-		May not work on today's Internet, because servers do not
-		wish to be interrogated.
-		Tested: 20160228
-
 ntpver::	Simple script using ntpq to print out the suite version.
 		Tested: 20160226
 


=====================================
util/ntpsweep/ntpsweep deleted
=====================================
--- a/util/ntpsweep/ntpsweep
+++ /dev/null
@@ -1,251 +0,0 @@
-#!/usr/bin/env perl
-#
-# Copyright (C) 1999,2000 Hans Lambermont and Origin B.V.
-#
-# SPDX-License-Identifier: BSD-3-clause
-#
-# Hans Lambermont <ntpsweep at lambermont.dyndns.org>
-
-package ntpsweep;
-use 5.006_000;
-use strict;
-use NTP::Util qw(do_dns ntp_read_vars ntp_peers ntp_ntpdig_line);
-use Getopt::Long qw(GetOptionsFromArray);
-Getopt::Long::Configure(qw(no_auto_abbrev no_ignore_case_always));
-
-my $usage;
-
-sub usage {
-    my ($ret) = @_;
-    print STDERR $usage;
-    exit $ret;
-}
-
-sub paged_usage {
-    my ($ret) = @_;
-    my $pager = $ENV{PAGER} || '(less || more)';
-
-    open STDOUT, "| $pager" or die "Can't fork a pager: $!";
-    print $usage;
-
-    exit $ret;
-}
-
-sub processOptions {
-    my $args = shift;
-
-    my $opts = {
-        'host-list' => [],
-        'peers' => '',
-        'maxlevel' => '',
-        'strip' => '',
-        'host' => '',
-        'help' => '', 'more-help' => ''
-    };
-    my $argument = '[hostfile]';
-    my $ret = GetOptionsFromArray($args, $opts, (
-        'host-list|l=s', 'peers|p', 'maxlevel|m=i',
-        'strip|s=s', 'host|h=s',
-        'help|?', 'more-help'));
-
-    $usage = <<'USAGE';
-ntpsweep - Print various informations about given ntp servers.
-USAGE: ntpsweep [ -<flag> [<val>] | --<name>[{=| }<val>] ]... [hostfile]
-
-    -l, --host-list=str          Host to execute actions on
-                                   - may appear multiple times
-    -p, --peers                  Recursively list all peers a host synchronizes to
-    -m, --maxlevel=num           Traverse peers up to this level (4 is a reasonable number)
-    -s, --strip=str              Strip this string from hostnames
-    -?, --help                   Display usage information and exit
-        --more-help              Pass the extended usage text through a pager
-
-Options are specified by doubled hyphens and their name or by a single
-hyphen and the flag character.
-USAGE
-
-    usage(0)       if $opts->{'help'};
-    paged_usage(0) if $opts->{'more-help'};
-    $_[0] = $opts;
-    return $ret;
-}
-
-(my $program = $0) =~ s%.*/(.+?)(.pl)?$%$1%;
-my ($showpeers, $maxlevel, $strip);
-my (%known_host_info, %known_host_peers);
-
-exit run(@ARGV) unless caller;
-
-sub run {
-    my $opts;
-    if (!processOptions(\@_, $opts) || 
-        (((@_ != 1) && !$opts->{host} && !@{$opts->{'host-list'}}))) {
-        usage(1);
-    };
-
-    # no STDOUT buffering
-    $| = 1;
-    ($showpeers, $maxlevel, $strip) = 
-        ($opts->{peers}, $opts->{maxlevel}, $opts->{strip});
-
-    my $hostsfile = shift;
-
-    # Main program
-
-    my @hosts;
-
-    if ($opts->{host}) {
-        push @hosts, $opts->{host};
-    }
-    else {
-        @hosts = read_hosts($hostsfile) if $hostsfile;
-        push @hosts, @{$opts->{'host-list'}};
-    }
-
-    # Print header
-    print <<EOF;
-Host                             st offset(s) version     system       processor
---------------------------------+--+---------+-----------+------------+---------
-EOF
-
-    %known_host_info = ();
-    %known_host_peers = ();
-    scan_hosts(@hosts);
-
-    return 0;
-}
-
-sub scan_hosts {
-    my (@hosts) = @_;
-
-    my $host;
-    for $host (@hosts) {
-        scan_host($host, 0, $host => 1);
-    }
-}
-
-sub read_hosts {
-    my ($hostsfile) = @_;
-    my @hosts;
-
-    open my $hosts, $hostsfile 
-        or die "$program: FATAL: unable to read $hostsfile: $!\n";
-
-    while (<$hosts>) {
-        next if /^\s*(#|$)/; # comment/empty
-        chomp;
-        push @hosts, $_;
-    }
-
-    close $hosts;
-    return @hosts;
-}
-
-sub scan_host {
-    my ($host, $level, %trace) = @_;
-    my $stratum = 0;
-    my $offset = 0;
-    my $daemonversion = "";
-    my $system = "";
-    my $processor = "";
-    my @peers;
-    my $known_host = 0;
-
-    if (exists $known_host_info{$host}) {
-        $known_host = 1;
-    }
-    else {
-        ($offset, $stratum) = ntp_ntpdig_line($host);
-
-        # got answers ? If so, go on.
-        if ($stratum) {
-            my $vars = ntp_read_vars(0, [qw(processor system daemon_version)], $host) || {};
-            $daemonversion = $vars->{daemon_version};
-            $system        = $vars->{system};
-            $processor     = $vars->{processor};
-
-            # Shorten daemon_version string.
-            $daemonversion =~ s/(;|Mon|Tue|Wed|Thu|Fri|Sat|Sun).*$//;
-            $daemonversion =~ s/version=//;
-            $daemonversion =~ s/(x|)ntpd //;
-            $daemonversion =~ s/(\(|\))//g;
-            $daemonversion =~ s/beta/b/;
-            $daemonversion =~ s/multicast/mc/;
-
-            # Shorten system string
-            $system =~ s/UNIX\///;
-            $system =~ s/RELEASE/r/;
-            $system =~ s/CURRENT/c/;
-
-            # Shorten processor string
-            $processor =~ s/unknown//;
-        }
-
-        # got answers ? If so, go on.
-        if ($daemonversion) {
-            if ($showpeers) {
-                my $peers_ref = ntp_peers($host);
-                my @peers_tmp = @$peers_ref;
-                for (@peers_tmp) {
-                    $_->{remote} =~ s/^(?: |x|\.|-|\+|#|\*|o)([^ ]+)/$1/;
-                    push @peers, $_->{remote};
-                }
-            }
-        }
-
-        # Add scanned host to known_hosts array
-        #push @known_hosts, $host;
-        if ($stratum) {
-            $known_host_info{$host} = sprintf "%2d %9.3f %-11s %-12s %s",
-                $stratum, $offset, (substr $daemonversion, 0, 11),
-                (substr $system, 0, 12), (substr $processor, 0, 9);
-        }
-        else {
-            # Stratum level 0 is consider invalid
-            $known_host_info{$host} = " ?";
-        }
-        $known_host_peers{$host} = [@peers];
-    }
-
-    if ($stratum || $known_host) { # Valid or known host
-        my $printhost = ' ' x $level . (do_dns($host) || $host);
-        # Shorten host string
-        if ($strip) {
-            $printhost =~ s/$strip//;
-        }
-        # append number of peers in brackets if requested and valid
-        if ($showpeers && ($known_host_info{$host} ne " ?")) {
-            $printhost .= " (" . @{$known_host_peers{$host}} . ")";
-        }
-        # Finally print complete host line
-        printf "%-32s %s\n",
-            (substr $printhost, 0, 32), $known_host_info{$host};
-        if ($showpeers && ($maxlevel ? $level < $maxlevel : 1)) {
-            $trace{$host} = 1;
-            # Loop through peers
-            foreach my $peer (@{$known_host_peers{$host}}) {
-                if (exists $trace{$peer}) {
-                    # we've detected a loop !
-                    $printhost = ' ' x ($level + 1) . "= " . $peer;
-                    # Shorten host string
-                    $printhost =~ s/$strip// if $strip;
-                    printf "%-32s\n", substr $printhost, 0, 32;
-                } else {
-                    if ((substr $peer, 0, 3) ne "127") {
-                        scan_host($peer, $level + 1, %trace);
-                    }
-                }
-            }
-        }
-    }
-    else { # We did not get answers from this host
-        my $printhost = ' ' x $level . (do_dns($host) || $host);
-        $printhost =~ s/$strip// if $strip;
-        printf "%-32s  ?\n", substr $printhost, 0, 32;
-    }
-}
-
-END { close STDOUT };
-
-1;
-__END__


=====================================
util/wscript
=====================================
--- a/util/wscript
+++ b/util/wscript
@@ -18,4 +18,3 @@ def build(ctx):
 			install_path    = False
 		)
 
-	ctx.manpage(1, "ntpsweep/ntpsweep-man.txt")


=====================================
wscript
=====================================
--- a/wscript
+++ b/wscript
@@ -148,15 +148,12 @@ def build(ctx):
 	ctx.recurse("util")
 	ctx.recurse("tests")
 
-	# Some of these presently fail because they require a Perl
-	# module that's never installed. Awkwardly, their man pages do
-	# get installed. There is a note about this mess in INSTALL.
 	scripts = [
 		"ntpleapfetch/ntpleapfetch",
 		"ntpstats/ntpviz",
 		"ntptrace/ntptrace",
 		"ntpwait/ntpwait",
-		#"util/ntpsweep/ntpsweep",
+		"ntpsweep/ntpsweep",
 	]
 
 	ctx(
@@ -171,7 +168,7 @@ def build(ctx):
 	ctx.manpage(1, "ntptrace/ntptrace-man.txt")
 	ctx.manpage(1, "ntpstats/ntpviz-man.txt")
 	ctx.manpage(8, "ntpwait/ntpwait-man.txt")
-	#ctx.manpage(1, "util/ntpsweep/ntpsweep-man.txt")
+	ctx.manpage(1, "ntpsweep/ntpsweep-man.txt")
 
 
 	# Skip running unit tests on a cross compile build



View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/839e421a5d7a6d7429d7eaa2573f72b826519076...1a7fed9b2d0819da7a9fadbc1e810b49b55d786f
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ntpsec.org/pipermail/vc/attachments/20160909/1b159716/attachment.html>


More information about the vc mailing list