Compare commits

16 Commits

Author SHA1 Message Date
a327aed72e Merge branch 'more_variables_and_other_approach' 2025-03-11 10:35:03 +01:00
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
373fac3184 use the bug tracking of gitea 2025-03-09 17:54:41 +01:00
1d93319c92 issues 2025-03-09 17:49:24 +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
5 changed files with 183 additions and 144 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,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
@ -183,54 +184,54 @@ ntpsecPeerNumber OBJECT-TYPE
::= { peers 1 }
ntpsecPeerTable OBJECT-TYPE
SYNTAX SEQUENCE OF PeerEntry
SYNTAX SEQUENCE OF ntpsecPeerEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION "Tabelle mit NTP-Peers."
::= { peers 2 }
ntpsecPeer OBJECT-TYPE
ntpsecPeerEntry OBJECT-TYPE
SYNTAX PeerEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION "Eintrag für einen einzelnen NTP-Peer."
INDEX { associd }
INDEX { ntpsecPeerIndex }
::= { ntpsecPeerTable 1 }
ntpsecPeerEntry ::= SEQUENCE {
index INTEGER,
associd INTEGER,
srcadr IpAddress,
srcport INTEGER,
dstadr IpAddress,
dstport INTEGER,
leap INTEGER,
hmode INTEGER,
stratum INTEGER,
ppoll INTEGER,
hpoll INTEGER,
precision INTEGER,
rootdelay INTEGER,
rootdisp INTEGER,
refid DisplayString,
reftime DisplayString,
rec DisplayString,
xmt DisplayString,
reach INTEGER,
unreach INTEGER,
delay_s DisplayString,
delay INTEGER,
offset INTEGER,
jitter INTEGER,
dispersion INTEGER,
keyid INTEGER,
filtdelay DisplayString,
filtoffset DisplayString,
pmode INTEGER,
filtdisp DisplayString,
flash INTEGER,
headway INTEGER,
ntscookies INTEGER
ntpsecPeerIndex INTEGER,
ntpsecPeerAssocid INTEGER,
ntpsecPeerSrcadr IpAddress,
ntpsecPeerSrcport INTEGER,
ntpsecPeerDstadr IpAddress,
ntpsecPeerDstport INTEGER,
ntpsecPeerLeap INTEGER,
ntpsecPeerHmode INTEGER,
ntpsecPeerStratum INTEGER,
ntpsecPeerPpoll INTEGER,
ntpsecPeerHpoll INTEGER,
ntpsecPeerPrecision INTEGER,
ntpsecPeerRootdelay INTEGER,
ntpsecPeerRootdisp INTEGER,
ntpsecPeerRefid DisplayString,
ntpsecPeerReftime DisplayString,
ntpsecPeerRec DisplayString,
ntpsecPeerXmt DisplayString,
ntpsecPeerReach INTEGER,
ntpsecPeerUnreach INTEGER,
ntpsecPeerDelay_s DisplayString,
ntpsecPeerDelay INTEGER,
ntpsecPeerOffset INTEGER,
ntpsecPeerJitter INTEGER,
ntpsecPeerDispersion INTEGER,
ntpsecPeerKeyid INTEGER,
ntpsecPeerFiltdelay DisplayString,
ntpsecPeerFiltoffset DisplayString,
ntpsecPeerPmode INTEGER,
ntpsecPeerFiltdisp DisplayString,
ntpsecPeerFlash INTEGER,
ntpsecPeerHeadway INTEGER,
ntpsecPeerNtscookies INTEGER
}
ntpsecPeerIndex OBJECT-TYPE
@ -238,41 +239,41 @@ ntpsecPeerIndex OBJECT-TYPE
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Peer-Index."
::= { ntpsecPeer 1 }
::= { ntpsecPeerEntry 1 }
ntpsecPeerAssocId OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Peer-Identifikationsnummer."
::= { ntpsecPeer 2 }
::= { ntpsecPeerEntry 2 }
ntpsecPeerSrcAdr OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Quell-IP-Adresse des Peers."
::= { ntpsecPeer 3 }
::= { ntpsecPeerEntry 3 }
ntpsecPeerSrcPort OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Quellport des Peers."
::= { ntpsecPeer 4 }
::= { ntpsecPeerEntry 4 }
ntpsecPeerDstAdr OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Ziel-IP-Adresse des Peers."
::= { ntpsecPeer 5 }
::= { ntpsecPeerEntry 5 }
ntpsecPeerDstPort OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION "Zielport des Peers."
::= { ntpsecPeer 6 }
::= { ntpsecPeerEntry 6 }
END

View File

@ -1,4 +1,5 @@
pass_valueimport ntp.packe0
import ntp.packet
import ntp.ntpc
import threading
from contextlib import AbstractContextManager
import time
@ -10,18 +11,17 @@ import grp
import logging
import logging.handlers
import pyagentx
import datetime
LOGGING_LEVEL=logging.DEBUG
BASE_OID_ENTERPRISE = '1.3.6.1.4.1'
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
@ -40,63 +40,52 @@ def int_scale1M(x):
def pass_value(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],
['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_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]
]
@ -125,32 +114,39 @@ class NtpDataCollector(threading.Thread):
self.period = period
self.stop_event = threading.Event()
self.session = ntp.packet.ControlSession()
self.session.openhost(self.ntpserver)
threading.Thread.__init__(self)
def run(self):
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)
logger.debug(f"{ntpserver_vars=}")
session = ntp.packet.ControlSession()
session.openhost(self.ntpserver)
tmp_data_store = {}
tmp_data_store['local'] = dict(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)
peers = self.session.readstat()
tmp_data_store['peers'] = {}
for peer in peers:
peer_vars = self.session.readvar(peer.associd)
tmp_data_store['peers'][peer.associd] = dict(peer_vars)
logger.debug(f"{peer.associd=}, {peer_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)
with globalDataStore as ds:
ds.update_data(tmp_data_store)
peers = session.readstat()
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')
def stop(self):
@ -163,15 +159,31 @@ class NtpsecDataUpdater(pyagentx.Updater):
with globalDataStore as ds:
if ds.data:
try:
for index, data_spec in enumerate(LOCAL_SERVER_KEYS, start=1):
logger.debug("Updating data store")
for index, data_spec in enumerate(SYSINFO_KEYS, start=1):
# logger.debug(f"local: {index=} {data_spec=}")
oid_prefix = f"{LOCAL_PREFIX}.{index}"
self._data[oid_prefix] = {
'name': oid_prefix,
'type': data_spec[1],
'value': data_spec[2](ds.data['local'][data_spec[0]])
}
try:
oid_prefix = f"{SYSINFO_PREFIX}.{index}"
self._data[oid_prefix] = {
'name': oid_prefix,
'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'])
# logger.debug(f"number of peers: {number_of_peers}")
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):
# logger.debug(f"peer: {associd=} {key_index=} {data_spec=}")
oid_prefix = f"{TABLE_OF_PEERS_PREFIX}.{key_index}.{peer_index}"
self._data[oid_prefix] = {
'name': oid_prefix,
'type': data_spec[1],
'value': data_spec[2](peer[data_spec[0]])
}
try:
oid_prefix = f"{TABLE_OF_PEERS_PREFIX}.{key_index}.{peer_index}"
self._data[oid_prefix] = {
'name': oid_prefix,
'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:
logger.error(f"Failed to update: {type(e)} {e}")
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')
self.period = period
super().__init__()
def setup(self):
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.stderr.fileno())
logger.removeHandler(stdout_handler)
pyagentx.setup_logging(debug=True)
def set_user_group(user, group):
if group:
@ -258,10 +272,10 @@ def set_user_group(user, group):
sys.exit(1)
os.setuid(uid)
if __name__ == '__main__':
def setup_logging(verbose):
log_level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig(
level=LOGGING_LEVEL,
level=log_level,
format="%(name)s - %(levelname)s - %(message)s",
handlers=[logging.handlers.SysLogHandler(address='/dev/log')]
)
@ -269,13 +283,18 @@ if __name__ == '__main__':
stdout_handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stdout_handler.setFormatter(formatter)
pyagentx.setup_logging(debug=verbose)
logger.addHandler(stdout_handler)
return logger
if __name__ == '__main__':
pid_filename = '/tmp/agentx-ntpsec.pid'
parser = argparse.ArgumentParser(description='snmpd agentx extension for ntpsec')
parser.add_argument('--period', '-p',
help='Period to query the NTP server, in seconds, default 60s',
type=int,
required=False,
default=60)
parser.add_argument('--ntpserver', '-n',
@ -299,39 +318,29 @@ if __name__ == '__main__':
help="Set gid of process",
required=False,
default='')
parser.add_argument('--verbose', '-v',
help='Enable debug output',
required=False,
action='store_true',
default=False)
args = parser.parse_args()
if args.daemonize:
daemonize(pid_filename)
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
period = args.period
try:
globalDataStore = DataStore()
ndc = NtpDataCollector(ntpserver=ntpserver, period=period)
ndc.start()
nsax = NtpsecAgent()
nsax = NtpsecAgent(period=period)
nsax.start()
except Exception as 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