From 9d8aa63bedd9ba608e3b180bceac4bd2e6d70ab8 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Mon, 10 Mar 2025 10:59:27 +0100 Subject: [PATCH 1/4] initial in new branch --- snippets/ntp/{test.py => test01.py} | 0 snippets/ntp/test02.py | 21 +++++++++++++++++++++ src/HOTTIS-NTPSEC-MIB.txt | 5 +++-- src/agentx-ntpsec.py | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) rename snippets/ntp/{test.py => test01.py} (100%) create mode 100644 snippets/ntp/test02.py diff --git a/snippets/ntp/test.py b/snippets/ntp/test01.py similarity index 100% rename from snippets/ntp/test.py rename to snippets/ntp/test01.py diff --git a/snippets/ntp/test02.py b/snippets/ntp/test02.py new file mode 100644 index 0000000..3abc13f --- /dev/null +++ b/snippets/ntp/test02.py @@ -0,0 +1,21 @@ +import ntp.packet +import json + +session = ntp.packet.ControlSession() +session.openhost('localhost') + +peers = session.readstat() + +k = [] + +l = session.readvar(0) +print(f"{l=}") +k.append(list(dict(l).keys())) + +for p in peers: + l = session.readvar(p.associd) + print(f"{p.associd}: {dict(l)}") + k.append(list(dict(l).keys())) + +print(json.dumps(k, indent=4)) + diff --git a/src/HOTTIS-NTPSEC-MIB.txt b/src/HOTTIS-NTPSEC-MIB.txt index 8e333c3..23281fa 100644 --- a/src/HOTTIS-NTPSEC-MIB.txt +++ b/src/HOTTIS-NTPSEC-MIB.txt @@ -16,8 +16,9 @@ hottis MODULE-IDENTITY ::= { enterprises 9676 } hottisNtpsec OBJECT IDENTIFIER ::= { hottis 123 } -local OBJECT IDENTIFIER ::= { hottisNtpsec 1 } -peers OBJECT IDENTIFIER ::= { hottisNtpsec 2 } +local OBJECT IDENTIFIER ::= { hottisNtpsec 1 } +system OBJECT IDENTIFIER ::= { hottisNtpsec 2 } +peers OBJECT IDENTIFIER ::= { hottisNtpsec 3 } ntpsecLocalLeap OBJECT-TYPE diff --git a/src/agentx-ntpsec.py b/src/agentx-ntpsec.py index 23ec8bc..dec8432 100644 --- a/src/agentx-ntpsec.py +++ b/src/agentx-ntpsec.py @@ -26,7 +26,7 @@ NUMBER_OF_PEERS_PREFIX = PEERS_PREFIX + '.1' # 2 is the prefix # the first 1 is for the table in the mib # the second 1 is for the entries in the table in the mib -TABLE_OF_PEERS_PREFIX = PEERS_PREFIX + '.2.1' +TABLE_OF_PEERS_PREFIX = PEERS_PREFIX + '.3.1' def int_scale1k(x): From e3b9e1153360af6f5e752da19fb8286106561bc6 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Mon, 10 Mar 2025 14:48:00 +0100 Subject: [PATCH 2/4] peer variables --- snippets/ntp/test02.py | 13 +++---------- src/agentx-ntpsec.py | 44 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/snippets/ntp/test02.py b/snippets/ntp/test02.py index 3abc13f..ec7f505 100644 --- a/snippets/ntp/test02.py +++ b/snippets/ntp/test02.py @@ -6,16 +6,9 @@ session.openhost('localhost') peers = session.readstat() -k = [] - -l = session.readvar(0) -print(f"{l=}") -k.append(list(dict(l).keys())) for p in peers: - l = session.readvar(p.associd) - print(f"{p.associd}: {dict(l)}") - k.append(list(dict(l).keys())) - -print(json.dumps(k, indent=4)) + vars = session.readvar(p.associd, ['srchost', 'srcadr', 'refid', 'stratum', 'hmode', 'rec', 'reach', 'hpoll', 'ppoll', 'delay', 'offset', 'jitter']) + peerSelectStatus = " x.-+#*o"[(session.rstatus >>8) & 0x07] + print(f"{p.associd}, {peerSelectStatus}, {session.rstatus:04x}: {dict(vars)}") diff --git a/src/agentx-ntpsec.py b/src/agentx-ntpsec.py index dec8432..e5a5193 100644 --- a/src/agentx-ntpsec.py +++ b/src/agentx-ntpsec.py @@ -38,6 +38,24 @@ def int_scale1M(x): def pass_value(x): return x +# sysinfo +# sysinfo = ( +# ("peeradr", "system peer: ", NTP_ADP), +# ("peermode", "system peer mode: ", NTP_MODE), +# ("leap", "leap indicator: ", NTP_2BIT), +# ("stratum", "stratum: ", NTP_INT), +# ("precision", "log2 precision: ", NTP_INT), +# ("rootdelay", "root delay: ", NTP_FLOAT), +# ("rootdisp", "root dispersion: ", NTP_FLOAT), +# ("rootdist", "root distance ", NTP_FLOAT), +# ("refid", "reference ID: ", NTP_STR), +# ("reftime", "reference time: ", NTP_LFP), +# ("sys_jitter", "system jitter: ", NTP_FLOAT), +# ("clk_jitter", "clock jitter: ", NTP_FLOAT), +# ("clk_wander", "clock wander: ", NTP_FLOAT), +# ("authdelay", "symm. auth. delay:", NTP_FLOAT), +# ) + LOCAL_SERVER_KEYS = [ ['leap', pyagentx.TYPE_INTEGER, pass_value], ['stratum', pyagentx.TYPE_INTEGER, pass_value], @@ -63,6 +81,32 @@ LOCAL_SERVER_KEYS = [ ['mintc', pyagentx.TYPE_INTEGER, pass_value] ] +# sysstats +# sysstats = ( +# ("ss_uptime", "uptime: ", NTP_UPTIME), +# ("ss_numctlreq", "control requests: ", NTP_INT), +# ) +# sysstats2 = ( +# ("ss_reset", "sysstats reset: ", NTP_UPTIME), +# ("ss_received", "packets received: ", NTP_PACKETS), +# ("ss_thisver", "current version: ", NTP_PACKETS), +# ("ss_oldver", "older version: ", NTP_PACKETS), +# ("ss_ver1", "NTPv1 total: ", NTP_PACKETS), +# ("ss_ver1client","NTPv1 clients: ", NTP_PACKETS), +# ("ss_ver1zero", "NTPv1 mode0: ", NTP_PACKETS), +# ("ss_ver1symm", "NTPv1 symm act: ", NTP_PACKETS), +# ("ss_badformat", "bad length or format: ", NTP_PACKETS), +# ("ss_badauth", "authentication failed:", NTP_PACKETS), +# ("ss_declined", "declined: ", NTP_PACKETS), +# ("ss_restricted","restricted: ", NTP_PACKETS), +# ("ss_limited", "rate limited: ", NTP_PACKETS), +# ("ss_kodsent", "KoD responses: ", NTP_PACKETS), +# ("ss_processed", "processed for time: ", NTP_PACKETS), +# ) +# + + + PEER_KEYS = [ ['srcadr', pyagentx.TYPE_OCTETSTRING, pass_value], ['srcport', pyagentx.TYPE_INTEGER, pass_value], From 57c04685a1a73c61200f6708bf23d80ec3db42cf Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Mon, 10 Mar 2025 20:25:38 +0100 Subject: [PATCH 3/4] changes --- src/agentx-ntpsec.py | 155 +++++++++++++++++-------------------------- 1 file changed, 62 insertions(+), 93 deletions(-) diff --git a/src/agentx-ntpsec.py b/src/agentx-ntpsec.py index e5a5193..17a000d 100644 --- a/src/agentx-ntpsec.py +++ b/src/agentx-ntpsec.py @@ -10,6 +10,7 @@ import grp import logging import logging.handlers import pyagentx +import datetime BASE_OID_ENTERPRISE = '1.3.6.1.4.1' @@ -17,16 +18,16 @@ BASE_OID_HOTTIS = BASE_OID_ENTERPRISE + '.9676' BASE_OID_HOTTIS_NTPSEC = BASE_OID_HOTTIS + '.123' # just the prefix where the objects are below -LOCAL_PREFIX = '1' - -PEERS_PREFIX = '2' +SYSINFO_PREFIX = '1' +SYSSTATS_PREFIX = '2' +PEERS_PREFIX = '3' NUMBER_OF_PEERS_PREFIX = PEERS_PREFIX + '.1' # this is for a table # 2 is the prefix # the first 1 is for the table in the mib # the second 1 is for the entries in the table in the mib -TABLE_OF_PEERS_PREFIX = PEERS_PREFIX + '.3.1' +TABLE_OF_PEERS_PREFIX = PEERS_PREFIX + '.2.1' def int_scale1k(x): @@ -38,107 +39,59 @@ def int_scale1M(x): def pass_value(x): return x -# sysinfo -# sysinfo = ( -# ("peeradr", "system peer: ", NTP_ADP), -# ("peermode", "system peer mode: ", NTP_MODE), -# ("leap", "leap indicator: ", NTP_2BIT), -# ("stratum", "stratum: ", NTP_INT), -# ("precision", "log2 precision: ", NTP_INT), -# ("rootdelay", "root delay: ", NTP_FLOAT), -# ("rootdisp", "root dispersion: ", NTP_FLOAT), -# ("rootdist", "root distance ", NTP_FLOAT), -# ("refid", "reference ID: ", NTP_STR), -# ("reftime", "reference time: ", NTP_LFP), -# ("sys_jitter", "system jitter: ", NTP_FLOAT), -# ("clk_jitter", "clock jitter: ", NTP_FLOAT), -# ("clk_wander", "clock wander: ", NTP_FLOAT), -# ("authdelay", "symm. auth. delay:", NTP_FLOAT), -# ) +def string_ntp_seconds(x): + h1, h2 = x.split('.') + seconds = int(h1, 16) + fraction = int(h2, 16) + ntp_epoch = datetime.datetime(1900, 1, 1) + timestamp = ntp_epoch + datetime.timedelta(seconds=seconds) + microseconds = fraction // 1000 + timestamp = timestamp.replace(microsecond=microseconds) + return f"{timestamp} UTC" -LOCAL_SERVER_KEYS = [ + +SYSINFO_KEYS = [ + ['peeradr', pyagentx.TYPE_OCTETSTRING, pass_value], + ['peermode', pyagentx.TYPE_INTEGER, pass_value], ['leap', pyagentx.TYPE_INTEGER, pass_value], ['stratum', pyagentx.TYPE_INTEGER, pass_value], - ['precision', pyagentx.TYPE_INTEGER, pass_value], + ['precision', pyagentx.TYPE_INTEGER, pass_value], ['rootdelay', pyagentx.TYPE_INTEGER, int_scale1M], - ['rootdisp', pyagentx.TYPE_INTEGER, int_scale1k], + ['rootdisp', pyagentx.TYPE_INTEGER, int_scale1M], + ['rootdist', pyagentx.TYPE_INTEGER, int_scale1M], ['refid', pyagentx.TYPE_OCTETSTRING, pass_value], - ['reftime', pyagentx.TYPE_OCTETSTRING, pass_value], - ['tc', pyagentx.TYPE_INTEGER, pass_value], - ['peer', pyagentx.TYPE_INTEGER, pass_value], - ['offset', pyagentx.TYPE_INTEGER, int_scale1M], - ['frequency', pyagentx.TYPE_INTEGER, int_scale1k], + ['reftime', pyagentx.TYPE_OCTETSTRING, string_ntp_seconds], ['sys_jitter', pyagentx.TYPE_INTEGER, int_scale1M], ['clk_jitter', pyagentx.TYPE_INTEGER, int_scale1M], - ['clock', pyagentx.TYPE_OCTETSTRING, pass_value], - ['processor', pyagentx.TYPE_OCTETSTRING, pass_value], - ['system', pyagentx.TYPE_OCTETSTRING, pass_value], - ['version', pyagentx.TYPE_OCTETSTRING, pass_value], ['clk_wander', pyagentx.TYPE_INTEGER, int_scale1M], - ['tai', pyagentx.TYPE_INTEGER, pass_value], - ['leapsec', pyagentx.TYPE_OCTETSTRING, pass_value], - ['expire', pyagentx.TYPE_OCTETSTRING, pass_value], - ['mintc', pyagentx.TYPE_INTEGER, pass_value] ] -# sysstats -# sysstats = ( -# ("ss_uptime", "uptime: ", NTP_UPTIME), -# ("ss_numctlreq", "control requests: ", NTP_INT), -# ) -# sysstats2 = ( -# ("ss_reset", "sysstats reset: ", NTP_UPTIME), -# ("ss_received", "packets received: ", NTP_PACKETS), -# ("ss_thisver", "current version: ", NTP_PACKETS), -# ("ss_oldver", "older version: ", NTP_PACKETS), -# ("ss_ver1", "NTPv1 total: ", NTP_PACKETS), -# ("ss_ver1client","NTPv1 clients: ", NTP_PACKETS), -# ("ss_ver1zero", "NTPv1 mode0: ", NTP_PACKETS), -# ("ss_ver1symm", "NTPv1 symm act: ", NTP_PACKETS), -# ("ss_badformat", "bad length or format: ", NTP_PACKETS), -# ("ss_badauth", "authentication failed:", NTP_PACKETS), -# ("ss_declined", "declined: ", NTP_PACKETS), -# ("ss_restricted","restricted: ", NTP_PACKETS), -# ("ss_limited", "rate limited: ", NTP_PACKETS), -# ("ss_kodsent", "KoD responses: ", NTP_PACKETS), -# ("ss_processed", "processed for time: ", NTP_PACKETS), -# ) -# - - +SYSSTATS_KEYS = [ + ['ss_uptime', pyagentx.TYPE_INTEGER, pass_value ], + ['ss_numctlreq', pyagentx.TYPE_INTEGER, pass_value ], + ['ss_reset', pyagentx.TYPE_INTEGER, pass_value ], + ['ss_received', pyagentx.TYPE_COUNTER64, pass_value ], + ['ss_badformat', pyagentx.TYPE_COUNTER64, pass_value ], + ['ss_declined', pyagentx.TYPE_COUNTER64, pass_value ], + ['ss_restricted', pyagentx.TYPE_COUNTER64, pass_value ], + ['ss_limited', pyagentx.TYPE_COUNTER64, pass_value ], + ['ss_kodsent', pyagentx.TYPE_COUNTER64, pass_value ], + ['ss_processed', pyagentx.TYPE_COUNTER64, pass_value ] +] PEER_KEYS = [ + ['srchost', pyagentx.TYPE_OCTETSTRING, pass_value], ['srcadr', pyagentx.TYPE_OCTETSTRING, pass_value], - ['srcport', pyagentx.TYPE_INTEGER, pass_value], - ['dstadr', pyagentx.TYPE_OCTETSTRING, pass_value], - ['dstport', pyagentx.TYPE_INTEGER, pass_value], - ['leap', pyagentx.TYPE_INTEGER, pass_value], - ['hmode', pyagentx.TYPE_INTEGER, pass_value], + ['refid', pyagentx.TYPE_OCTETSTRING, pass_value], ['stratum', pyagentx.TYPE_INTEGER, pass_value], + ['hmode', pyagentx.TYPE_INTEGER, pass_value], ['ppoll', pyagentx.TYPE_INTEGER, pass_value], ['hpoll', pyagentx.TYPE_INTEGER, pass_value], - ['precision', pyagentx.TYPE_INTEGER, pass_value], - ['rootdelay', pyagentx.TYPE_INTEGER, int_scale1k], - ['rootdisp', pyagentx.TYPE_INTEGER, int_scale1k], - ['refid', pyagentx.TYPE_OCTETSTRING, pass_value], - ['reftime', pyagentx.TYPE_OCTETSTRING, pass_value], - ['rec', pyagentx.TYPE_OCTETSTRING, pass_value], - ['xmt', pyagentx.TYPE_OCTETSTRING, pass_value], + ['rec', pyagentx.TYPE_OCTETSTRING, string_ntp_seconds], ['reach', pyagentx.TYPE_INTEGER, pass_value], - ['unreach', pyagentx.TYPE_INTEGER, pass_value], - ['delay-s', pyagentx.TYPE_OCTETSTRING, pass_value], ['delay', pyagentx.TYPE_INTEGER, int_scale1k], ['offset', pyagentx.TYPE_INTEGER, int_scale1M], ['jitter', pyagentx.TYPE_INTEGER, int_scale1M], - ['dispersion', pyagentx.TYPE_INTEGER, int_scale1k], - ['keyid', pyagentx.TYPE_INTEGER, pass_value], - ['filtdelay', pyagentx.TYPE_OCTETSTRING, pass_value], - ['filtoffset', pyagentx.TYPE_OCTETSTRING, pass_value], - ['pmode', pyagentx.TYPE_INTEGER, pass_value], - ['filtdisp', pyagentx.TYPE_OCTETSTRING, pass_value], - ['flash', pyagentx.TYPE_INTEGER, pass_value], - ['headway', pyagentx.TYPE_INTEGER, pass_value], - ['ntscookies', pyagentx.TYPE_INTEGER, pass_value] ] @@ -173,20 +126,23 @@ class NtpDataCollector(threading.Thread): while not self.stop_event.is_set(): try: logger.debug('Query ntp server') + tmp_data_store = {} session = ntp.packet.ControlSession() session.openhost(self.ntpserver) - ntpserver_vars = session.readvar(0) - logger.debug(f"{ntpserver_vars=}") + sysinfo_vars = session.readvar(0, [ x[0] for x in SYSINFO_KEYS ]) + logger.debug(f"{sysinfo_vars=}") + tmp_data_store['sysinfo'] = dict(sysinfo_vars) - tmp_data_store = {} - tmp_data_store['local'] = dict(ntpserver_vars) + sysstats_vars = session.readvar(0, [ x[0] for x in SYSSTATS_KEYS ]) + logger.debug(f"{sysstats_vars=}") + tmp_data_store['sysstats'] = dict(sysstats_vars) peers = session.readstat() tmp_data_store['peers'] = {} for peer in peers: - peer_vars = session.readvar(peer.associd) + peer_vars = session.readvar(peer.associd, [ x[0] for x in PEER_KEYS ]) tmp_data_store['peers'][peer.associd] = dict(peer_vars) logger.debug(f"{peer.associd=}, {peer_vars=}") @@ -210,17 +166,30 @@ class NtpsecDataUpdater(pyagentx.Updater): if ds.data: try: logger.debug("Updating data store") - for index, data_spec in enumerate(LOCAL_SERVER_KEYS, start=1): + for index, data_spec in enumerate(SYSINFO_KEYS, start=1): # logger.debug(f"local: {index=} {data_spec=}") try: - oid_prefix = f"{LOCAL_PREFIX}.{index}" + oid_prefix = f"{SYSINFO_PREFIX}.{index}" self._data[oid_prefix] = { 'name': oid_prefix, 'type': data_spec[1], - 'value': data_spec[2](ds.data['local'][data_spec[0]]) + 'value': data_spec[2](ds.data['sysinfo'][data_spec[0]]) } except KeyError as e: logger.error(f"key {data_spec[0]} missing in local, skip it: {str(e)}") + + for index, data_spec in enumerate(SYSSTATS_KEYS, start=1): + # logger.debug(f"local: {index=} {data_spec=}") + try: + oid_prefix = f"{SYSSTATS_PREFIX}.{index}" + self._data[oid_prefix] = { + 'name': oid_prefix, + 'type': data_spec[1], + 'value': data_spec[2](ds.data['sysstats'][data_spec[0]]) + } + except KeyError as e: + logger.error(f"key {data_spec[0]} missing in local, skip it: {str(e)}") + number_of_peers = len(ds.data['peers']) # logger.debug(f"number of peers: {number_of_peers}") number_of_peers_oid_prefix = f"{NUMBER_OF_PEERS_PREFIX}" From c0dfb9b628229a8d8e416e81c553c0ceeb0661b5 Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 11 Mar 2025 10:33:24 +0100 Subject: [PATCH 4/4] fix date formatting --- src/agentx-ntpsec.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/agentx-ntpsec.py b/src/agentx-ntpsec.py index 17a000d..a2e0c1d 100644 --- a/src/agentx-ntpsec.py +++ b/src/agentx-ntpsec.py @@ -1,4 +1,5 @@ import ntp.packet +import ntp.ntpc import threading from contextlib import AbstractContextManager import time @@ -40,14 +41,7 @@ def pass_value(x): return x def string_ntp_seconds(x): - h1, h2 = x.split('.') - seconds = int(h1, 16) - fraction = int(h2, 16) - ntp_epoch = datetime.datetime(1900, 1, 1) - timestamp = ntp_epoch + datetime.timedelta(seconds=seconds) - microseconds = fraction // 1000 - timestamp = timestamp.replace(microsecond=microseconds) - return f"{timestamp} UTC" + return ntp.ntpc.prettydate(x).split(' ')[1] SYSINFO_KEYS = [