Package Products :: Package ZenEvents :: Module zentrap |
[hide private]
[frames] | no frames]

# Source Code for Module Products.ZenEvents.zentrap

      1  ##############################################################################
      2  #
      3  # Copyright (C) Zenoss, Inc. 2007, 2011-2012, all rights reserved.
      4  #
      5  # This content is made available according to terms specified in
      6  # License.zenoss under the directory where your Zenoss product is installed.
      7  #
      8  ##############################################################################
      9
     10
     11  __doc__ = """zentrap
     12
     13  Creates events from SNMP Traps.
     14  Currently a wrapper around the Net-SNMP C library.
     15  """
     16
     17  import time
     18  import sys
     19  import socket
     20  import errno
     21  import base64
     22  import logging
     23  from struct import unpack
     24  from ipaddr import IPAddress
     25
     26  log = logging.getLogger("zen.zentrap")
     27
     28  # Magical interfacing with C code
     29  import ctypes as c
     30
     31  import Globals
     32  import zope.interface
     33  import zope.component
     34
     35  from twisted.python.failure import Failure
     36  from twisted.internet import defer
     37
     38  from Products.ZenCollector.daemon import CollectorDaemon
     39  from Products.ZenCollector.interfaces import ICollector, ICollectorPreferences,\
     40                                               IEventService, \
     41                                               IScheduledTask
     42  from Products.ZenCollector.tasks import SimpleTaskFactory,\
     43                                          SimpleTaskSplitter,\
     44                                          BaseTask, TaskStates
     45  from Products.ZenUtils.observable import ObservableMixin
     46
     47
     48  from pynetsnmp import netsnmp, twistedsnmp
     49
     50  from Products.ZenUtils.captureReplay import CaptureReplay
     51  from Products.ZenEvents.EventServer import Stats
     52  from Products.ZenUtils.Utils import unused
     53  from Products.ZenCollector.services.config import DeviceProxy
     54  from Products.ZenHub.services.SnmpTrapConfig import User
     55  unused(Globals, DeviceProxy, User)
     56
     57  from zenoss.protocols.protobufs.zep_pb2 import SEVERITY_WARNING
     58
     59
     60  # This is what struct sockaddr_in {} looks like
     61  family = [('family', c.c_ushort)]
     62  if sys.platform == 'darwin':
     63      family = [('len', c.c_ubyte), ('family', c.c_ubyte)]
     64
     65 -class sockaddr_in(c.Structure):
     66      _fields_ = family + [
     67          ('port', c.c_ubyte * 2),     # need to decode from net-byte-order
     68          ('addr', c.c_ubyte * 4),
     69          ]
     70
     71 -class sockaddr_in6(c.Structure):
     72      _fields_ = family + [
     73          ('port', c.c_ushort),        # need to decode from net-byte-order
     74          ('flow', c.c_ubyte * 4),
     75          ('addr', c.c_ubyte * 16),
     76          ('scope_id', c.c_ubyte * 4),
     77          ]
     78
     79  _pre_parse_factory = c.CFUNCTYPE(c.c_int,
     80                                   c.POINTER(netsnmp.netsnmp_session),
     81                                   c.POINTER(netsnmp.netsnmp_transport),
     82                                   c.c_void_p,
     83                                   c.c_int)
     84
     85  # teach python that the return type of snmp_clone_pdu is a pdu pointer
     86  netsnmp.lib.snmp_clone_pdu.restype = netsnmp.netsnmp_pdu_p
     87
     88  # Version codes from the PDU
     89  SNMPv1 = 0
     90  SNMPv2 = 1
     91  SNMPv3 = 3
     92
     93 -class FakePacket(object):
     94      """
     95      A fake object to make packet replaying feasible.
     96      """
     97 -    def __init__(self):
     98          self.fake = True
     99
    100
    101 -class SnmpTrapPreferences(CaptureReplay):
    102      zope.interface.implements(ICollectorPreferences)
    103
    104 -    def __init__(self):
    105          """
    106          Constructs a new PingCollectionPreferences instance and
    107          provides default values for needed attributes.
    108          """
    109          self.collectorName = 'zentrap'
    110          self.defaultRRDCreateCommand = None
    111          self.configCycleInterval = 20 # minutes
    112          self.cycleInterval = 5 * 60 # seconds
    113
    114          # The configurationService attribute is the fully qualified class-name
    115          # of our configuration service that runs within ZenHub
    116          self.configurationService = 'Products.ZenHub.services.SnmpTrapConfig'
    117
    118          # Will be filled in based on buildOptions
    119          self.options = None
    120
    121          self.configCycleInterval = 20*60
    122          self.task = None
    123
    124 -    def postStartupTasks(self):
    125          self.task = TrapTask('zentrap', configId='zentrap')
    126          yield self.task
    127
    128 -    def buildOptions(self, parser):
    129          """
    130          Command-line options to be supported
    131          """
    132          TRAP_PORT = 162
    133          try:
    134              TRAP_PORT = socket.getservbyname('snmptrap', 'udp')
    135          except socket.error:
    136              pass
    137          parser.add_option('--trapport', '-t',
    138              dest='trapport', type='int', default=TRAP_PORT,
    139              help="Listen for SNMP traps on this port rather than the default")
    140          parser.add_option('--useFileDescriptor',
    141                                 dest='useFileDescriptor',
    142                                 type='int',
    143                                 help=("Read from an existing connection "
    144                                       " rather than opening a new port."),
    145                                 default=None)
    146
    147          self.buildCaptureReplayOptions(parser)
    148
    149 -    def postStartup(self):
    150          # Ensure that we always have an oidMap
    151          daemon = zope.component.getUtility(ICollector)
    152          daemon.oidMap = {}
    153
    154 -def ipv6_is_enabled():
    155      "test if ipv6 is enabled"
    156      try:
    157          socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, 0)
    158      except socket.error, e:
    159          if e.errno == errno.EAFNOSUPPORT:
    160              return False
    161          raise
    162      return True
    163
    164 -class TrapTask(BaseTask, CaptureReplay):
    165      """
    166      Listen for SNMP traps and turn them into events
    167      Connects to the TrapService service in zenhub.
    168      """
    169      zope.interface.implements(IScheduledTask)
    170
    171 -    def __init__(self, taskName, configId,
    172                   scheduleIntervalSeconds=3600, taskConfig=None):
    173          BaseTask.__init__(self, taskName, configId,
    174                   scheduleIntervalSeconds, taskConfig)
    175          self.log = log
    176
    177          # Needed for interface
    178          self.name = taskName
    179          self.configId = configId
    180          self.state = TaskStates.STATE_IDLE
    181          self.interval = scheduleIntervalSeconds
    182          self._daemon = zope.component.getUtility(ICollector)
    183          self._eventService = zope.component.queryUtility(IEventService)
    184          self._preferences = self._daemon
    185
    186          # For compatibility with captureReplay
    187          self.options = self._daemon.options
    188
    189          self.oidMap = self._daemon.oidMap
    190          self.stats = Stats()
    191
    192          # Command-line argument sanity checking
    193          self.processCaptureReplayOptions()
    194          self.session=None
    195          self._replayStarted = False
    196          if not self.options.replayFilePrefix:
    197              trapPort = self._preferences.options.trapport
    198              if not self._preferences.options.useFileDescriptor and trapPort < 1024:
    199                  listen_ip = "ipv6" if ipv6_is_enabled() else "0.0.0.0"
    200                  # Makes call to zensocket here (does an exec* so it never returns)
    201                  self._daemon.openPrivilegedPort('--listen', '--proto=udp', '--port=%s:%d' % (listen_ip, trapPort))
    202                  self.log("Unexpected return from openPrivilegedPort. Exiting.")
    203                  sys.exit(1)
    204
    205              # Start listening for SNMP traps
    206              self.log.info("Starting to listen on SNMP trap port %s", trapPort)
    207              self.session = netsnmp.Session()
    208              listening_protocol = "udp6" if ipv6_is_enabled() else "udp"
    209              if self._preferences.options.useFileDescriptor is not None:
    210                  # open port 1162, but then dup fileno onto it
    211                  listening_address = listening_protocol + ':1162'
    212                  fileno = int(self._preferences.options.useFileDescriptor)
    213              else:
    214                  listening_address = '%s:%d' % (listening_protocol, trapPort)
    215                  fileno = -1
    216              self._pre_parse_callback = _pre_parse_factory(self._pre_parse)
    217              debug = self.log.isEnabledFor(logging.DEBUG)
    218              self.session.awaitTraps(listening_address, fileno, self._pre_parse_callback, debug)
    219              self.session.callback = self.receiveTrap
    220              twistedsnmp.updateReactor()
    221
    222 -    def doTask(self):
    223          """
    224          This is a wait-around task since we really are called
    225          asynchronously.
    226          """
    227          if self.options.replayFilePrefix and not self._replayStarted:
    228              log.debug("Replay starting...")
    229              self._replayStarted=True
    230              self.replayAll()
    231              log.debug("Replay done...")
    232              return
    233          return defer.succeed("Waiting for SNMP traps...")
    234
    235 -    def isReplaying(self):
    236          """
    237          @returns True if we are replaying a packet instead of capturing one
    238          """
    239          return len(self._preferences.options.replayFilePrefix) > 0
    240
    241 -    def getEnterpriseString(self, pdu):
    242          """
    243          Get the enterprise string from the PDU or replayed packet
    244
    245          @param pdu: raw packet
    246          @type pdu: binary
    247          @return: enterprise string
    248          @rtype: string
    249          """
    250          def lp2oid(ptr, length):
    251              "Convert a pointer to an array of longs to an OID"
    252              return '.'.join([str(ptr[i]) for i in range(length)])
    253
    254          if hasattr(pdu, "fake"): # Replaying a packet
    255              enterprise = pdu.enterprise
    256          else:
    257              enterprise = lp2oid(pdu.enterprise, pdu.enterprise_length)
    258          return enterprise
    259
    260 -    def getResult(self, pdu):
    261          """
    262          Get the values from the PDU or replayed packet
    263
    264          @param pdu: raw packet
    265          @type pdu: binary
    266          @return: variables from the PDU or Fake packet
    267          @rtype: dictionary
    268          """
    269          if hasattr(pdu, "fake"): # Replaying a packet
    270              variables = pdu.variables
    271          else:
    272              variables = netsnmp.getResult(pdu)
    273          return variables
    274
    275 -    def getCommunity(self, pdu):
    276          """
    277          Get the community string from the PDU or replayed packet
    278
    279          @param pdu: raw packet
    280          @type pdu: binary
    281          @return: SNMP community
    282          @rtype: string
    283          """
    284          community = ''
    285          if hasattr(pdu, "fake"): # Replaying a packet
    286              community = pdu.community
    287          elif pdu.community_len:
    288                  community = c.string_at(pdu.community, pdu.community_len)
    289
    290          return community
    291
    292 -    def convertPacketToPython(self, addr, pdu):
    293          """
    294          Store the raw packet for later examination and troubleshooting.
    295
    296          @param addr: packet-sending host's IP address and port
    297          @type addr: (string, number)
    298          @param pdu: raw packet
    299          @type pdu: binary
    300          @return: Python FakePacket object
    301          @rtype: Python FakePacket object
    302          """
    303          packet = FakePacket()
    304          packet.version = pdu.version
    305          packet.host = addr[0]
    306          packet.port = addr[1]
    307          packet.variables = netsnmp.getResult(pdu)
    308          packet.community = ''
    309          packet.enterprise_length = pdu.enterprise_length
    310
    311          # Here's where we start to encounter differences between packet types
    312          if pdu.version == SNMPv1:
    313              # SNMPv1 can't be received via IPv6
    314              packet.agent_addr =  [pdu.agent_addr[i] for i in range(4)]
    315              packet.trap_type = pdu.trap_type
    316              packet.specific_type = pdu.specific_type
    317              packet.enterprise = self.getEnterpriseString(pdu)
    318              packet.community = self.getCommunity(pdu)
    319
    320          return packet
    321
    322 -    def replay(self, pdu):
    323          """
    324          Replay a captured packet
    325
    326          @param pdu: raw packet
    327          @type pdu: binary
    328          """
    329          ts = time.time()
    330          self.asyncHandleTrap([pdu.host, pdu.port], pdu, ts)
    331
    332 -    def oid2name(self, oid, exactMatch=True, strip=False):
    333          """
    334          Returns a MIB name based on an OID and special handling flags.
    335
    336          @param oid: SNMP Object IDentifier
    337          @type oid: string
    338          @param exactMatch: find the full OID or don't match
    339          @type exactMatch: boolean
    340          @param strip: show what matched, or matched + numeric OID remainder
    341          @type strip: boolean
    342          @return: Twisted deferred object
    343          @rtype: Twisted deferred object
    344          """
    345          if isinstance(oid, tuple):
    346              oid = '.'.join(map(str, oid))
    347
    348          oid = oid.strip('.')
    349          if exactMatch:
    350              if oid in self.oidMap:
    351                  return self.oidMap[oid]
    352              else:
    353                  return oid
    354
    355          oidlist = oid.split('.')
    356          for i in range(len(oidlist), 0, -1):
    357              name = self.oidMap.get('.'.join(oidlist[:i]), None)
    358              if name is None:
    359                  continue
    360
    361              oid_trail = oidlist[i:]
    362              if len(oid_trail) > 0 and not strip:
    363                  return "%s.%s" % (name, '.'.join(oid_trail))
    364              else:
    365                  return name
    366
    367          return oid
    368
    369 -    def _pre_parse(self, session, transport, transport_data, transport_data_length):
    370          """Called before the net-snmp library parses the PDU. In the case
    371          where a v3 trap comes in with unkwnown credentials, net-snmp silently
    372          discards the packet. This method gives zentrap a way to log that these
    373          packets were received to help with troubleshooting."""
    374          if self.log.isEnabledFor(logging.DEBUG):
    375              ipv6_socket_address = c.cast(transport_data, c.POINTER(sockaddr_in6)).contents
    376              if ipv6_socket_address.family == socket.AF_INET6:
    377                  self.log.debug("pre_parse: IPv6 %s" % (socket.inet_ntop(socket.AF_INET6, ipv6_socket_address.addr)))
    378              elif ipv6_socket_address.family == socket.AF_INET:
    379                  ipv4_socket_address = c.cast(transport_data, c.POINTER(sockaddr_in)).contents
    380                  self.log.debug("pre_parse: IPv4 %s" % socket.inet_ntop(socket.AF_INET, ipv4_socket_address.addr))
    381              else:
    382                  self.log.debug("pre_parse: unexpected address family: %s" % ipv6_socket_address.family)
    383          return 1
    384
    385 -    def receiveTrap(self, pdu):
    386          """
    387          Accept a packet from the network and spin off a Twisted
    388          deferred to handle the packet.
    389
    390          @param pdu: Net-SNMP object
    391          @type pdu: netsnmp_pdu object
    392          """
    393          if pdu.version not in (SNMPv1, SNMPv2, SNMPv3):
    394              self.log.error("Unable to handle trap version %d", pdu.version)
    395              return
    396          if pdu.transport_data is None:
    397              self.log.error("PDU does not contain transport data")
    398              return
    399
    400          ipv6_socket_address = c.cast(pdu.transport_data, c.POINTER(sockaddr_in6)).contents
    401          if ipv6_socket_address.family == socket.AF_INET6:
    402              if pdu.transport_data_length < c.sizeof(sockaddr_in6):
    403                  self.log.error("PDU transport data is too small for sockaddr_in6 struct.")
    404                  return
    405              ip_address = self.getPacketIp(ipv6_socket_address.addr)
    406          elif ipv6_socket_address.family == socket.AF_INET:
    407              if pdu.transport_data_length < c.sizeof(sockaddr_in):
    408                  self.log.error("PDU transport data is too small for sockaddr_in struct.")
    409                  return
    410              ipv4_socket_address = c.cast(pdu.transport_data, c.POINTER(sockaddr_in)).contents
    411              ip_address = '.'.join(str(i) for i in ipv4_socket_address.addr)
    412          else:
    413              self.log.error("Got a packet with unrecognized network family: %s", ipv6_socket_address.family)
    414              return
    415
    416          port = socket.ntohs(ipv6_socket_address.port)
    417          self.log.debug( "Received packet from %s at port %s" % (ip_address, port) )
    418          self.processPacket(ip_address, port, pdu, time.time())
    419
    420 -    def getPacketIp(self, addr):
    421          """
    422          For IPv4, convert a pointer to 4 bytes to a dotted-ip-address
    423          For IPv6, convert a pointer to 16 bytes to a canonical IPv6 address.
    424          """
    425
    426          def _gen_byte_pairs():
    427              for left, right in zip(addr[::2], addr[1::2]):
    428                  yield "%.2x%.2x" % (left, right)
    429
    430          v4_mapped_prefix = [0x00] * 10 + [0xff] * 2
    431          if addr[:len(v4_mapped_prefix)] == v4_mapped_prefix:
    432              ip_address = '.'.join(str(i) for i in addr[-4:])
    433          else:
    434              try:
    435                  basic_v6_address = ':'.join(_gen_byte_pairs())
    436                  ip_address = str(IPAddress(basic_v6_address, 6))
    437              except ValueError:
    438                  self.log.warn("The IPv6 address is incorrect: %s", addr[:])
    439                  ip_address = "::"
    440          return ip_address
    441
    442 -    def processPacket(self, ip_address, port, pdu, ts):
    443          """
    444          Wrapper around asyncHandleTrap to process the provided packet.
    445
    446          @param pdu: Net-SNMP object
    447          @type pdu: netsnmp_pdu object
    448          @param ts: time stamp
    449          @type ts: datetime
    450          """
    451          # At the end of this callback, pdu will be deleted, so copy it
    452          # for asynchronous processing
    453          dup = netsnmp.lib.snmp_clone_pdu(c.byref(pdu))
    454          if not dup:
    455              self.log.error("Could not clone PDU for asynchronous processing")
    456              return
    457
    458          def cleanup(result):
    459              """
    460              Twisted callback to delete a previous memory allocation
    461
    462              @param result: Net-SNMP object
    463              @type result: netsnmp_pdu object
    464              @return: the result parameter
    465              @rtype: binary
    466              """
    467              netsnmp.lib.snmp_free_pdu(dup)
    468              return result
    469
    470          d = defer.maybeDeferred(self.asyncHandleTrap, (ip_address, port), dup.contents, ts)
    471          d.addBoth(cleanup)
    472
    473 -    def _value_from_dateandtime(self, value):
    474          """
    475          Tries converting a DateAndTime value to a printable string.
    476
    477          A date-time specification.
    478          field  octets  contents                  range
    479          -----  ------  --------                  -----
    480          1      1-2     year*                     0..65536
    481          2        3     month                     1..12
    482          3        4     day                       1..31
    483          4        5     hour                      0..23
    484          5        6     minutes                   0..59
    485          6        7     seconds                   0..60
    486                        (use 60 for leap-second)
    487          7        8     deci-seconds              0..9
    488          8        9     direction from UTC        '+' / '-'
    489          9       10     hours from UTC*           0..13
    490          10      11     minutes from UTC          0..59
    491          """
    492          strval = None
    493          vallen = len(value)
    494          if vallen == 8 or (vallen == 11 and value[8] in ('+','-')):
    495              (year, mon, day, hour, mins, secs, dsecs) = unpack(">HBBBBBB", value[:8])
    496              # Ensure valid date representation
    497              if mon < 1 or mon > 12:
    498                  return None
    499              if day < 1 or day > 31:
    500                  return None
    501              if hour < 0 or hour > 23:
    502                  return None
    503              if mins > 60:
    504                  return None
    505              if secs > 60:
    506                  return None
    507              if dsecs > 9:
    508                  return None
    509              if vallen == 11:
    510                  utc_dir = value[8]
    511                  (utc_hours, utc_mins) = unpack(">BB", value[9:])
    512              else:
    513                  tz_mins = time.timezone / 60
    514                  if tz_mins < 0:
    515                      utc_dir = '-'
    516                      tz_mins = -tz_mins
    517                  else:
    518                      utc_dir = '+'
    519                  utc_hours = tz_mins / 60
    520                  utc_mins = tz_mins % 60
    521              strval = "%04d-%02d-%02dT%02d:%02d:%02d.%d00%s%02d:%02d" % (year,
    522                  mon, day, hour, mins, secs, dsecs, utc_dir, utc_hours, utc_mins)
    523
    524          return strval
    525
    526 -    def _convert_value(self, value):
    527          if not isinstance(value, basestring):
    528              return value
    529          try:
    530              value.decode('utf8')
    531              return value
    532          except UnicodeDecodeError:
    533              # Try converting to a date
    534              decoded = self._value_from_dateandtime(value)
    535              if not decoded:
    536                  decoded = 'BASE64:' + base64.b64encode(value)
    537              return decoded
    538
    539 -    def snmpInform(self, addr, pdu):
    540          """
    541          A SNMP trap can request that the trap recipient return back a response.
    542          This is where we do that.
    543          """
    544          reply = netsnmp.lib.snmp_clone_pdu(c.byref(pdu))
    545          if not reply:
    546              self.log.error("Could not clone PDU for INFORM response")
    547              raise RuntimeError("Cannot respond to INFORM PDU")
    548          reply.contents.command = netsnmp.SNMP_MSG_RESPONSE
    549          reply.contents.errstat = 0
    550          reply.contents.errindex = 0
    551
    552          # FIXME: might need to add udp6 for IPv6 addresses
    553          sess = netsnmp.Session(peername='%s:%d' % tuple(addr),
    554                                 version=pdu.version)
    555          sess.open()
    556          if not netsnmp.lib.snmp_send(sess.sess, reply):
    557              netsnmp.lib.snmp_sess_perror("Unable to send inform PDU",
    558                                           self.session.sess)
    559              netsnmp.lib.snmp_free_pdu(reply)
    560          sess.close()
    561
    562 -    def decodeSnmpv1(self, addr, pdu):
    563          eventType = 'unknown'
    564          result = {}
    565
    566          variables = self.getResult(pdu)
    567
    568          # Sometimes the agent_addr is useless.
    569          # Use addr[0] unchanged in this case.
    570          # Note that SNMPv1 packets *cannot* come in via IPv6
    571          new_addr = '.'.join(map(str, [pdu.agent_addr[i] for i in range(4)]))
    572          result["device"] = addr[0] if new_addr == "0.0.0.0" else new_addr
    573
    574          enterprise = self.getEnterpriseString(pdu)
    575          eventType = self.oid2name(
    576                  enterprise, exactMatch=False, strip=False)
    577          generic = pdu.trap_type
    578          specific = pdu.specific_type
    579
    580          # Try an exact match with a .0. inserted between enterprise and
    581          # specific OID. It seems that MIBs frequently expect this .0.
    582          # to exist, but the device's don't send it in the trap.
    583          result["oid"] = "%s.0.%d" % (enterprise, specific)
    584          name = self.oid2name(result["oid"], exactMatch=True, strip=False)
    585
    586          # If we didn't get a match with the .0. inserted we will try
    587          # resolving with the .0. inserted and allow partial matches.
    588          if name == result["oid"]:
    589              result["oid"] = "%s.%d" % (enterprise, specific)
    590              name = self.oid2name(result["oid"], exactMatch=False, strip=False)
    591
    592          # Look for the standard trap types and decode them without
    593          # relying on any MIBs being loaded.
    594          eventType = {
    595              0: 'snmp_coldStart',
    596              1: 'snmp_warmStart',
    597              2: 'snmp_linkDown',
    598              3: 'snmp_linkUp',
    599              4: 'snmp_authenticationFailure',
    600              5: 'snmp_egpNeighorLoss',
    601              6: name,
    602          }.get(generic, name)
    603
    604          # Decode all variable bindings. Allow partial matches and strip
    605          # off any index values.
    606          for vb_oid, vb_value in variables:
    607              vb_value = self._convert_value(vb_value)
    608              vb_oid = '.'.join(map(str, vb_oid))
    609
    610              # Add a detail for the variable binding.
    611              r = self.oid2name(vb_oid, exactMatch=False, strip=False)
    612              result[r] = vb_value
    613
    614              # Add a detail for the index-stripped variable binding.
    615              r = self.oid2name(vb_oid, exactMatch=False, strip=True)
    616              result[r] = vb_value
    617          return eventType, result
    618
    619 -    def decodeSnmpv2(self, addr, pdu):
    620          eventType = 'unknown'
    621          result = {"oid": "", "device": addr[0]}
    622
    623          variables = self.getResult(pdu)
    624          for vb_oid, vb_value in variables:
    625              vb_value = self._convert_value(vb_value)
    626              vb_oid = '.'.join(map(str, vb_oid))
    627              # SNMPv2-MIB/snmpTrapOID
    628              if vb_oid == '1.3.6.1.6.3.1.1.4.1.0':
    629                  result["oid"] = '.'.join(map(str, vb_value))
    630                  eventType = self.oid2name(
    631                          vb_value, exactMatch=False, strip=False)
    632              else:
    633                  # Add a detail for the variable binding.
    634                  r = self.oid2name(vb_oid, exactMatch=False, strip=False)
    635                  result[r] = vb_value
    636                  # Add a detail for the index-stripped variable binding.
    637                  r = self.oid2name(vb_oid, exactMatch=False, strip=True)
    638                  result[r] = vb_value
    639          if eventType in ["linkUp", "linkDown"]:
    640              eventType = "snmp_" + eventType
    641          return eventType, result
    642
    643 -    def asyncHandleTrap(self, addr, pdu, startProcessTime):
    644          """
    645          Twisted callback to process a trap
    646
    647          @param addr: packet-sending host's IP address, port info
    648          @type addr: ( host-ip, port)
    649          @param pdu: Net-SNMP object
    650          @type pdu: netsnmp_pdu object
    651          @param startProcessTime: time stamp
    652          @type startProcessTime: datetime
    653          @return: Twisted deferred object
    654          @rtype: Twisted deferred object
    655          """
    656          self.capturePacket(addr[0], addr, pdu)
    657
    658          # Some misbehaving agents will send SNMPv1 traps contained within
    659          # an SNMPv2c PDU. So we can't trust tpdu.version to determine what
    660          # version trap exists within the PDU. We need to assume that a
    661          # PDU contains an SNMPv1 trap if the enterprise_length is greater
    662          # than zero in addition to the PDU version being 0.
    663          if pdu.version == SNMPv1 or pdu.enterprise_length > 0:
    664              eventType, result = self.decodeSnmpv1(addr, pdu)
    665          elif pdu.version in (SNMPv2, SNMPv3):
    666              eventType, result = self.decodeSnmpv2(addr, pdu)
    667          else:
    668              self.log.error("Unable to handle trap version %d", pdu.version)
    669              return
    670
    671          summary = 'snmp trap %s' % eventType
    672          self.log.debug(summary)
    673          community = self.getCommunity(pdu)
    674          result.setdefault('component', '')
    675          result.setdefault('eventClassKey', eventType)
    676          result.setdefault('eventGroup', 'trap')
    677          result.setdefault('severity', SEVERITY_WARNING)
    678          result.setdefault('summary', summary)
    679          result.setdefault('community', community)
    680          result.setdefault('firstTime', startProcessTime)
    681          result.setdefault('lastTime', startProcessTime)
    682          result.setdefault('monitor', self.options.monitor)
    683          self._eventService.sendEvent(result)
    684          self.stats.add(time.time() - startProcessTime)
    685
    686          if self.isReplaying():
    687              self.replayed += 1
    688              # Don't attempt to respond back if we're replaying packets
    689              return
    690
    691          if pdu.command == netsnmp.SNMP_MSG_INFORM:
    692              self.snmpInform(addr, pdu)
    693
    694 -    def displayStatistics(self):
    695          totalTime, totalEvents, maxTime = self.stats.report()
    696          display = "%d events processed in %.2f seconds" % (
    697                        totalEvents,
    698                        totalTime)
    699          if totalEvents > 0:
    700              display += """
    701  %.5f average seconds per event
    702  Maximum processing time for one event was %.5f""" % (
    703                         (totalTime / totalEvents), maxTime)
    704          return display
    705
    706 -    def cleanup(self):
    707          if self.session:
    708              self.session.close()
    709          status = self.displayStatistics()
    710          self.log.info(status)
    711
    712
    713 -class MibConfigTask(ObservableMixin):
    714      """
    715      Receive a configuration object containing MIBs and update the
    716      mapping of OIDs to names.
    717      """
    718      zope.interface.implements(IScheduledTask)
    719
    720 -    def __init__(self, taskName, configId,
    721                   scheduleIntervalSeconds=3600, taskConfig=None):
    722          super(MibConfigTask, self).__init__()
    723
    724          # Needed for ZCA interface contract
    725          self.name = taskName
    726          self.configId = configId
    727          self.state = TaskStates.STATE_IDLE
    728          self.interval = scheduleIntervalSeconds
    729          self._preferences = taskConfig
    730          self._daemon = zope.component.getUtility(ICollector)
    731
    732          self._daemon.oidMap = self._preferences.oidMap
    733
    734 -    def doTask(self):
    735          return defer.succeed("Already updated OID -> name mappings...")
    736
    737 -    def cleanup(self):
    738          pass
    739
    740
    741 -class TrapDaemon(CollectorDaemon):
    742
    743      _frameworkFactoryName = "nosip"
    744
    745 -    def runPostConfigTasks(self, result=None):
    746          # 1) super sets self._prefs.task with the call to postStartupTasks
    747          # 2) call remote createAllUsers
    748          # 3) service in turn walks DeviceClass tree and calls remote_createUser
    749          #      which needs self._prefs.task to be set
    750          CollectorDaemon.runPostConfigTasks(self, result)
    751          if not isinstance(result, Failure):
    752              service = self.getRemoteConfigServiceProxy()
    753              service.callRemote("createAllUsers")
    754
    755 -    def remote_createUser(self, user):
    756          log.debug("TrapDaemon.remote_createUser {0}".format(user))
    757          task = self.preferences.task
    758          if task is not None:
    759              task.session.create_users([user])
    760
    761  if __name__=='__main__':
    762      myPreferences = SnmpTrapPreferences()
    763      myTaskFactory = SimpleTaskFactory(MibConfigTask)
    764      myTaskSplitter = SimpleTaskSplitter(myTaskFactory)
    765      daemon = TrapDaemon(myPreferences, myTaskSplitter)
    766      daemon.run()
    767