[Git][NTPsec/ntpsec][master] Significant coverage and clairty improvments on ntpsnmpd
Ian Bruene
gitlab at mg.gitlab.com
Thu Nov 23 20:28:43 UTC 2017
Ian Bruene pushed to branch master at NTPsec / ntpsec
Commits:
feb97562 by Ian Bruene at 2017-11-23T14:28:23-06:00
Significant coverage and clairty improvments on ntpsnmpd
- - - - -
1 changed file:
- ntpclients/ntpsnmpd
Changes:
=====================================
ntpclients/ntpsnmpd
=====================================
--- a/ntpclients/ntpsnmpd
+++ b/ntpclients/ntpsnmpd
@@ -201,21 +201,22 @@ class DataSource: # This may be broken up in future to be less NTP-specific
{1:
node(None, None, True,
# ntpAssocStatInPkts Counter32
- {1: node(self.cbr_assocStatInPkts,
- None, True, None),
+ {1: node(None, None, False,
+ self.sub_assocStatInPkts),
# ntpAssocStatOutPkts Counter32
- 2: node(self.cbr_assocStatOutPkts,
- None, True, None),
+ 2: node(None, None, False,
+ self.sub_assocStatOutPkts),
# ntpAssocStatProtocolError
# Counter32
- 3: node(self.cbr_assocStatProtoErr,
- None, True, None)})})}),
+ 3: node(None, None, False,
+ self.sub_assocStatProtoErr)})})}),
# ntpEntControl
4:
node(None, None, True,
# ntpEntHeartbeatInterval unit32
{1: node(self.cbr_entHeartbeatInterval,
- None, True, None),
+ self.cbw_entHeartbeatInterval,
+ True, None),
# ntpEntNotifBits BITS {...}
2: node(self.cbr_entNotifBits, self.cbw_entNotifBits,
True, None)}),
@@ -245,13 +246,15 @@ class DataSource: # This may be broken up in future to be less NTP-specific
3: node(None, None, True, None)})})}
self.session = ntp.packet.ControlSession()
self.session.openhost(DEFHOST) # only local for now
- # Cache so we don't hammer ntpd, default 1 second timeout (from a hat)
+ # Cache so we don't hammer ntpd, default 1 second timeout
+ # timeout default pulled from a hat
self.cache = ntp.util.Cache(1)
# The undo system is only for the last operation
self.inSetP = False # Are we currently in the set procedure?
self.setVarbinds = [] # Varbind of the current set operation
self.setHandlers = [] # Handlers for commit/undo/cleanup of set
self.setUndoData = [] # Previous values for undoing
+ self.heartbeatInterval = 0
self.sentNotifications = 0
# Notify bits, these should be saved to disk
# Also they currently have no effect
@@ -284,7 +287,7 @@ class DataSource: # This may be broken up in future to be less NTP-specific
else:
return None, None, None
- # These exist instead of just getOID_core for clearer semantics
+ # These exist instead of just using getOID_core for clearer semantics
def getOID(self, searchoid, returnGenerator=False):
"Get the requested OID"
return self.getOID_core(False, searchoid, returnGenerator)
@@ -358,34 +361,68 @@ class DataSource: # This may be broken up in future to be less NTP-specific
# Uinteger32
return ax.Varbind(ax.VALUE_GAUGE32, oid, 42)
- def cbr_timePrecision(self, oid, write=None):
- data = self.session.readvar(0, ["precision"])
- return ax.Varbind(ax.VALUE_INTEGER, oid, data["precision"])
+ def cbr_timePrecision(self, oid):
+ return self.readCallbackSkeletonSimple(oid, "precision",
+ ax.VALUE_INTEGER)
def cbr_timeDistance(self, oid): # DUMMY
# Displaystring
return ax.Varbind(ax.VALUE_OCTET_STR, oid, "foo")
- #############################
+ # Blank: ntpEntStatus
- def cbr_statusCurrentMode(self, oid): # DUMMY
+ def cbr_statusCurrentMode(self, oid): # DUMMY, partially implemented
# Range of integers
- return ax.Varbind(ax.VALUE_INTEGER, oid, 15)
+ try:
+ # Don't care about the data, this is a ploy to the the rstatus
+ self.session.readvar(0, ["stratum"])
+ except ntp.packet.ControlException as e:
+ if e.message == ntp.packet.SERR_SOCKET:
+ # Can't connect, ntpd probably not running
+ return ax.Varbind(ax.VALUE_INTEGER, oid, 1)
+ else:
+ raise e
+ rstatus = self.session.rstatus # a ploy to get the system status
+ source = ntp.control.CTL_SYS_SOURCE(rstatus)
+ if source == ntp.control.CTL_SST_TS_UNSPEC:
+ mode = 2 # Not yet synced
+ elif False:
+ mode = 3 # No reference configured
+ elif source == ntp.control.CTL_SST_TS_LOCAL:
+ mode = 4 # Distributing local clock (low accuracy)
+ elif source in (ntp.control.CTL_SST_TS_ATOM, ntp.control.CTL_SST_TS_LF,
+ ntp.control.CTL_SST_TS_HF, ntp.control.CTL_SST_TS_UHF):
+ # I am not sure if I should be including the radios in this
+ mode = 5 # Synced to local refclock
+ elif source == ntp.control.CTL_SST_TS_NTP:
+ # Should this include "other"? That covers things like chrony...
+ mode = 6 # Sync to remote NTP
+ else:
+ mode = 99 # Unknown
+ return ax.Varbind(ax.VALUE_INTEGER, oid, mode)
def cbr_statusStratum(self, oid):
# NTPstratum
- data = self.session.readvar(0, ["stratum"])
- return ax.Varbind(ax.VALUE_GAUGE32, oid, data["stratum"])
+ return self.readCallbackSkeletonSimple(oid, "stratum",
+ ax.VALUE_GAUGE32)
- def cbr_statusActiveRefSourceID(self, oid): # DUMMY
+ def cbr_statusActiveRefSourceID(self, oid):
# range of uint32
- return ax.Varbind(ax.VALUE_GAUGE32, oid, 1024)
+ peers = self.misc_getPeerData()
+ syspeer = 0
+ for associd in peers.keys():
+ rstatus = peers[associd]["peerstatus"]
+ if (ntp.control.CTL_PEER_STATVAL(rstatus) & 0x7) == \
+ ntp.control.CTL_PST_SEL_SYSPEER:
+ syspeer = associd
+ break
+ return ax.Varbind(ax.VALUE_GAUGE32, oid, syspeer)
def cbr_statusActiveRefSourceName(self, oid):
# utf8
- # TODO: pretty sure this is right, just need DNS
data = self.session.readvar(0, ["peeradr"])
- return ax.Varbind(ax.VALUE_OCTET_STR, oid, data["peeradr"])
+ data = ntp.util.canonicalize_dns(data["peeradr"])
+ return ax.Varbind(ax.VALUE_OCTET_STR, oid, data)
def cbr_statusActiveOffset(self, oid):
# DisplayString
@@ -404,9 +441,22 @@ class DataSource: # This may be broken up in future to be less NTP-specific
data = self.session.readvar(0, ["rootdisp"], raw=True)
return ax.Varbind(ax.VALUE_OCTET_STR, oid, data["rootdisp"][1])
- def cbr_statusEntityUptime(self, oid): # DUMMY
+ def cbr_statusEntityUptime(self, oid):
# TimeTicks
- return ax.Varbind(ax.VALUE_TIME_TICKS, oid, 8)
+ # What the spec claims:
+ # The uptime of the NTP entity, (i.e., the time since ntpd was
+ # (re-)initialized not sysUptime!). The time is represented in
+ # hundreds of seconds since Jan 1, 1970 (00:00:00.000) UTC.
+ #
+ # First problem: TimeTicks represents hundred*ths* of seconds, could
+ # easily be a typo.
+ # Second problem: snmpwalk will happily give you a display of
+ # how long a period of time a value is, such as uptime since start.
+ # That is the opposite of what the spec claims.
+ #
+ # I am abandoning the spec, and going with what makes a lick of sense
+ uptime = self.session.readvar(0, ["ss_reset"])["ss_reset"] * 100
+ return ax.Varbind(ax.VALUE_TIME_TICKS, oid, uptime)
def cbr_statusDateTime(self, oid):
# NtpDateTime
@@ -420,24 +470,35 @@ class DataSource: # This may be broken up in future to be less NTP-specific
# NtpDateTime
return ax.Varbind(ax.VALUE_OCTET_STR, oid, "blah")
- def cbr_statusLeapSecDirection(self, oid): # DUMMY
+ def cbr_statusLeapSecDirection(self, oid):
# range of int32
- return ax.Varbind(ax.VALUE_INTEGER, oid, -1)
+ leap = self.session.readvar(0, ["leap"])["leap"]
+ if leap == 1:
+ pass # leap 1 == forward
+ elif leap == 2:
+ leap = -1 # leap 2 == backward
+ else:
+ leap = 0 # leap 0 or 3 == no change
+ return ax.Varbind(ax.VALUE_INTEGER, oid, leap)
def cbr_statusInPkts(self, oid):
- data = self.session.readvar(0, ["io_received"])
- return ax.Varbind(ax.VALUE_COUNTER32, oid, data["io_received"])
+ return self.readCallbackSkeletonSimple(oid, "io_received",
+ ax.VALUE_COUNTER32)
def cbr_statusOutPkts(self, oid):
- data = self.session.readvar(0, ["io_sent"])
- return ax.Varbind(ax.VALUE_COUNTER32, oid, data["io_sent"])
+ return self.readCallbackSkeletonSimple(oid, "io_sent",
+ ax.VALUE_COUNTER32)
def cbr_statusBadVersion(self, oid):
- data = self.session.readvar(0, ["ss_oldver"])
- return ax.Varbind(ax.VALUE_COUNTER32, oid, data["ss_oldver"])
+ return self.readCallbackSkeletonSimple(oid, "ss_oldver",
+ ax.VALUE_COUNTER32)
- def cbr_statusProtocolError(self, oid): # DUMMY
- return ax.Varbind(ax.VALUE_COUNTER32, oid, 400)
+ def cbr_statusProtocolError(self, oid):
+ data = self.session.readvar(0, ["ss_badformat", "ss_badauth"])
+ protoerr = 0
+ for key in data.keys():
+ protoerr += data[key]
+ return ax.Varbind(ax.VALUE_COUNTER32, oid, protoerr)
def cbr_statusNotifications(self, oid):
return ax.Varbind(ax.VALUE_COUNTER32, oid, self.sentNotifications)
@@ -451,39 +512,21 @@ class DataSource: # This may be broken up in future to be less NTP-specific
# assocAddrType
# assocAddr
# assocOffset
-
- def cbr_assocStratum(self, oid): # DUMMY
- # NTPStratum
- return ax.Varbind(ax.VALUE_GAUGE32, oid, 12)
-
- def cbr_assocStatusJitter(self, oid): # DUMMY
- # DisplayString
- return ax.Varbind(ax.VALUE_OCTET_STR, oid, "You")
-
- def cbr_assocStatusDelay(self, oid): # DUMMY
- # DisplayString
- return ax.Varbind(ax.VALUE_OCTET_STR, oid, "Kindly?")
-
- def cbr_assocStatusDisp(self, oid): # DUMMY
- # DisplayString
- return ax.Varbind(ax.VALUE_OCTET_STR, oid, "*thunk*")
-
- def cbr_assocStatInPkts(self, oid): # DUMMY
- return ax.Varbind(ax.VALUE_COUNTER32, oid, 2)
-
- def cbr_assocStatOutPkts(self, oid): # DUMMY
- return ax.Varbind(ax.VALUE_COUNTER32, oid, 4)
-
- def cbr_assocStatProtoErr(self, oid): # DUMMY
- return ax.Varbind(ax.VALUE_COUNTER32, oid, 8)
+ # assocStratum
+ # assocJitter
+ # assocDelay
+ # assocDispersion
+ # assocInPackets
+ # assocOutPackets
+ # assocProtocolErrors
#########################
- def cbr_entHeartbeatInterval(self, oid): # DUMMY
+ def cbr_entHeartbeatInterval(self, oid):
# uint32
- return ax.Varbind(ax.VALUE_GAUGE32, oid, 16)
+ return ax.Varbind(ax.VALUE_GAUGE32, oid, self.heartbeatInterval)
- def cbr_entNotifBits(self, oid): # DUMMY
+ def cbr_entNotifBits(self, oid):
# BITS
data = ax.bools2Bits((self.notifyModeChange,
self.notifyStratumChange,
@@ -511,7 +554,19 @@ class DataSource: # This may be broken up in future to be less NTP-specific
# Actions: test, undo, commit, cleanup
# =====================================
- def cbw_entNotifBits(self, action, varbind, oldData=None): # DUMMY
+ def cbw_entHeartbeatInterval(self, action, varbind, oldData=None):
+ if action == "test":
+ return ax.ERR_NOERROR
+ elif action == "commit":
+ self.heartbeatInterval = varbind.payload
+ return ax.ERR_NOERROR
+ elif action == "undo":
+ self.heartbeatInterval = oldData
+ return ax.ERR_NOERROR
+ elif action == "cleanup":
+ pass
+
+ def cbw_entNotifBits(self, action, varbind, oldData=None):
if action == "test":
return ax.ERR_NOERROR
elif action == "commit":
@@ -542,52 +597,38 @@ class DataSource: # This may be broken up in future to be less NTP-specific
# =====================================
def sub_assocID(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
- associds = self.misc_getPeerIDs()
- return ax.Varbind(ax.VALUE_GAUGE32, oid, associds[index])
- subs = {}
- associds = self.misc_getPeerIDs() # need the peer count
- for i in range(len(associds)):
- subs[i] = ax.mibnode(readCallback, None, None, None)
- return subs
+ def handler(oid, associd):
+ return ax.Varbind(ax.VALUE_GAUGE32, oid, associd)
+ return self.dynamicCallbackSkeleton(handler)
def sub_assocName(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
+ def handler(oid, associd):
pdata = self.misc_getPeerData()
- associd = self.misc_getPeerIDs()[index]
+ if pdata is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
peername = pdata[associd]["srcadr"][1] # TODO: DNS
peername = ntp.util.canonicalize_dns(peername)
return ax.Varbind(ax.VALUE_OCTET_STR, oid, peername)
- subs = {}
- associds = self.misc_getPeerIDs() # need the peer count
- for i in range(len(associds)):
- subs[i] = ax.mibnode(readCallback, None, None, None)
- return subs
+ return self.dynamicCallbackSkeleton(handler)
def sub_assocRefID(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
+ def handler(oid, associd):
pdata = self.misc_getPeerData()
- associd = self.misc_getPeerIDs()[index]
+ if pdata is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
# elaborate code in util.py indicates this may not be stable
try:
refid = pdata[associd]["refid"][1]
except IndexError:
refid = ""
return ax.Varbind(ax.VALUE_OCTET_STR, oid, refid)
- subs = {}
- associds = self.misc_getPeerIDs() # need the peer count
- for i in range(len(associds)):
- subs[i] = ax.mibnode(readCallback, None, None, None)
- return subs
+ return self.dynamicCallbackSkeleton(handler)
def sub_assocAddrType(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
+ def handler(oid, associd):
pdata = self.misc_getPeerData()
- associd = self.misc_getPeerIDs()[index]
+ if pdata is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
srcadr = pdata[associd]["srcadr"][1]
try:
socklen = len(socket.getaddrinfo(srcadr, None)[0][-1])
@@ -602,17 +643,13 @@ class DataSource: # This may be broken up in future to be less NTP-specific
# detect those yet. Or if I even need to.
addrtype = 0 # is this ok? or should it return a NULL?
return ax.Varbind(ax.VALUE_INTEGER, oid, addrtype)
- subs = {}
- associds = self.misc_getPeerIDs() # need the peer count
- for i in range(len(associds)):
- subs[i] = ax.mibnode(readCallback, None, None, None)
- return subs
+ return self.dynamicCallbackSkeleton(handler)
def sub_assocAddr(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
+ def handler(oid, associd):
pdata = self.misc_getPeerData()
- associd = self.misc_getPeerIDs()[index]
+ if pdata is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
srcadr = pdata[associd]["srcadr"][1]
# WARNING: I am only guessing that this is correct
# Discover what type of address we have
@@ -632,82 +669,116 @@ class DataSource: # This may be broken up in future to be less NTP-specific
pieces = []
srcadr = [int(x) for x in pieces]
return ax.Varbind(ax.VALUE_OCTET_STR, oid, srcadr)
- subs = {}
- associds = self.misc_getPeerIDs() # need the peer count
- for i in range(len(associds)):
- subs[i] = ax.mibnode(readCallback, None, None, None)
- return subs
+ return self.dynamicCallbackSkeleton(handler)
def sub_assocOffset(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
+ def handler(oid, associd):
pdata = self.misc_getPeerData()
- associd = self.misc_getPeerIDs()[index]
+ if pdata is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
offset = pdata[associd]["offset"][1]
offset = ntp.util.unitifyvar(offset, "offset", width=None,
unitSpace=True)
return ax.Varbind(ax.VALUE_OCTET_STR, oid, offset)
- subs = {}
- associds = self.misc_getPeerIDs() # need the peer count
- for i in range(len(associds)):
- subs[i] = ax.mibnode(readCallback, None, None, None)
- return subs
+ return self.dynamicCallbackSkeleton(handler)
def sub_assocStratum(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
+ def handler(oid, associd):
pdata = self.misc_getPeerData()
- associd = self.misc_getPeerIDs()[index]
+ if pdata is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
stratum = pdata[associd]["stratum"][0]
return ax.Varbind(ax.VALUE_GAUGE32, oid, stratum)
- subs = {}
- associds = self.misc_getPeerIDs() # need the peer count
- for i in range(len(associds)):
- subs[i] = ax.mibnode(readCallback, None, None, None)
- return subs
+ return self.dynamicCallbackSkeleton(handler)
def sub_assocJitter(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
+ def handler(oid, associd):
pdata = self.misc_getPeerData()
- associd = self.misc_getPeerIDs()[index]
+ if pdata is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
jitter = pdata[associd]["jitter"][1]
return ax.Varbind(ax.VALUE_OCTET_STR, oid, jitter)
- subs = {}
- associds = self.misc_getPeerIDs() # need the peer count
- for i in range(len(associds)):
- subs[i] = ax.mibnode(readCallback, None, None, None)
- return subs
+ return self.dynamicCallbackSkeleton(handler)
def sub_assocDelay(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
+ def handler(oid, associd):
pdata = self.misc_getPeerData()
- associd = self.misc_getPeerIDs()[index]
+ if pdata is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
delay = pdata[associd]["delay"][1]
return ax.Varbind(ax.VALUE_OCTET_STR, oid, delay)
- subs = {}
- associds = self.misc_getPeerIDs() # need the peer count
- for i in range(len(associds)):
- subs[i] = ax.mibnode(readCallback, None, None, None)
- return subs
+ return self.dynamicCallbackSkeleton(handler)
def sub_assocDispersion(self):
- def readCallback(oid):
- index = oid.subids[-1] # if called properly this works (Ha!)
+ def handler(oid, associd):
pdata = self.misc_getPeerData()
- associd = self.misc_getPeerIDs()[index]
+ if pdata is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
dispersion = pdata[associd]["rootdisp"][1]
return ax.Varbind(ax.VALUE_OCTET_STR, oid, dispersion)
+ return self.dynamicCallbackSkeleton(handler)
+
+ def sub_assocStatInPkts(self):
+ def handler(oid, associd):
+ inpkts = self.safeReadvar(associd, ["received"])
+ if inpkts is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
+ inpkts = inpkts["received"]
+ return ax.Varbind(ax.VALUE_COUNTER32, oid, inpkts)
+ return self.dynamicCallbackSkeleton(handler)
+
+ def sub_assocStatOutPkts(self):
+ def handler(oid, associd):
+ outpkts = self.safeReadvar(associd, ["sent"])
+ if outpkts is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
+ outpkts = outpkts["sent"]
+ return ax.Varbind(ax.VALUE_COUNTER32, oid, outpkts)
+ return self.dynamicCallbackSkeleton(handler)
+
+ def sub_assocStatProtoErr(self):
+ def handler(oid, associd):
+ pvars = self.safeReadvar(associd, ["badauth", "bogusorg",
+ "seldisp", "selbroken"])
+ if pvars is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
+ protoerr = 0
+ for key in pvars.keys():
+ protoerr += pvars[key]
+ return ax.Varbind(ax.VALUE_COUNTER32, oid, protoerr)
+ return self.dynamicCallbackSkeleton(handler)
+
+ # =====================================
+ # Misc data helpers (not part of the MIB proper)
+ # =====================================
+
+ def safeReadvar(self, associd, variables=None, raw=False):
+ # Use this when we want to catch packet errors, but don't care
+ # about what they are
+ try:
+ return self.session.readvar(associd, varlist=variables, raw=raw)
+ except ntp.packet.ControlException:
+ return None
+
+ def dynamicCallbackSkeleton(self, handler):
+ def readCallback(oid):
+ index = oid.subids[-1] # if called properly this works (Ha!)
+ associd = self.misc_getPeerIDs()[index]
+ return handler(oid, associd)
subs = {}
associds = self.misc_getPeerIDs() # need the peer count
for i in range(len(associds)):
subs[i] = ax.mibnode(readCallback, None, None, None)
return subs
- # =====================================
- # Misc data helpers (not part of the MIB proper)
- # =====================================
+ def readCallbackSkeletonSimple(self, oid, varname, dataType):
+ # Used for entries that just need a simple variable retrevial
+ # but do not need any processing.
+ data = self.safeReadvar(0, [varname])[varname]
+ if data is None:
+ return ax.Varbind(ax.VALUE_NULL, oid)
+ else:
+ return ax.Varbind(dataType, oid, data)
def misc_getPeerIDs(self):
peerids = self.cache.get("peerids")
@@ -725,7 +796,8 @@ class DataSource: # This may be broken up in future to be less NTP-specific
peerdata = {}
for aid in associds:
try:
- pdata = self.session.readvar(aid, raw=True)
+ pdata = self.safeReadvar(aid, raw=True)
+ pdata["peerstatus"] = self.session.rstatus
except IOError as e:
continue
peerdata[aid] = pdata
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/commit/feb97562d3ff71b1d5df25b981502395a2be1545
---
View it on GitLab: https://gitlab.com/NTPsec/ntpsec/commit/feb97562d3ff71b1d5df25b981502395a2be1545
You're receiving this email because of your account on gitlab.com.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ntpsec.org/pipermail/vc/attachments/20171123/d8fec758/attachment.html>
More information about the vc
mailing list