2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
10 # | Copyright Mathias Kettner 2014 mk@mathias-kettner.de |
11 # +------------------------------------------------------------------+
13 # This file is part of Check_MK.
14 # The official homepage is at http://mathias-kettner.de/check_mk.
16 # check_mk is free software; you can redistribute it and/or modify it
17 # under the terms of the GNU General Public License as published by
18 # the Free Software Foundation in version 2. check_mk is distributed
19 # in the hope that it will be useful, but WITHOUT ANY WARRANTY; with-
20 # out even the implied warranty of MERCHANTABILITY or FITNESS FOR A
21 # PARTICULAR PURPOSE. See the GNU General Public License for more de-
22 # tails. You should have received a copy of the GNU General Public
23 # License along with GNU Make; see the file COPYING. If not, write
24 # to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
25 # Boston, MA 02110-1301 USA.
36 from cmk
.exceptions
import MKGeneralException
39 import cmk_base
.console
as console
40 import cmk_base
.config
as config
41 import cmk_base
.ip_lookup
as ip_lookup
44 def do_scan_parents(hosts
):
47 for h
in config
.all_active_realhosts()
48 if config
.in_binary_hostlist(h
, config
.scanparent_hosts
)]
53 gateway_hosts
= set([])
55 if config
.max_num_processes
< 1:
56 config
.max_num_processes
= 1
58 outfilename
= cmk
.paths
.check_mk_config_dir
+ "/parents.mk"
60 if not traceroute_available():
61 raise MKGeneralException(
62 'The program "traceroute" was not found.\n'
63 'The parent scan needs this program.\n'
64 'Please install it and try again.')
66 if os
.path
.exists(outfilename
):
67 first_line
= file(outfilename
, "r").readline()
68 if not first_line
.startswith('# Automatically created by --scan-parents at'):
69 raise MKGeneralException("conf.d/parents.mk seems to be created manually.\n\n"
70 "The --scan-parents function would overwrite this file.\n"
71 "Please rename it to keep the configuration or delete "
72 "the file and try again.")
74 console
.output("Scanning for parents (%d processes)..." % config
.max_num_processes
)
77 while len(chunk
) < config
.max_num_processes
and len(hosts
) > 0:
80 # skip hosts that already have a parent
81 if config
.parents_of(host
):
82 console
.verbose("(manual parent) ")
86 gws
= scan_parents_of(chunk
)
88 for host
, (gw
, _unused_state
, _unused_ping_fails
, _unused_message
) in zip(chunk
, gws
):
90 gateway
, gateway_ip
, dns_name
= gw
91 if not gateway
: # create artificial host
95 gateway
= "gw-%s" % (gateway_ip
.replace(".", "-"))
96 if gateway
not in gateway_hosts
:
97 gateway_hosts
.add(gateway
)
98 parent_hosts
.append("%s|parent|ping" % gateway
)
99 parent_ips
[gateway
] = gateway_ip
100 if config
.monitoring_host
:
101 parent_rules
.append( (config
.monitoring_host
, [gateway
]) ) # make Nagios a parent of gw
102 parent_rules
.append( (gateway
, [host
]) )
103 elif host
!= config
.monitoring_host
and config
.monitoring_host
:
104 # make monitoring host the parent of all hosts without real parent
105 parent_rules
.append( (config
.monitoring_host
, [host
]) )
107 with
file(outfilename
, "w") as out
:
108 out
.write("# Automatically created by --scan-parents at %s\n\n" % time
.asctime())
109 out
.write("# Do not edit this file. If you want to convert an\n")
110 out
.write("# artificial gateway host into a permanent one, then\n")
111 out
.write("# move its definition into another *.mk file\n")
113 out
.write("# Parents which are not listed in your all_hosts:\n")
114 out
.write("all_hosts += %s\n\n" % pprint
.pformat(parent_hosts
))
116 out
.write("# IP addresses of parents not listed in all_hosts:\n")
117 out
.write("ipaddresses.update(%s)\n\n" % pprint
.pformat(parent_ips
))
119 out
.write("# Parent definitions\n")
120 out
.write("parents += %s\n\n" % pprint
.pformat(parent_rules
))
121 console
.output("\nWrote %s\n" % outfilename
)
124 def traceroute_available():
125 for path
in os
.environ
['PATH'].split(os
.pathsep
):
126 f
= path
+ '/traceroute'
127 if os
.path
.exists(f
) and os
.access(f
, os
.X_OK
):
131 def scan_parents_of(hosts
, silent
=False, settings
=None):
135 if config
.monitoring_host
:
136 nagios_ip
= ip_lookup
.lookup_ipv4_address(config
.monitoring_host
)
140 os
.putenv("LANG", "")
141 os
.putenv("LC_ALL", "")
143 # Start processes in parallel
146 console
.verbose("%s " % host
)
148 ip
= ip_lookup
.lookup_ipv4_address(host
)
149 command
= [ "traceroute",
150 "-w", "%d" % settings
.get("timeout", 8),
151 "-q", "%d" % settings
.get("probes", 2),
152 "-m", "%d" % settings
.get("max_ttl", 10),
154 console
.vverbose("Running '%s'\n" % subprocess
.list2cmdline(command
))
156 procs
.append((host
, ip
, subprocess
.Popen(command
,
157 stdout
=subprocess
.PIPE
,
158 stderr
=subprocess
.STDOUT
,
161 if cmk
.debug
.enabled():
163 procs
.append((host
, None, "ERROR: %s" % e
))
165 # Output marks with status of each single scan
166 def dot(color
, dot
='o'):
168 console
.output(tty
.bold
+ color
+ dot
+ tty
.normal
)
170 # Now all run and we begin to read the answers. For each host
171 # we add a triple to gateways: the gateway, a scan state and a diagnostic output
173 for host
, ip
, proc_or_error
in procs
:
174 if type(proc_or_error
) in [ str, unicode ]:
175 lines
= [ proc_or_error
]
178 exitstatus
= proc_or_error
.wait()
179 lines
= [ l
.strip() for l
in proc_or_error
.stdout
.readlines() ]
183 gateways
.append((None, "failed", 0, "Traceroute failed with exit code %d" % (exitstatus
& 255)))
186 if len(lines
) == 1 and lines
[0].startswith("ERROR:"):
187 message
= lines
[0][6:].strip()
188 console
.verbose("%s: %s\n", host
, message
, stream
=sys
.stderr
)
190 gateways
.append((None, "dnserror", 0, message
))
193 elif len(lines
) == 0:
194 if cmk
.debug
.enabled():
195 raise MKGeneralException("Cannot execute %s. Is traceroute installed? Are you root?" % command
)
202 console
.error("%s: %s\n" % (host
, ' '.join(lines
)))
203 gateways
.append((None, "garbled", 0, "The output of traceroute seem truncated:\n%s" %
208 # Parse output of traceroute:
209 # traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 40 byte packets
211 # 2 10.0.0.254 0.417 ms 0.459 ms 0.670 ms
212 # 3 172.16.0.254 0.967 ms 1.031 ms 1.544 ms
213 # 4 217.0.116.201 23.118 ms 25.153 ms 26.959 ms
214 # 5 217.0.76.134 32.103 ms 32.491 ms 32.337 ms
215 # 6 217.239.41.106 32.856 ms 35.279 ms 36.170 ms
216 # 7 74.125.50.149 45.068 ms 44.991 ms *
217 # 8 * 66.249.94.86 41.052 ms 66.249.94.88 40.795 ms
218 # 9 209.85.248.59 43.739 ms 41.106 ms 216.239.46.240 43.208 ms
219 # 10 216.239.48.53 45.608 ms 47.121 ms 64.233.174.29 43.126 ms
220 # 11 209.85.255.245 49.265 ms 40.470 ms 39.870 ms
221 # 12 8.8.8.8 28.339 ms 28.566 ms 28.791 ms
223 for line
in lines
[1:]:
226 if route
.count('.') == 3:
229 routes
.append(None) # No answer from this router
232 console
.error("%s: invalid output line from traceroute: '%s'\n" %
236 error
= "incomplete output from traceroute. No routes found."
237 console
.error("%s: %s\n" % (host
, error
))
238 gateways
.append((None, "garbled", 0, error
))
242 # Only one entry -> host is directly reachable and gets nagios as parent -
243 # if nagios is not the parent itself. Problem here: How can we determine
244 # if the host in question is the monitoring host? The user must configure
245 # this in monitoring_host.
246 elif len(routes
) == 1:
248 gateways
.append( (None, "root", 0, "") ) # We are the root-monitoring host
250 elif config
.monitoring_host
:
251 gateways
.append( ((config
.monitoring_host
, nagios_ip
, None), "direct", 0, "") )
254 gateways
.append( (None, "direct", 0, "") )
257 # Try far most route which is not identical with host itself
258 ping_probes
= settings
.get("ping_probes", 5)
261 for r
in routes
[::-1]:
262 if not r
or (r
== ip
):
264 # Do (optional) PING check in order to determine if that
265 # gateway can be monitored via the standard host check
267 if not gateway_reachable_via_ping(r
, ping_probes
):
268 console
.verbose("(not using %s, not reachable)\n", r
, stream
=sys
.stderr
)
269 skipped_gateways
+= 1
274 error
= "No usable routing information"
276 console
.error("%s: %s\n" % (host
, error
))
277 gateways
.append((None, "notfound", 0, error
))
281 # TTLs already have been filtered out)
283 gateway
= _ip_to_hostname(route
)
285 console
.verbose("%s(%s) ", gateway
, gateway_ip
)
287 console
.verbose("%s ", gateway_ip
)
289 # Try to find DNS name of host via reverse DNS lookup
290 dns_name
= _ip_to_dnsname(gateway_ip
)
291 gateways
.append( ((gateway
, gateway_ip
, dns_name
), "gateway", skipped_gateways
, "") )
296 def gateway_reachable_via_ping(ip
, probes
):
297 return subprocess
.call(["ping", "-q", "-i", "0.2", "-l", "3", "-c",
298 "%d" % probes
, "-W", "5", ip
],
299 stdout
=open(os
.devnull
, "w"),
300 stderr
=subprocess
.STDOUT
, close_fds
=True) == 0
303 # find hostname belonging to an ip address. We must not use
304 # reverse DNS but the Check_MK mechanisms, since we do not
305 # want to find the DNS name but the name of a matching host
307 def _ip_to_hostname(ip
):
308 if not cmk_base
.config_cache
.exists("ip_to_hostname"):
309 cache
= cmk_base
.config_cache
.get_dict("ip_to_hostname")
311 for host
in config
.all_active_realhosts():
313 cache
[ip_lookup
.lookup_ipv4_address(host
)] = host
317 cache
= cmk_base
.config_cache
.get_dict("ip_to_hostname")
322 def _ip_to_dnsname(ip
):
324 return socket
.gethostbyaddr(ip
)[0]