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