1
2
3
4
5
6
7
8
9
10
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 cPickle
24 from exceptions import EOFError, IOError
25
26
27 import ctypes as c
28
29 import Globals
30
31 from EventServer import EventServer
32
33 from pynetsnmp import netsnmp, twistedsnmp
34
35 from twisted.internet import defer, reactor
36 from Products.ZenUtils.Driver import drive
37 from Products.ZenUtils.captureReplay import CaptureReplay
38
39
40
41
42 family = [('family', c.c_ushort)]
43 if sys.platform == 'darwin':
44 family = [('len', c.c_ubyte), ('family', c.c_ubyte)]
45
47 _fields_ = family + [
48 ('port', c.c_ubyte * 2),
49 ('addr', c.c_ubyte * 4)
50 ];
51
52
53 netsnmp.lib.snmp_clone_pdu.restype = netsnmp.netsnmp_pdu_p
54
55 TRAP_PORT = 162
56 try:
57 TRAP_PORT = socket.getservbyname('snmptrap', 'udp')
58 except socket.error:
59 pass
60
62 "Convert a pointer to an array of longs to an oid"
63 return '.'.join([str(ptr[i]) for i in range(length)])
64
66 "Convert a pointer to 4 bytes to a dotted-ip-address"
67 return '.'.join([str(ptr[i]) for i in range(4)])
68
69
71 """
72 A fake object to make packet replaying feasible.
73 """
76
77
78 -class ZenTrap(EventServer, CaptureReplay):
79 """
80 Listen for SNMP traps and turn them into events
81 Connects to the EventService service in zenhub.
82 """
83
84 name = 'zentrap'
85
87 EventServer.__init__(self)
88
89 self.oidCache = {}
90
91
92 self.processCaptureReplayOptions()
93
94 if not self.options.useFileDescriptor and self.options.trapport < 1024:
95 self.openPrivilegedPort('--listen', '--proto=udp',
96 '--port=%s:%d' % (self.options.listenip,
97 self.options.trapport))
98 self.session = netsnmp.Session()
99 if self.options.useFileDescriptor is not None:
100 fileno = int(self.options.useFileDescriptor)
101
102 self.session.awaitTraps('%s:1162' % self.options.listenip, fileno)
103 else:
104 self.session.awaitTraps('%s:%d' % (
105 self.options.listenip, self.options.trapport))
106 self.session.callback = self.receiveTrap
107
108 twistedsnmp.updateReactor()
109
110
112 """
113 Get the enterprise string from the PDU or replayed packet
114
115 @param pdu: raw packet
116 @type pdu: binary
117 @return: enterprise string
118 @rtype: string
119 """
120 if hasattr(pdu, "fake"):
121 enterprise = pdu.enterprise
122 else:
123 enterprise = lp2oid(pdu.enterprise, pdu.enterprise_length)
124 return enterprise
125
126
128 """
129 Get the values from the PDU or replayed packet
130
131 @param pdu: raw packet
132 @type pdu: binary
133 @return: variables from the PDU or Fake packet
134 @rtype: dictionary
135 """
136 if hasattr(pdu, "fake"):
137 variables = pdu.variables
138 else:
139 variables = netsnmp.getResult(pdu)
140 return variables
141
142
143
145 """
146 Get the communitry string from the PDU or replayed packet
147
148 @param pdu: raw packet
149 @type pdu: binary
150 @return: SNMP community
151 @rtype: string
152 """
153 community = ''
154 if hasattr(pdu, "fake"):
155 community = pdu.community
156 elif pdu.community_len:
157 community = c.string_at(pdu.community, pdu.community_len)
158
159 return community
160
161
163 """
164 Store the raw packet for later examination and troubleshooting.
165
166 @param addr: packet-sending host's IP address and port
167 @type addr: (string, number)
168 @param pdu: raw packet
169 @type pdu: binary
170 @return: Python FakePacket object
171 @rtype: Python FakePacket object
172 """
173 packet = FakePacket()
174 packet.version = pdu.version
175 packet.host = addr[0]
176 packet.port = addr[1]
177 packet.variables = netsnmp.getResult(pdu)
178 packet.community = ''
179
180
181 if pdu.version == 0:
182 packet.agent_addr = [pdu.agent_addr[i] for i in range(4)]
183 packet.trap_type = pdu.trap_type
184 packet.specific_type = pdu.specific_type
185 packet.enterprise = self.getEnterpriseString(pdu)
186 packet.community = self.getCommunity(pdu)
187
188 return packet
189
190
192 """
193 Replay a captured packet
194
195 @param pdu: raw packet
196 @type pdu: binary
197 """
198 ts = time.time()
199 d = self.asyncHandleTrap([pdu.host, pdu.port], pdu, ts)
200
201
202 - def oid2name(self, oid, exactMatch=True, strip=False):
203 """
204 Get OID name from cache or ZenHub
205
206 @param oid: SNMP Object IDentifier
207 @type oid: string
208 @param exactMatch: find the full OID or don't match
209 @type exactMatch: boolean
210 @param strip: show what matched, or matched + numeric OID remainder
211 @type strip: boolean
212 @return: Twisted deferred object
213 @rtype: Twisted deferred object
214 """
215 if type(oid) == type(()):
216 oid = '.'.join(map(str, oid))
217 cacheKey = "%s:%r:%r" % (oid, exactMatch, strip)
218 if self.oidCache.has_key(cacheKey):
219 return defer.succeed(self.oidCache[cacheKey])
220
221 self.log.debug("OID cache miss on %s (exactMatch=%r, strip=%r)" % (
222 oid, exactMatch, strip))
223 d = self.model().callRemote('oid2name', oid, exactMatch, strip)
224
225 def cache(name, key):
226 """
227 Twisted callback to cache and return the name
228
229 @param name: human-readable-name form of OID
230 @type name: string
231 @param key: key of OID and params
232 @type key: string
233 @return: the name parameter
234 @rtype: string
235 """
236 self.oidCache[key] = name
237 return name
238
239 d.addCallback(cache, cacheKey)
240 return d
241
242
244 """
245 Accept a packet from the network and spin off a Twisted
246 deferred to handle the packet.
247
248 @param pdu: Net-SNMP object
249 @type pdu: netsnmp_pdu object
250 """
251 ts = time.time()
252
253
254 if pdu.sessid != 0: return
255
256 if pdu.version not in [ 0, 1 ]:
257 self.log.error("Unable to handle trap version %d", pdu.version)
258 return
259
260
261
262
263 transport = c.cast(pdu.transport_data, c.POINTER(sockaddr_in))
264 if not transport: return
265 transport = transport.contents
266
267
268 if transport.family != socket.AF_INET: return
269
270 addr = [bp2ip(transport.addr),
271 transport.port[0] << 8 | transport.port[1]]
272
273 self.log.debug( "Received packet from %s at port %s" % (addr[0], addr[1]) )
274 self.processPacket(addr, pdu, ts)
275
276
278 """
279 Wrapper around asyncHandleTrap to process the provided packet.
280
281 @param addr: packet-sending host's IP address, port info
282 @type addr: ( host-ip, port)
283 @param pdu: Net-SNMP object
284 @type pdu: netsnmp_pdu object
285 @param ts: time stamp
286 @type ts: datetime
287 """
288
289
290 dup = netsnmp.lib.snmp_clone_pdu(c.addressof(pdu))
291 if not dup:
292 self.log.error("Could not clone PDU for asynchronous processing")
293 return
294
295 def cleanup(result):
296 """
297 Twisted callback to delete a previous memory allocation
298
299 @param result: Net-SNMP object
300 @type result: netsnmp_pdu object
301 @return: the result parameter
302 @rtype: binary
303 """
304 netsnmp.lib.snmp_free_pdu(dup)
305 return result
306
307 d = self.asyncHandleTrap(addr, dup.contents, ts)
308 d.addBoth(cleanup)
309
310
312 """
313 Twisted callback to process a trap
314
315 @param addr: packet-sending host's IP address, port info
316 @type addr: ( host-ip, port)
317 @param pdu: Net-SNMP object
318 @type pdu: netsnmp_pdu object
319 @param ts: time stamp
320 @type ts: datetime
321 @return: Twisted deferred object
322 @rtype: Twisted deferred object
323 """
324 def inner(driver):
325 """
326 Generator function that actually processes the packet
327
328 @param driver: Twisted deferred object
329 @type driver: Twisted deferred object
330 @return: Twisted deferred object
331 @rtype: Twisted deferred object
332 """
333 self.capturePacket( addr[0], addr, pdu)
334
335 eventType = 'unknown'
336 result = {}
337 if pdu.version == 1:
338
339 variables = self.getResult(pdu)
340 for oid, value in variables:
341 oid = '.'.join(map(str, oid))
342
343 if oid == '1.3.6.1.6.3.1.1.4.1.0':
344 yield self.oid2name(value, exactMatch=False, strip=False)
345 eventType = driver.next()
346 else:
347
348 yield self.oid2name(oid, exactMatch=False, strip=False)
349 result[driver.next()] = value
350
351 yield self.oid2name(oid, exactMatch=False, strip=True)
352 result[driver.next()] = value
353
354 elif pdu.version == 0:
355
356 variables = self.getResult(pdu)
357 addr[0] = '.'.join(map(str, [pdu.agent_addr[i] for i in range(4)]))
358 enterprise = self.getEnterpriseString(pdu)
359 eventType = driver.next()
360 generic = pdu.trap_type
361 specific = pdu.specific_type
362
363
364
365
366 oid = "%s.0.%d" % (enterprise, specific)
367 yield self.oid2name(oid, exactMatch=True, strip=False)
368 name = driver.next()
369
370
371
372 if name == oid:
373 oid = "%s.%d" % (enterprise, specific)
374 yield self.oid2name(oid, exactMatch=False, strip=False)
375 name = driver.next()
376
377
378
379 eventType = {
380 0: 'snmp_coldStart',
381 1: 'snmp_warmStart',
382 2: 'snmp_linkDown',
383 3: 'snmp_linkUp',
384 4: 'snmp_authenticationFailure',
385 5: 'snmp_egpNeighorLoss',
386 6: name,
387 }.get(generic, name)
388
389
390
391 for oid, value in variables:
392 oid = '.'.join(map(str, oid))
393
394 yield self.oid2name(oid, exactMatch=False, strip=False)
395 result[driver.next()] = value
396
397 yield self.oid2name(oid, exactMatch=False, strip=True)
398 result[driver.next()] = value
399 else:
400 self.log.error("Unable to handle trap version %d", pdu.version)
401 return
402
403 summary = 'snmp trap %s' % eventType
404 self.log.debug(summary)
405 community = self.getCommunity(pdu)
406 result['oid'] = oid
407 result['device'] = addr[0]
408 result.setdefault('component', '')
409 result.setdefault('eventClassKey', eventType)
410 result.setdefault('eventGroup', 'trap')
411 result.setdefault('severity', 3)
412 result.setdefault('summary', summary)
413 result.setdefault('community', community)
414 result.setdefault('firstTime', ts)
415 result.setdefault('lastTime', ts)
416 result.setdefault('monitor', self.options.monitor)
417 self.sendEvent(result)
418
419
420 if len(self.options.replayFilePrefix) > 0:
421 self.replayed += 1
422 return
423
424
425 if pdu.command == netsnmp.SNMP_MSG_INFORM:
426 reply = netsnmp.lib.snmp_clone_pdu(c.addressof(pdu))
427 if not reply:
428 self.log.error("Could not clone PDU for INFORM response")
429 raise RuntimeError("Cannot respond to INFORM PDU")
430 reply.contents.command = netsnmp.SNMP_MSG_RESPONSE
431 reply.contents.errstat = 0
432 reply.contents.errindex = 0
433 sess = netsnmp.Session(peername='%s:%d' % tuple(addr),
434 version=pdu.version)
435 sess.open()
436 if not netsnmp.lib.snmp_send(sess.sess, reply):
437 netsnmp.lib.snmp_sess_perror("Unable to send inform PDU",
438 self.session.sess)
439 netsnmp.lib.snmp_free_pdu(reply)
440 sess.close()
441 return drive(inner)
442
443
445 """
446 Command-line options to be supported
447 """
448 EventServer.buildOptions(self)
449 self.parser.add_option('--trapport', '-t',
450 dest='trapport', type='int', default=TRAP_PORT,
451 help="Listen for SNMP traps on this port rather than the default")
452 self.parser.add_option('--listenip',
453 dest='listenip', default='0.0.0.0',
454 help="IP address to listen on. Default is 0.0.0.0")
455 self.parser.add_option('--useFileDescriptor',
456 dest='useFileDescriptor',
457 type='int',
458 help=("Read from an existing connection "
459 " rather than opening a new port."),
460 default=None)
461
462 self.buildCaptureReplayOptions()
463
464
465 if __name__ == '__main__':
466 z = ZenTrap()
467 z.run()
468 z.report()
469