1
2
3
4
5
6
7
8
9
10
11 __doc__ = """NmapPingTask
12
13 Pings a all devices in the current device list.
14 """
15
16 import logging
17 log = logging.getLogger("zen.NmapPingTask")
18 import tempfile
19 import subprocess
20 from twisted.internet import utils
21 from twisted.internet import task as twistedTask
22 from twisted.internet import defer
23 from twisted.internet import reactor
24 import os.path
25 from cStringIO import StringIO
26 import stat
27
28 import Globals
29 from zope import interface
30 from zope import component
31 from Products import ZenCollector
32 from Products.ZenCollector.tasks import BaseTask, TaskStates
33 from Products.ZenCollector import interfaces
34 from Products.ZenCollector.tasks import SimpleTaskFactory
35 from Products.ZenUtils.Utils import zenPath
36 from Products.ZenEvents import ZenEventClasses
37 from zenoss.protocols.protobufs import zep_pb2 as events
38
39
40 from Products.ZenStatus import PingTask
41 from Products.ZenStatus.ping.CmdPingTask import CmdPingTask
42 from PingResult import PingResult, parseNmapXmlToDict
43 from Products.ZenStatus.PingCollectionPreferences import PingCollectionPreferences
44 from Products.ZenStatus.interfaces import IPingTaskFactory
45 from Products.ZenStatus import nmap
46
47 _NMAP_BINARY = zenPath("bin/nmap")
48
49
50 _CLEAR = 0
51 _CRITICAL = 5
52 _WARNING = 3
53
54
55 _SENDEVENT_YIELD_INTERVAL = 100
56
57
58
59 _NEVER_INTERVAL = 60 * 60 * 24 * 365 * 100
74
76 """
77 A Factory to create PingTasks that do not run. This allows NmapPingTask
78 to use the created PingTasks as placeholders for configuration.
79 """
80 interface.implements(IPingTaskFactory)
81
84
86
87 if self.config.monitoredIps[0].ipVersion == 6:
88
89 log.debug("Creating an IPv6 task: %s", self.config.monitoredIps[0].ip)
90 task = CmdPingTask(
91 self.name,
92 self.configId,
93 self.interval,
94 self.config,
95 )
96 else:
97 log.debug("Creating an IPv4 task: %s", self.config.monitoredIps[0].ip)
98 task = PingTask(
99 self.name,
100 self.configId,
101 self.interval,
102 self.config,
103 )
104
105 task.pauseOnScheduled = True
106 task.interval = _NEVER_INTERVAL
107 return task
108
110 self.name = None
111 self.configId = None
112 self.interval = None
113 self.config = None
114
116 interface.implements(ZenCollector.interfaces.IScheduledTask)
117 """
118 NmapPingTask pings all PingTasks using using nmap.
119 """
120
121 - def __init__(self,
122 taskName, configId,
123 scheduleIntervalSeconds=60,
124 taskConfig=None):
125 """
126 @param deviceId: the Zenoss deviceId to watch
127 @type deviceId: string
128 @param taskName: the unique identifier for this task
129 @type taskName: string
130 @param scheduleIntervalSeconds: the interval at which this task will be
131 collected
132 @type scheduleIntervalSeconds: int
133 @param taskConfig: the configuration for this task
134 """
135 super(NmapPingTask, self).__init__(
136 taskName, configId,
137 scheduleIntervalSeconds, taskConfig=None
138 )
139
140
141 self.name = taskName
142 self.configId = configId
143 self.state = TaskStates.STATE_IDLE
144 self.interval = scheduleIntervalSeconds
145
146 if taskConfig is None:
147 raise TypeError("taskConfig cannot be None")
148 self._preferences = taskConfig
149
150 self._daemon = component.getUtility(ZenCollector.interfaces.ICollector)
151 self._dataService = component.queryUtility(ZenCollector.interfaces.IDataService)
152 self._eventService = component.queryUtility(ZenCollector.interfaces.IEventService)
153
154 self._pings = 0
155 self._nmapPresent = False
156 self._nmapIsSuid = False
157 self.collectorName = self._daemon._prefs.collectorName
158
159
172
192
194 """
195 Detect that nmap is set SUID
196 """
197 if self._nmapPresent and self._nmapIsSuid == False:
198
199 attribs = os.stat(_NMAP_BINARY)
200
201 self._nmapIsSuid = (attribs.st_uid == 0) and (attribs.st_mode & stat.S_ISUID)
202 if self._nmapIsSuid is False:
203 raise nmap.NmapNotSuid()
204 self._sendNmapNotSuid()
205
207 """
208 Send/Clear event to show that nmap is set SUID.
209 """
210 resolution = None
211 if self._nmapIsSuid:
212 msg = "nmap is set SUID"
213 severity = _CLEAR
214 else:
215 msg = "nmap is NOT SUID: %s" % _NMAP_BINARY
216 try:
217 import socket
218 cmd = subprocess.check_output('echo "chown root.`id -gn` $ZENHOME/bin/nmap && chmod u+s $ZENHOME/bin/nmap"', shell=True).strip()
219 resolution = "Log on to %s and execute the following as root: %s" % (socket.getfqdn(), cmd)
220 except Exception as ex:
221 log.exception("There was an error generating a help message related to sudo nmap.")
222 severity = _CRITICAL
223 evt = dict(
224 device=self.collectorName,
225 eventClass=ZenEventClasses.Status_Ping,
226 eventGroup='Ping',
227 eventKey="nmap_suid",
228 severity=severity,
229 summary=msg,
230 )
231 if resolution:
232 evt['resolution'] = resolution
233 self._eventService.sendEvent(evt)
234
254
255 @defer.inlineCallbacks
256 - def _executeNmapCmd(self, inputFileFilename, traceroute=False, outputType='xml'):
257 """
258 Execute nmap and return it's output.
259 """
260 args = []
261 args.extend(["-iL", inputFileFilename])
262 args.append("-sn")
263 args.append("-PE")
264 args.append("-n")
265 args.append("--privileged")
266 args.append("--send-ip")
267
268
269 args.extend(["--initial-rtt-timeout", "%.1fs" % self._preferences.pingTimeOut])
270 args.extend(["--min-rtt-timeout", "%.1fs" % self._preferences.pingTimeOut])
271 args.extend(["--max-retries", "0"])
272
273 if traceroute:
274 args.append("--traceroute")
275
276 if outputType == 'xml':
277 args.extend(["-oX", '-'])
278 else:
279 raise ValueError("Unsupported nmap output type: %s" % outputType)
280
281
282 if log.isEnabledFor(logging.DEBUG):
283 log.debug("executing nmap %s", " ".join(args))
284 out, err, exitCode = yield utils.getProcessOutputAndValue(
285 _NMAP_BINARY, args)
286
287 if exitCode != 0:
288 input = open(inputFileFilename).read()
289 log.debug("input file: %s", input)
290 log.debug("stdout: %s", out)
291 log.debug("stderr: %s", err)
292 raise nmap.NmapExecutionError(
293 exitCode=exitCode, stdout=out, stderr=err, args=args)
294
295 try:
296 nmapResults = parseNmapXmlToDict(StringIO(out))
297 defer.returnValue(nmapResults)
298 except Exception as e:
299 input = open(inputFileFilename).read()
300 log.debug("input file: %s", input)
301 log.debug("stdout: %s", out)
302 log.debug("stderr: %s", err)
303 log.exception(e)
304 raise nmap.NmapExecutionError(
305 exitCode=exitCode, stdout=out, stderr=err, args=args)
306
307 @defer.inlineCallbacks
333
334
336 """
337 Iterate the daemons task list and find PingTask tasks that are IPV4.
338 """
339 tasks = self._daemon._scheduler._tasks
340 pingTasks = {}
341 for configName, task in tasks.iteritems():
342 if isinstance(task.task, PingTask):
343 if task.task.config.ipVersion == 4:
344 pingTasks[configName] = task.task
345 return pingTasks
346
347 @defer.inlineCallbacks
349 """
350 Find the IPs, ping/traceroute, parse, correlate, and send events.
351 """
352
353 ipTasks = self._getPingTasks()
354 if len(ipTasks) == 0:
355 log.debug("No ips to ping!")
356 raise StopIteration()
357
358 with tempfile.NamedTemporaryFile(prefix='zenping_nmap_') as tfile:
359 ips = []
360 for taskName, ipTask in ipTasks.iteritems():
361 ips.append(ipTask.config.ip)
362 ipTask.resetPingResult()
363 ips.sort()
364 for ip in ips:
365 tfile.write("%s\n" % ip)
366 tfile.flush()
367
368
369 tracerouteInterval = self._daemon.options.tracerouteInterval
370
371
372 doTraceroute = False
373 if tracerouteInterval > 0:
374 if self._pings == 0 or (self._pings % tracerouteInterval) == 0:
375 doTraceroute = True
376
377 import time
378 for attempt in range(0, self._daemon._prefs.pingTries):
379
380 start = time.time()
381 results = yield self._executeNmapCmd(tfile.name, doTraceroute)
382 elapsed = time.time() - start
383 log.debug("Nmap execution took %f seconds", elapsed)
384
385
386 doTraceroute = False
387
388
389 for taskName, ipTask in ipTasks.iteritems():
390 ip = ipTask.config.ip
391 if ip in results:
392 result = results[ip]
393 ipTask.logPingResult(result)
394 else:
395
396 ipTask.logPingResult(PingResult(ip, isUp=False))
397
398 downTasks = {}
399 i = 0
400 for taskName, ipTask in ipTasks.iteritems():
401 i += 1
402 if ipTask.isUp:
403 log.debug("%s is up!", ipTask.config.ip)
404 ipTask.sendPingUp()
405 ipTask.storeResults()
406 else:
407 log.debug("%s is down", ipTask.config.ip)
408 downTasks[ipTask.config.ip] = ipTask
409 ipTask.storeResults()
410
411
412 if i % _SENDEVENT_YIELD_INTERVAL:
413 yield twistedTask.deferLater(reactor, 0, lambda: None)
414
415 yield self._correlate(downTasks)
416 self._nmapExecution()
417
418 @defer.inlineCallbacks
420 """
421 Correlate ping down events.
422
423 This simple correlator will take a list of PingTasks that are in the
424 down state. It loops through the list and the last known trace route
425 for each of the ip's. For every hop in the traceroute (starting from the
426 collector to the ip in question), the hop's ip is searched for in
427 downTasks. If it's found, then this collector was also monitoring the
428 source of the problem.
429
430 Note: this does not take in to account multiple routes to the ip in
431 question. It uses only the last known traceroute as given by nmap which
432 will not have routing loops and hosts that block icmp.
433 """
434
435
436 i = 0
437 for currentIp, ipTask in downTasks.iteritems():
438 i += 1
439
440 for hop in ipTask.trace:
441
442
443 if hop.ip in downTasks and hop.ip != currentIp:
444
445 rootCause = downTasks[hop.ip]
446 ipTask.sendPingDown(rootCause=rootCause)
447 break
448 else:
449
450 ipTask.sendPingDown()
451
452
453 if i % _SENDEVENT_YIELD_INTERVAL:
454 yield twistedTask.deferLater(reactor, 0, lambda: None, )
455
456
457
458
460 """
461 Called by the collector framework scheduler, and allows us to
462 see how each task is doing.
463 """
464 return ''
465