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

Source Code for Module Products.ZenEvents.zentrap

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