alerta-contrib/integrations/snmptrap/handler.py
2023-03-21 00:15:34 +01:00

202 lines
6.6 KiB
Python

import datetime
import logging
import os
import platform
import re
import sys
from alertaclient.api import Client
__version__ = '5.0.0'
LOG = logging.getLogger('alerta.snmptrap')
logging.basicConfig(
format='%(asctime)s - %(name)s: %(levelname)s - %(message)s', level=logging.DEBUG)
class SnmpTrapHandler:
def __init__(self):
self.api = None
def run(self):
endpoint = os.environ.get('ALERTA_ENDPOINT', 'http://localhost:8080')
key = os.environ.get('ALERTA_API_KEY', None)
self.api = Client(endpoint=endpoint, key=key)
data = sys.stdin.read()
LOG.info('snmptrapd -> %r', data)
try:
data = unicode(data, 'utf-8', errors='ignore') # python 2
except NameError:
pass
LOG.debug('unicoded -> %s', data)
try:
resource, event, correlate, trap_version, trapvars = self.parse_snmptrap(
data)
if resource and event:
self.api.send_alert(
resource=resource,
event=event,
correlate=correlate,
group='SNMP',
value=trapvars['$w'],
severity='indeterminate',
environment='Production',
service=['Network'],
text=trapvars['$W'],
event_type='snmptrapAlert',
attributes={'trapvars': {
k.replace('$', '_'): v for k, v in trapvars.items()}},
tags=[trap_version],
create_time=datetime.datetime.strptime('{}T{}.000Z'.format(
trapvars['$x'], trapvars['$X']), '%Y-%m-%dT%H:%M:%S.%fZ'),
raw_data=data
)
except Exception as e:
LOG.warning('Failed to send alert: %s', e)
LOG.debug('Send heartbeat...')
try:
origin = '{}/{}'.format('snmptrap', platform.uname()[1])
self.api.heartbeat(origin, tags=[__version__])
except Exception as e:
LOG.warning('Failed to send heartbeat: %s', e)
def parse_snmptrap(self, data):
pdu_data = data.splitlines()
varbind_list = pdu_data[:]
trapvars = dict()
for line in pdu_data:
if line.startswith('$'):
special, value = line.split(None, 1)
trapvars[special] = value
varbind_list.pop(0)
if '$s' in trapvars:
if trapvars['$s'] == '0':
trap_version = 'SNMPv1'
elif trapvars['$s'] == '1':
trap_version = 'SNMPv2c'
elif trapvars['$s'] == '2':
trap_version = 'SNMPv2u' # not supported
else:
trap_version = 'SNMPv3'
trapvars['$s'] = trap_version
else:
LOG.warning('Failed to parse unknown trap type.')
return
# Get varbinds
varbinds = dict()
idx = 0
for varbind in '\n'.join(varbind_list).split('~%~'):
if varbind == '':
break
idx += 1
try:
oid, value = varbind.split(None, 1)
except ValueError:
oid = varbind
value = ''
varbinds[oid] = value
trapvars['$' + str(idx)] = value # $n
LOG.debug('$%s %s', str(idx), value)
trapvars['$q'] = trapvars['$q'].lstrip(
'.') # if numeric, remove leading '.'
trapvars['$#'] = str(idx)
LOG.debug('varbinds = %s', varbinds)
correlate = list()
if trap_version == 'SNMPv1':
if trapvars['$w'] == '0':
trapvars['$O'] = 'coldStart'
correlate = ['coldStart', 'warmStart']
elif trapvars['$w'] == '1':
trapvars['$O'] = 'warmStart'
correlate = ['coldStart', 'warmStart']
elif trapvars['$w'] == '2':
trapvars['$O'] = 'linkDown'
correlate = ['linkUp', 'linkDown']
elif trapvars['$w'] == '3':
trapvars['$O'] = 'linkUp'
correlate = ['linkUp', 'linkDown']
elif trapvars['$w'] == '4':
trapvars['$O'] = 'authenticationFailure'
elif trapvars['$w'] == '5':
trapvars['$O'] = 'egpNeighborLoss'
elif trapvars['$w'] == '6': # enterpriseSpecific(6)
if trapvars['$q'].isdigit(): # XXX - specific trap number was not decoded
trapvars['$O'] = '{}.0.{}'.format(
trapvars['$N'], trapvars['$q'])
else:
trapvars['$O'] = trapvars['$q']
elif trap_version == 'SNMPv2c':
if 'coldStart' in trapvars['$2']:
trapvars['$w'] = '0'
trapvars['$W'] = 'Cold Start'
elif 'warmStart' in trapvars['$2']:
trapvars['$w'] = '1'
trapvars['$W'] = 'Warm Start'
elif 'linkDown' in trapvars['$2']:
trapvars['$w'] = '2'
trapvars['$W'] = 'Link Down'
elif 'linkUp' in trapvars['$2']:
trapvars['$w'] = '3'
trapvars['$W'] = 'Link Up'
elif 'authenticationFailure' in trapvars['$2']:
trapvars['$w'] = '4'
trapvars['$W'] = 'Authentication Failure'
elif 'egpNeighborLoss' in trapvars['$2']:
trapvars['$w'] = '5'
trapvars['$W'] = 'EGP Neighbor Loss'
else:
trapvars['$w'] = '6'
trapvars['$W'] = 'Enterprise Specific'
trapvars['$O'] = trapvars['$2'] # SNMPv2-MIB::snmpTrapOID.0
LOG.debug('trapvars = %s', trapvars)
LOG.info('%s-Trap-PDU %s from %s at %s %s', trap_version,
trapvars['$O'], trapvars['$B'], trapvars['$x'], trapvars['$X'])
if trapvars['$B'] != '<UNKNOWN>':
resource = trapvars['$B']
elif trapvars['$A'] != '0.0.0.0':
resource = trapvars['$A']
else:
m = re.match(r'UDP: \[(\d+\.\d+\.\d+\.\d+)\]', trapvars['$b'])
if m:
resource = m.group(1)
else:
resource = '<NONE>'
return resource, trapvars['$O'], correlate, trap_version, trapvars
def main():
LOG = logging.getLogger('alerta.snmptrap')
try:
SnmpTrapHandler().run()
except (SystemExit, KeyboardInterrupt):
LOG.info('Exiting alerta SNMP trapper.')
sys.exit(0)
except Exception as e:
LOG.error(e, exc_info=1)
sys.exit(1)
if __name__ == '__main__':
main()