Compare commits

...

15 Commits

Author SHA1 Message Date
c0dfb9b628 fix date formatting 2025-03-11 10:33:24 +01:00
57c04685a1 changes 2025-03-10 20:25:38 +01:00
e3b9e11533 peer variables 2025-03-10 14:48:00 +01:00
9d8aa63bed initial in new branch 2025-03-10 10:59:27 +01:00
af30d5ec57 error handling 2025-03-07 22:37:46 +01:00
e5d167dcda service file 2025-03-04 18:40:27 +01:00
613c2be109 arg parsing int 2025-03-02 22:34:11 +01:00
b371796690 adjust logging, 2 2025-03-02 22:31:54 +01:00
8b29d8808c adjust logging 2025-03-02 22:29:10 +01:00
30da4ed974 verbose switch 2025-03-02 22:19:15 +01:00
b5e8a63013 period for update too 2025-03-02 22:14:20 +01:00
679b9492d7 period fix 2025-03-01 23:50:09 +01:00
1e97ef3b2e changes 2025-02-25 14:00:28 +01:00
bfbaf70430 Merge branch 'main' of gitea.hottis.de:wn/snmp-agentx-ntpsec 2025-02-25 11:24:52 +01:00
d3b8114b0b mib adjusted 2025-02-25 11:24:49 +01:00
5 changed files with 296 additions and 180 deletions

14
snippets/ntp/test02.py Normal file
View File

@ -0,0 +1,14 @@
import ntp.packet
import json
session = ntp.packet.ControlSession()
session.openhost('localhost')
peers = session.readstat()
for p in peers:
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)}")

View File

@ -16,186 +16,264 @@ hottis MODULE-IDENTITY
::= { enterprises 9676 } ::= { enterprises 9676 }
hottisNtpsec OBJECT IDENTIFIER ::= { hottis 123 } hottisNtpsec OBJECT IDENTIFIER ::= { hottis 123 }
local OBJECT IDENTIFIER ::= { hottisNtpsec 1 } local OBJECT IDENTIFIER ::= { hottisNtpsec 1 }
peers OBJECT IDENTIFIER ::= { hottisNtpsec 2 } system OBJECT IDENTIFIER ::= { hottisNtpsec 2 }
peers OBJECT IDENTIFIER ::= { hottisNtpsec 3 }
leap OBJECT-TYPE ntpsecLocalLeap OBJECT-TYPE
SYNTAX INTEGER (0..2) SYNTAX INTEGER (0..3)
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Leap Indicator (0=none, 1=last minute 61s, 2=last minute 59s)" DESCRIPTION "Leap Indicator (0=none, 1=last minute 61s, 2=last minute 59s, 3=invalid)"
::= { local 1 } ::= { local 1 }
stratum OBJECT-TYPE ntpsecLocalStratum OBJECT-TYPE
SYNTAX INTEGER (0..16) SYNTAX INTEGER (0..16)
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Stratum-Level des lokalen NTP-Servers." DESCRIPTION "Stratum level of this NTP server"
::= { local 2 } ::= { local 2 }
precision OBJECT-TYPE ntpsecLocalPrecision OBJECT-TYPE
SYNTAX INTEGER SYNTAX INTEGER
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Präzision des lokalen NTP-Servers." DESCRIPTION "Precision of this NTP server (log2 s)"
::= { local 3 } ::= { local 3 }
rootdelay OBJECT-TYPE ntpsecLocalRootdelay OBJECT-TYPE
SYNTAX INTEGER SYNTAX INTEGER
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Root Delay in Millisekunden." DESCRIPTION "Total roundtrip delay to the primary reference clock (ns)"
::= { local 4 } ::= { local 4 }
rootdisp OBJECT-TYPE ntpsecLocalRootdisp OBJECT-TYPE
SYNTAX INTEGER SYNTAX INTEGER
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Root Dispersion in Millisekunden." DESCRIPTION "Root dispersion to the primary reference clock (us)"
::= { local 5 } ::= { local 5 }
refid OBJECT-TYPE ntpsecLocalRefid OBJECT-TYPE
SYNTAX DisplayString SYNTAX DisplayString
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Referenz-ID des lokalen NTP-Servers." DESCRIPTION "Reference ID of this NTP server"
::= { local 6 }
ntpsecLocalReftime OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Reference time"
::= { local 7 } ::= { local 7 }
reftime OBJECT-TYPE ntpsecLocalTc OBJECT-TYPE
SYNTAX INTEGER SYNTAX INTEGER
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Letzte Referenzzeit des lokalen NTP-Servers." DESCRIPTION "Time constant and poll exponent (log2 s, 3-17)"
::= { local 8 } ::= { local 8 }
clock OBJECT-TYPE ntpsecLocalPeer OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "System peer association id"
::= { local 9 }
ntpsecLocalOffset OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Combined offset relative to this host (ns)"
::= { local 10 }
ntpsecLocalFrequency OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Frequency offset (PPM*1000) relative to hardware clock"
::= { local 11 }
ntpsecLocalSysJitter OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Combined system jitter"
::= { local 12 }
ntpsecLocalClkJitter OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Clock jitter"
::= { local 13 }
ntpsecLocalClock OBJECT-TYPE
SYNTAX DisplayString SYNTAX DisplayString
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Uhrzeit des lokalen Systems." DESCRIPTION "Date and time of day"
::= { local 14 }
ntpsecLocalProcessor OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Processor of this NTP server"
::= { local 15 } ::= { local 15 }
processor OBJECT-TYPE ntpsecLocalSystem OBJECT-TYPE
SYNTAX DisplayString SYNTAX DisplayString
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Prozessortyp des Systems." DESCRIPTION "System of this NTP server"
::= { local 16 } ::= { local 16 }
system OBJECT-TYPE ntpsecLocalVersion OBJECT-TYPE
SYNTAX DisplayString SYNTAX DisplayString
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Systemtyp." DESCRIPTION "NTPsec version of this NTP server"
::= { local 17 } ::= { local 17 }
version OBJECT-TYPE ntpsecLocalClkWander OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Clock frequency wander (PPM*1000000)"
::= { local 18 }
ntpsecLocalTai OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "TAI-UTC offset (s)"
::= { local 19 }
ntpsecLocalLeapsec OBJECT-TYPE
SYNTAX DisplayString SYNTAX DisplayString
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "NTPsec Version." DESCRIPTION "Timestamp when the last/next leap second was/will be inserted"
::= { local 19 } ::= { local 20 }
ntpsecLocalExpire OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Timestamp when the NIST leapseconds file expieres"
::= { local 21 }
ntpsecLocalMintc OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Minimum time constant (log2 s, 3-17)"
::= { local 22 }
peerNumber OBJECT-TYPE ntpsecPeerNumber OBJECT-TYPE
SYNTAX INTEGER SYNTAX INTEGER
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Number of Peers." DESCRIPTION "Number of Peers."
::= { peers 1 } ::= { peers 1 }
peerTable OBJECT-TYPE ntpsecPeerTable OBJECT-TYPE
SYNTAX SEQUENCE OF PeerEntry SYNTAX SEQUENCE OF ntpsecPeerEntry
MAX-ACCESS not-accessible MAX-ACCESS not-accessible
STATUS current STATUS current
DESCRIPTION "Tabelle mit NTP-Peers." DESCRIPTION "Tabelle mit NTP-Peers."
::= { peers 2 } ::= { peers 2 }
peerEntry OBJECT-TYPE ntpsecPeerEntry OBJECT-TYPE
SYNTAX PeerEntry SYNTAX PeerEntry
MAX-ACCESS not-accessible MAX-ACCESS not-accessible
STATUS current STATUS current
DESCRIPTION "Eintrag für einen einzelnen NTP-Peer." DESCRIPTION "Eintrag für einen einzelnen NTP-Peer."
INDEX { associd } INDEX { ntpsecPeerIndex }
::= { peerTable 1 } ::= { ntpsecPeerTable 1 }
PeerEntry ::= SEQUENCE { ntpsecPeerEntry ::= SEQUENCE {
index INTEGER, ntpsecPeerIndex INTEGER,
associd INTEGER, ntpsecPeerAssocid INTEGER,
srcadr IpAddress, ntpsecPeerSrcadr IpAddress,
srcport INTEGER, ntpsecPeerSrcport INTEGER,
dstadr IpAddress, ntpsecPeerDstadr IpAddress,
dstport INTEGER, ntpsecPeerDstport INTEGER,
leap INTEGER, ntpsecPeerLeap INTEGER,
hmode INTEGER, ntpsecPeerHmode INTEGER,
stratum INTEGER, ntpsecPeerStratum INTEGER,
ppoll INTEGER, ntpsecPeerPpoll INTEGER,
hpoll INTEGER, ntpsecPeerHpoll INTEGER,
precision INTEGER, ntpsecPeerPrecision INTEGER,
rootdelay INTEGER, ntpsecPeerRootdelay INTEGER,
rootdisp INTEGER, ntpsecPeerRootdisp INTEGER,
refid DisplayString, ntpsecPeerRefid DisplayString,
reftime DisplayString, ntpsecPeerReftime DisplayString,
rec DisplayString, ntpsecPeerRec DisplayString,
xmt DisplayString, ntpsecPeerXmt DisplayString,
reach INTEGER, ntpsecPeerReach INTEGER,
unreach INTEGER, ntpsecPeerUnreach INTEGER,
delay_s DisplayString, ntpsecPeerDelay_s DisplayString,
delay INTEGER, ntpsecPeerDelay INTEGER,
offset INTEGER, ntpsecPeerOffset INTEGER,
jitter INTEGER, ntpsecPeerJitter INTEGER,
dispersion INTEGER, ntpsecPeerDispersion INTEGER,
keyid INTEGER, ntpsecPeerKeyid INTEGER,
filtdelay DisplayString, ntpsecPeerFiltdelay DisplayString,
filtoffset DisplayString, ntpsecPeerFiltoffset DisplayString,
pmode INTEGER, ntpsecPeerPmode INTEGER,
filtdisp DisplayString, ntpsecPeerFiltdisp DisplayString,
flash INTEGER, ntpsecPeerFlash INTEGER,
headway INTEGER, ntpsecPeerHeadway INTEGER,
ntscookies INTEGER ntpsecPeerNtscookies INTEGER
} }
index OBJECT-TYPE ntpsecPeerIndex OBJECT-TYPE
SYNTAX INTEGER SYNTAX INTEGER
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Peer-Index." DESCRIPTION "Peer-Index."
::= { peerEntry 1 } ::= { ntpsecPeerEntry 1 }
associd OBJECT-TYPE ntpsecPeerAssocId OBJECT-TYPE
SYNTAX INTEGER SYNTAX INTEGER
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Peer-Identifikationsnummer." DESCRIPTION "Peer-Identifikationsnummer."
::= { peerEntry 2 } ::= { ntpsecPeerEntry 2 }
srcadr OBJECT-TYPE ntpsecPeerSrcAdr OBJECT-TYPE
SYNTAX IpAddress SYNTAX DisplayString
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Quell-IP-Adresse des Peers." DESCRIPTION "Quell-IP-Adresse des Peers."
::= { peerEntry 3 } ::= { ntpsecPeerEntry 3 }
srcport OBJECT-TYPE ntpsecPeerSrcPort OBJECT-TYPE
SYNTAX INTEGER SYNTAX INTEGER
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Quellport des Peers." DESCRIPTION "Quellport des Peers."
::= { peerEntry 4 } ::= { ntpsecPeerEntry 4 }
dstadr OBJECT-TYPE ntpsecPeerDstAdr OBJECT-TYPE
SYNTAX IpAddress SYNTAX DisplayString
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Ziel-IP-Adresse des Peers." DESCRIPTION "Ziel-IP-Adresse des Peers."
::= { peerEntry 5 } ::= { ntpsecPeerEntry 5 }
dstport OBJECT-TYPE ntpsecPeerDstPort OBJECT-TYPE
SYNTAX INTEGER SYNTAX INTEGER
MAX-ACCESS read-only MAX-ACCESS read-only
STATUS current STATUS current
DESCRIPTION "Zielport des Peers." DESCRIPTION "Zielport des Peers."
::= { peerEntry 6 } ::= { ntpsecPeerEntry 6 }
END END

View File

@ -1,4 +1,5 @@
pass_valueimport ntp.packe0 import ntp.packet
import ntp.ntpc
import threading import threading
from contextlib import AbstractContextManager from contextlib import AbstractContextManager
import time import time
@ -10,18 +11,17 @@ import grp
import logging import logging
import logging.handlers import logging.handlers
import pyagentx import pyagentx
import datetime
LOGGING_LEVEL=logging.DEBUG
BASE_OID_ENTERPRISE = '1.3.6.1.4.1' BASE_OID_ENTERPRISE = '1.3.6.1.4.1'
BASE_OID_HOTTIS = BASE_OID_ENTERPRISE + '.9676' BASE_OID_HOTTIS = BASE_OID_ENTERPRISE + '.9676'
BASE_OID_HOTTIS_NTPSEC = BASE_OID_HOTTIS + '.123' BASE_OID_HOTTIS_NTPSEC = BASE_OID_HOTTIS + '.123'
# just the prefix where the objects are below # just the prefix where the objects are below
LOCAL_PREFIX = '1' SYSINFO_PREFIX = '1'
SYSSTATS_PREFIX = '2'
PEERS_PREFIX = '2' PEERS_PREFIX = '3'
NUMBER_OF_PEERS_PREFIX = PEERS_PREFIX + '.1' NUMBER_OF_PEERS_PREFIX = PEERS_PREFIX + '.1'
# this is for a table # this is for a table
@ -40,63 +40,52 @@ def int_scale1M(x):
def pass_value(x): def pass_value(x):
return x return x
LOCAL_SERVER_KEYS = [ def string_ntp_seconds(x):
return ntp.ntpc.prettydate(x).split(' ')[1]
SYSINFO_KEYS = [
['peeradr', pyagentx.TYPE_OCTETSTRING, pass_value],
['peermode', pyagentx.TYPE_INTEGER, pass_value],
['leap', pyagentx.TYPE_INTEGER, pass_value], ['leap', pyagentx.TYPE_INTEGER, pass_value],
['stratum', 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], ['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], ['refid', pyagentx.TYPE_OCTETSTRING, pass_value],
['reftime', pyagentx.TYPE_OCTETSTRING, pass_value], ['reftime', pyagentx.TYPE_OCTETSTRING, string_ntp_seconds],
['tc', pyagentx.TYPE_INTEGER, pass_value],
['peer', pyagentx.TYPE_INTEGER, pass_value],
['offset', pyagentx.TYPE_INTEGER, int_scale1M],
['frequency', pyagentx.TYPE_INTEGER, int_scale1k],
['sys_jitter', pyagentx.TYPE_INTEGER, int_scale1M], ['sys_jitter', pyagentx.TYPE_INTEGER, int_scale1M],
['clk_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], ['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], SYSSTATS_KEYS = [
['mintc', pyagentx.TYPE_INTEGER, pass_value] ['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 = [ PEER_KEYS = [
['srchost', pyagentx.TYPE_OCTETSTRING, pass_value],
['srcadr', pyagentx.TYPE_OCTETSTRING, pass_value], ['srcadr', pyagentx.TYPE_OCTETSTRING, pass_value],
['srcport', pyagentx.TYPE_INTEGER, pass_value], ['refid', pyagentx.TYPE_OCTETSTRING, 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],
['stratum', pyagentx.TYPE_INTEGER, pass_value], ['stratum', pyagentx.TYPE_INTEGER, pass_value],
['hmode', pyagentx.TYPE_INTEGER, pass_value],
['ppoll', pyagentx.TYPE_INTEGER, pass_value], ['ppoll', pyagentx.TYPE_INTEGER, pass_value],
['hpoll', pyagentx.TYPE_INTEGER, pass_value], ['hpoll', pyagentx.TYPE_INTEGER, pass_value],
['precision', pyagentx.TYPE_INTEGER, pass_value], ['rec', pyagentx.TYPE_OCTETSTRING, string_ntp_seconds],
['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],
['reach', pyagentx.TYPE_INTEGER, pass_value], ['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], ['delay', pyagentx.TYPE_INTEGER, int_scale1k],
['offset', pyagentx.TYPE_INTEGER, int_scale1M], ['offset', pyagentx.TYPE_INTEGER, int_scale1M],
['jitter', 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]
] ]
@ -125,32 +114,39 @@ class NtpDataCollector(threading.Thread):
self.period = period self.period = period
self.stop_event = threading.Event() self.stop_event = threading.Event()
self.session = ntp.packet.ControlSession()
self.session.openhost(self.ntpserver)
threading.Thread.__init__(self) threading.Thread.__init__(self)
def run(self): def run(self):
while not self.stop_event.is_set(): while not self.stop_event.is_set():
logger.debug('Query ntp server') try:
logger.debug('Query ntp server')
tmp_data_store = {}
ntpserver_vars = self.session.readvar(0) session = ntp.packet.ControlSession()
logger.debug(f"{ntpserver_vars=}") session.openhost(self.ntpserver)
tmp_data_store = {} sysinfo_vars = session.readvar(0, [ x[0] for x in SYSINFO_KEYS ])
tmp_data_store['local'] = dict(ntpserver_vars) logger.debug(f"{sysinfo_vars=}")
tmp_data_store['sysinfo'] = dict(sysinfo_vars)
peers = self.session.readstat() sysstats_vars = session.readvar(0, [ x[0] for x in SYSSTATS_KEYS ])
tmp_data_store['peers'] = {} logger.debug(f"{sysstats_vars=}")
for peer in peers: tmp_data_store['sysstats'] = dict(sysstats_vars)
peer_vars = self.session.readvar(peer.associd)
tmp_data_store['peers'][peer.associd] = dict(peer_vars)
logger.debug(f"{peer.associd=}, {peer_vars=}")
with globalDataStore as ds: peers = session.readstat()
ds.update_data(tmp_data_store) tmp_data_store['peers'] = {}
for peer in peers:
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=}")
with globalDataStore as ds:
ds.update_data(tmp_data_store)
except ntp.packet.ControlException as e:
logger.error(f"ntp.packet.ControlException while querying NTP server: {str(e)}")
finally:
time.sleep(self.period)
time.sleep(self.period)
logger.info('NtpDataCollector terminating') logger.info('NtpDataCollector terminating')
def stop(self): def stop(self):
@ -163,15 +159,31 @@ class NtpsecDataUpdater(pyagentx.Updater):
with globalDataStore as ds: with globalDataStore as ds:
if ds.data: if ds.data:
try: 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=}") # logger.debug(f"local: {index=} {data_spec=}")
oid_prefix = f"{LOCAL_PREFIX}.{index}" try:
self._data[oid_prefix] = { oid_prefix = f"{SYSINFO_PREFIX}.{index}"
'name': oid_prefix, self._data[oid_prefix] = {
'type': data_spec[1], 'name': oid_prefix,
'value': data_spec[2](ds.data['local'][data_spec[0]]) 'type': data_spec[1],
} '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']) number_of_peers = len(ds.data['peers'])
# logger.debug(f"number of peers: {number_of_peers}") # logger.debug(f"number of peers: {number_of_peers}")
number_of_peers_oid_prefix = f"{NUMBER_OF_PEERS_PREFIX}" number_of_peers_oid_prefix = f"{NUMBER_OF_PEERS_PREFIX}"
@ -196,25 +208,29 @@ class NtpsecDataUpdater(pyagentx.Updater):
} }
for key_index, data_spec in enumerate(PEER_KEYS, start=3): for key_index, data_spec in enumerate(PEER_KEYS, start=3):
# logger.debug(f"peer: {associd=} {key_index=} {data_spec=}") # logger.debug(f"peer: {associd=} {key_index=} {data_spec=}")
oid_prefix = f"{TABLE_OF_PEERS_PREFIX}.{key_index}.{peer_index}" try:
self._data[oid_prefix] = { oid_prefix = f"{TABLE_OF_PEERS_PREFIX}.{key_index}.{peer_index}"
'name': oid_prefix, self._data[oid_prefix] = {
'type': data_spec[1], 'name': oid_prefix,
'value': data_spec[2](peer[data_spec[0]]) 'type': data_spec[1],
} 'value': data_spec[2](peer[data_spec[0]])
}
except KeyError as e:
logger.error(f"key {data_spec[0]} missing in peer {associd}, skip it: {str(e)}")
except Exception as e: except Exception as e:
logger.error(f"Failed to update: {type(e)} {e}") logger.error(f"Failed to update: {type(e)} {e}")
class NtpsecAgent(pyagentx.Agent): class NtpsecAgent(pyagentx.Agent):
def __init__(self, agent_id='NtpsecAgent', socket_path=None): def __init__(self, period=1, agent_id='NtpsecAgent', socket_path=None):
logger.info('Agent created') logger.info('Agent created')
self.period = period
super().__init__() super().__init__()
def setup(self): def setup(self):
logger.info('Agent setup') logger.info('Agent setup')
self.register(BASE_OID_HOTTIS_NTPSEC, NtpsecDataUpdater, freq=1) self.register(BASE_OID_HOTTIS_NTPSEC, NtpsecDataUpdater, freq=self.period)
@ -239,8 +255,6 @@ def daemonize(pid_filename):
os.dup2(log.fileno(), sys.stdout.fileno()) os.dup2(log.fileno(), sys.stdout.fileno())
os.dup2(log.fileno(), sys.stderr.fileno()) os.dup2(log.fileno(), sys.stderr.fileno())
logger.removeHandler(stdout_handler)
pyagentx.setup_logging(debug=True)
def set_user_group(user, group): def set_user_group(user, group):
if group: if group:
@ -258,10 +272,10 @@ def set_user_group(user, group):
sys.exit(1) sys.exit(1)
os.setuid(uid) os.setuid(uid)
def setup_logging(verbose):
if __name__ == '__main__': log_level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig( logging.basicConfig(
level=LOGGING_LEVEL, level=log_level,
format="%(name)s - %(levelname)s - %(message)s", format="%(name)s - %(levelname)s - %(message)s",
handlers=[logging.handlers.SysLogHandler(address='/dev/log')] handlers=[logging.handlers.SysLogHandler(address='/dev/log')]
) )
@ -269,13 +283,18 @@ if __name__ == '__main__':
stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stdout_handler.setFormatter(formatter) stdout_handler.setFormatter(formatter)
pyagentx.setup_logging(debug=verbose)
logger.addHandler(stdout_handler) logger.addHandler(stdout_handler)
return logger
if __name__ == '__main__':
pid_filename = '/tmp/agentx-ntpsec.pid' pid_filename = '/tmp/agentx-ntpsec.pid'
parser = argparse.ArgumentParser(description='snmpd agentx extension for ntpsec') parser = argparse.ArgumentParser(description='snmpd agentx extension for ntpsec')
parser.add_argument('--period', '-p', parser.add_argument('--period', '-p',
help='Period to query the NTP server, in seconds, default 60s', help='Period to query the NTP server, in seconds, default 60s',
type=int,
required=False, required=False,
default=60) default=60)
parser.add_argument('--ntpserver', '-n', parser.add_argument('--ntpserver', '-n',
@ -299,39 +318,29 @@ if __name__ == '__main__':
help="Set gid of process", help="Set gid of process",
required=False, required=False,
default='') default='')
parser.add_argument('--verbose', '-v',
help='Enable debug output',
required=False,
action='store_true',
default=False)
args = parser.parse_args() args = parser.parse_args()
if args.daemonize: if args.daemonize:
daemonize(pid_filename) daemonize(pid_filename)
set_user_group(args.user, args.group) set_user_group(args.user, args.group)
logger = setup_logging(args.verbose)
if args.group:
try:
gid = grp.getgrnam(args.group).gr_gid
except KeyError:
logger.error(f"Group {args.group} does not exist")
sys.exit(1)
os.setgid(gid)
if args.user:
try:
uid = pwd.getpwnam(args.user).pw_uid
except KeyError:
logger.error(f"user {args.user} does not exist")
sys.exit(1)
os.setuid(uid)
ntpserver = args.ntpserver ntpserver = args.ntpserver
period = args.period period = args.period
try: try:
globalDataStore = DataStore() globalDataStore = DataStore()
ndc = NtpDataCollector(ntpserver=ntpserver, period=period) ndc = NtpDataCollector(ntpserver=ntpserver, period=period)
ndc.start() ndc.start()
nsax = NtpsecAgent() nsax = NtpsecAgent(period=period)
nsax.start() nsax.start()
except Exception as e: except Exception as e:
logger.error(f"Unhandled exception: {e}") logger.error(f"Unhandled exception: {e}")

View File

@ -0,0 +1,15 @@
[Unit]
Description=AgentX NTPsec Service
After=network.target
[Service]
ExecStart=/usr/bin/python3 /usr/local/bin/agentx-ntpsec.py -u Debian-snmp -g Debian-snmp -p 30 --pid /run/agentx-ntpsec.pid
User=Debian-snmp
Group=Debian-snmp
PIDFile=/run/agentx-ntpsec.pid
Restart=always
[Install]
WantedBy=multi-user.target