[Git][NTPsec/ntpsec][master] In pyntpq, reslist listing is working.
Eric S. Raymond
gitlab at mg.gitlab.com
Thu Nov 3 17:18:17 UTC 2016
Eric S. Raymond pushed to branch master at NTPsec / ntpsec
Commits:
908fa090 by Eric S. Raymond at 2016-11-03T12:10:40-04:00
In pyntpq, reslist listing is working.
- - - - -
4 changed files:
- docs/mode6.txt
- ntpq/pyntpq
- pylib/packet.py
- pylib/util.py
Changes:
=====================================
docs/mode6.txt
=====================================
--- a/docs/mode6.txt
+++ b/docs/mode6.txt
@@ -29,7 +29,8 @@ include::includes/hand.txt[]
This page describes the Mode 6 protocol used to get status information
from a running ntpd and configure some of its behaviors on the fly.
The protocol is normally used by the 'ntpq' program distributed with
-the suite.
+the suite. It is fully documented here so that other clients can be
+written.
[[packet]]
== Mode 6 packet structure ==
@@ -369,7 +370,7 @@ fly).
This request is used for two purposes: to retrieve restriction lists
and to retrieve interface statistics. For the former use, the request
payload should be the string "addr_restrictions"; for the latter case,
-the request payload should be be "ifstats" of empty. Both uses
+the request payload should be be "ifstats" or empty. Both uses
require authentication. The response payload is, in both cases, a
textual varlist.
@@ -483,6 +484,18 @@ header fields, then the payload.
The cryptographic hash is normally MD5, but if ntpd is built with
OpenSSL support it is possible to use and generate SHA1 keys as well.
+== Known bugs ==
+
+The quality of legacy ntpd implementations of this protocol is poor.
+Clients should be prepared for the following quirks:
+
+* In textual varlists, value literals may have spurious trailing NULs
+ which must be stripped.
+
+* In the reslist response, tag names and values may be corrupted by
+ inserted binary garbage. Any octet with the 0x80 bit set should
+ be discarded.
+
'''''
include::includes/footer.txt[]
=====================================
ntpq/pyntpq
=====================================
--- a/ntpq/pyntpq
+++ b/ntpq/pyntpq
@@ -1253,7 +1253,7 @@ usage: ifstats
self.say(ReslistSummary.header)
self.say(("=" * ReslistSummary.width) + "\n")
for entry in entries:
- self.say(formatter.summary(entry) + "\n")
+ self.say(formatter.summary(entry))
except Mode6Exception as e:
self.warn(e.message + "\n")
return
=====================================
pylib/packet.py
=====================================
--- a/pylib/packet.py
+++ b/pylib/packet.py
@@ -690,18 +690,19 @@ class Mode6Session:
def __parse_varlist(self):
"Parse a response as a textual varlist."
- # Trim trailing NULs from the text
- response = self.response
- while response.endswith(b"\x00"):
- response = response[:-1]
+ # Strip out NULs and binary garbage from text;
+ # ntpd seems prone to generate these, especially
+ # in reslist responses.
+ response = ""
+ for c in self.response:
+ if ord(c) > 0 and ord(c) < 127:
+ response += c
response = response.rstrip()
items = []
if response:
for pair in response.split(","):
try:
- # Yes, some servers seem to ship embedded NULs.
- while pair.endswith(b"\x00"):
- pair = pair[:-1]
+ pair = pair.strip()
eq = pair.index("=")
var = pair[:eq].strip()
val = pair[eq+1:].strip()
@@ -957,14 +958,23 @@ class Mode6Session:
def __ordlist(self, listtype):
"Retrieve ordered-list data."
self.doquery(opcode=CTL_OP_READ_ORDLIST_A, qdata=listtype, auth=True)
- return self.response
+ stanzas = []
+ for (key, value) in self.__parse_varlist().items():
+ if key[-1].isdigit() and key[-2] == '.':
+ (stem, stanza) = key.split(".")
+ stanza = int(stanza)
+ if stanza > len(stanzas) - 1:
+ for i in range(len(stanzas), stanza + 1):
+ stanzas.append(collections.OrderedDict())
+ stanzas[stanza][stem] = value
+ return stanzas
def reslist(self):
"Retrieve reslist data."
- print(self.ordlist("addr_restrictions"))
+ return self.__ordlist("addr_restrictions")
def ifstats(self):
"Retrieve ifstats data."
- print(self.ordlist("ifstats"))
+ print(self.__ordlist("ifstats"))
# end
=====================================
pylib/util.py
=====================================
--- a/pylib/util.py
+++ b/pylib/util.py
@@ -7,6 +7,7 @@ import sys
import subprocess
import time
import ntp.ntpc
+import re
from ntp.packet import *
@@ -25,6 +26,11 @@ def portsplit(hostname):
return (hostname, portsuffix)
def canonicalize_dns(hostname):
+ "Canonicalize a hostname or numeric IP address."
+ # Catch garbaged hostnames in corrupted Mode 6 responses
+ m = re.match("([:.[\]]|\w)*", hostname)
+ if not m:
+ raise TypeError
(hostname, portsuffix) = portsplit(hostname)
try:
ai = socket.getaddrinfo(hostname, None, 0, 0, 0, socket.AI_CANONNAME)
@@ -217,7 +223,10 @@ class PeerSummary:
if srchost != None:
clock_name = srchost
elif self.showhostnames:
- clock_name = canonicalize_dns(srcadr)
+ try:
+ clock_name = canonicalize_dns(srcadr)
+ except TypeError:
+ return ''
else:
clock_name = srcadr
if self.wideremote and len(clock_name) > self.namewidth:
@@ -244,15 +253,19 @@ class PeerSummary:
last_sync = variables.get("rec") or variables.get("reftime")
jd = estjitter if have_jitter else estdisp
jd = " -" if jd >= 999 else ("%7.3f" % jd)
- line += (
- " %2ld %c %4.4s %4.4s %3lo %7.3f %8.3f %s\n" % \
- (variables.get("stratum", 0),
- ptype,
- PeerSummary.prettyinterval(now if last_sync is None else int(now - ntp.ntpc.lfptofloat(last_sync))),
- PeerSummary.prettyinterval(poll_sec),
- reach, estdelay, estoffset,
- jd))
- return line
+ try:
+ line += (
+ " %2ld %c %4.4s %4.4s %3lo %7.3f %8.3f %s\n" % \
+ (variables.get("stratum", 0),
+ ptype,
+ PeerSummary.prettyinterval(now if last_sync is None else int(now - ntp.ntpc.lfptofloat(last_sync))),
+ PeerSummary.prettyinterval(poll_sec),
+ reach, estdelay, estoffset,
+ jd))
+ return line
+ except TypeError:
+ # This can happen when ntpd ships a corrupt varlist
+ return ''
class MRUSummary:
"Reusable class for MRU entry summary generation."
@@ -281,11 +294,53 @@ class MRUSummary:
else:
rscode = '.'
(dns, port) = portsplit(entry.addr)
- if self.showhostnames:
- dns = canonicalize_dns(dns)
- stats += " %4hx %c %d %d %6d %5s %s" % \
- (entry.rs, rscode, PKT_MODE(entry.mv), PKT_VERSION(entry.mv),
- entry.ct, port[1:], dns)
- return stats
+ try:
+ if self.showhostnames:
+ dns = canonicalize_dns(dns)
+ stats += " %4hx %c %d %d %6d %5s %s" % \
+ (entry.rs, rscode,
+ PKT_MODE(entry.mv), PKT_VERSION(entry.mv),
+ entry.ct, port[1:], dns)
+ return stats
+ except TypeError:
+ # This can happen when ntpd ships a corrupt varlist
+ return ''
+
+class ReslistSummary:
+ "Reusable class for reslist entry summary generation."
+ header = """\
+ hits addr/prefix or addr mask
+ restrictions
+"""
+ width = 72
+ @staticmethod
+ def __getPrefix(mask):
+ if not mask:
+ prefix = ''
+ if ':' in mask:
+ sep = ':'
+ base = 16
+ else:
+ sep = '.'
+ base = 10
+ prefix = sum([bin(int(x, base)).count('1') for x in mask.split(sep) if x])
+ return '/' + str(prefix)
+ def summary(self, variables):
+ hits = variables.get("hits", "?")
+ address = variables.get("addr", "?")
+ mask = variables.get("mask", "?")
+ if address == '?' or mask == '?':
+ return ''
+ address += ReslistSummary.__getPrefix(mask)
+ flags = variables.get("flags", "?")
+ # reslist reponses are often corrupted
+ s = "%10s %s\n %s\n" % (hits, address, flags)
+ # Throw away corrupted entries. This is a shim - we really
+ # want to make ntpd stop generating garbage
+ for c in s:
+ if not c.isalnum() and not c in "/.: \n":
+ print("Failed on %s" % repr(c))
+ return ''
+ return s
# end
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/commit/908fa0903b43e3af0e020ec4e3581d593570e9bc
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ntpsec.org/pipermail/vc/attachments/20161103/f430bbfb/attachment.html>
More information about the vc
mailing list