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 import cmk
.utils
.tty
as tty
37 import cmk
.utils
.paths
38 import cmk
.utils
.debug
39 from cmk
.utils
.exceptions
import MKGeneralException
42 import cmk_base
.console
as console
43 import cmk_base
.config
as config
44 import cmk_base
.ip_lookup
as ip_lookup
47 def do_scan_parents(hosts
):
50 h
for h
in config
.all_active_realhosts()
51 if config
.in_binary_hostlist(h
, config
.scanparent_hosts
)
57 gateway_hosts
= set([])
59 if config
.max_num_processes
< 1:
60 config
.max_num_processes
= 1
62 outfilename
= cmk
.utils
.paths
.check_mk_config_dir
+ "/parents.mk"
64 if not traceroute_available():
65 raise MKGeneralException('The program "traceroute" was not found.\n'
66 'The parent scan needs this program.\n'
67 'Please install it and try again.')
69 if os
.path
.exists(outfilename
):
70 first_line
= file(outfilename
, "r").readline()
71 if not first_line
.startswith('# Automatically created by --scan-parents at'):
72 raise MKGeneralException("conf.d/parents.mk seems to be created manually.\n\n"
73 "The --scan-parents function would overwrite this file.\n"
74 "Please rename it to keep the configuration or delete "
75 "the file and try again.")
77 console
.output("Scanning for parents (%d processes)..." % config
.max_num_processes
)
80 while len(chunk
) < config
.max_num_processes
and len(hosts
) > 0:
83 # skip hosts that already have a parent
84 if config
.parents_of(host
):
85 console
.verbose("(manual parent) ")
89 gws
= scan_parents_of(chunk
)
91 for host
, (gw
, _unused_state
, _unused_ping_fails
, _unused_message
) in zip(chunk
, gws
):
93 gateway
, gateway_ip
, dns_name
= gw
94 if not gateway
: # create artificial host
98 gateway
= "gw-%s" % (gateway_ip
.replace(".", "-"))
99 if gateway
not in gateway_hosts
:
100 gateway_hosts
.add(gateway
)
101 parent_hosts
.append("%s|parent|ping" % gateway
)
102 parent_ips
[gateway
] = gateway_ip
103 if config
.monitoring_host
:
104 parent_rules
.append((config
.monitoring_host
,
105 [gateway
])) # make Nagios a parent of gw
106 parent_rules
.append((gateway
, [host
]))
107 elif host
!= config
.monitoring_host
and config
.monitoring_host
:
108 # make monitoring host the parent of all hosts without real parent
109 parent_rules
.append((config
.monitoring_host
, [host
]))
111 with
file(outfilename
, "w") as out
:
112 out
.write("# Automatically created by --scan-parents at %s\n\n" % time
.asctime())
113 out
.write("# Do not edit this file. If you want to convert an\n")
114 out
.write("# artificial gateway host into a permanent one, then\n")
115 out
.write("# move its definition into another *.mk file\n")
117 out
.write("# Parents which are not listed in your all_hosts:\n")
118 out
.write("all_hosts += %s\n\n" % pprint
.pformat(parent_hosts
))
120 out
.write("# IP addresses of parents not listed in all_hosts:\n")
121 out
.write("ipaddresses.update(%s)\n\n" % pprint
.pformat(parent_ips
))
123 out
.write("# Parent definitions\n")
124 out
.write("parents += %s\n\n" % pprint
.pformat(parent_rules
))
125 console
.output("\nWrote %s\n" % outfilename
)
128 def traceroute_available():
129 for path
in os
.environ
['PATH'].split(os
.pathsep
):
130 f
= path
+ '/traceroute'
131 if os
.path
.exists(f
) and os
.access(f
, os
.X_OK
):
135 def scan_parents_of(hosts
, silent
=False, settings
=None):
139 if config
.monitoring_host
:
140 nagios_ip
= ip_lookup
.lookup_ipv4_address(config
.monitoring_host
)
144 os
.putenv("LANG", "")
145 os
.putenv("LC_ALL", "")
147 # Start processes in parallel
150 console
.verbose("%s " % host
)
152 ip
= ip_lookup
.lookup_ipv4_address(host
)
155 "%d" % settings
.get("timeout", 8), "-q",
156 "%d" % settings
.get("probes", 2), "-m",
157 "%d" % settings
.get("max_ttl", 10), "-n", ip
159 console
.vverbose("Running '%s'\n" % subprocess
.list2cmdline(command
))
161 procs
.append((host
, ip
,
164 stdout
=subprocess
.PIPE
,
165 stderr
=subprocess
.STDOUT
,
167 except Exception as e
:
168 if cmk
.utils
.debug
.enabled():
170 procs
.append((host
, None, "ERROR: %s" % e
))
172 # Output marks with status of each single scan
173 def dot(color
, dot
='o'):
175 console
.output(tty
.bold
+ color
+ dot
+ tty
.normal
)
177 # Now all run and we begin to read the answers. For each host
178 # we add a triple to gateways: the gateway, a scan state and a diagnostic output
180 for host
, ip
, proc_or_error
in procs
:
181 if isinstance(proc_or_error
, six
.string_types
):
182 lines
= [proc_or_error
]
185 exitstatus
= proc_or_error
.wait()
186 lines
= [l
.strip() for l
in proc_or_error
.stdout
.readlines()]
190 gateways
.append((None, "failed", 0,
191 "Traceroute failed with exit code %d" % (exitstatus
& 255)))
194 if len(lines
) == 1 and lines
[0].startswith("ERROR:"):
195 message
= lines
[0][6:].strip()
196 console
.verbose("%s: %s\n", host
, message
, stream
=sys
.stderr
)
198 gateways
.append((None, "dnserror", 0, message
))
201 elif len(lines
) == 0:
202 if cmk
.utils
.debug
.enabled():
203 raise MKGeneralException(
204 "Cannot execute %s. Is traceroute installed? Are you root?" % command
)
211 console
.error("%s: %s\n" % (host
, ' '.join(lines
)))
212 gateways
.append((None, "garbled", 0,
213 "The output of traceroute seem truncated:\n%s" % ("".join(lines
))))
217 # Parse output of traceroute:
218 # traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 40 byte packets
220 # 2 10.0.0.254 0.417 ms 0.459 ms 0.670 ms
221 # 3 172.16.0.254 0.967 ms 1.031 ms 1.544 ms
222 # 4 217.0.116.201 23.118 ms 25.153 ms 26.959 ms
223 # 5 217.0.76.134 32.103 ms 32.491 ms 32.337 ms
224 # 6 217.239.41.106 32.856 ms 35.279 ms 36.170 ms
225 # 7 74.125.50.149 45.068 ms 44.991 ms *
226 # 8 * 66.249.94.86 41.052 ms 66.249.94.88 40.795 ms
227 # 9 209.85.248.59 43.739 ms 41.106 ms 216.239.46.240 43.208 ms
228 # 10 216.239.48.53 45.608 ms 47.121 ms 64.233.174.29 43.126 ms
229 # 11 209.85.255.245 49.265 ms 40.470 ms 39.870 ms
230 # 12 8.8.8.8 28.339 ms 28.566 ms 28.791 ms
232 for line
in lines
[1:]:
235 if route
.count('.') == 3:
238 routes
.append(None) # No answer from this router
241 console
.error("%s: invalid output line from traceroute: '%s'\n" % (host
, line
))
244 error
= "incomplete output from traceroute. No routes found."
245 console
.error("%s: %s\n" % (host
, error
))
246 gateways
.append((None, "garbled", 0, error
))
250 # Only one entry -> host is directly reachable and gets nagios as parent -
251 # if nagios is not the parent itself. Problem here: How can we determine
252 # if the host in question is the monitoring host? The user must configure
253 # this in monitoring_host.
254 elif len(routes
) == 1:
256 gateways
.append((None, "root", 0, "")) # We are the root-monitoring host
258 elif config
.monitoring_host
:
259 gateways
.append(((config
.monitoring_host
, nagios_ip
, None), "direct", 0, ""))
262 gateways
.append((None, "direct", 0, ""))
265 # Try far most route which is not identical with host itself
266 ping_probes
= settings
.get("ping_probes", 5)
269 for r
in routes
[::-1]:
270 if not r
or (r
== ip
):
272 # Do (optional) PING check in order to determine if that
273 # gateway can be monitored via the standard host check
275 if not gateway_reachable_via_ping(r
, ping_probes
):
276 console
.verbose("(not using %s, not reachable)\n", r
, stream
=sys
.stderr
)
277 skipped_gateways
+= 1
282 error
= "No usable routing information"
284 console
.error("%s: %s\n" % (host
, error
))
285 gateways
.append((None, "notfound", 0, error
))
289 # TTLs already have been filtered out)
291 gateway
= _ip_to_hostname(route
)
293 console
.verbose("%s(%s) ", gateway
, gateway_ip
)
295 console
.verbose("%s ", gateway_ip
)
297 # Try to find DNS name of host via reverse DNS lookup
298 dns_name
= _ip_to_dnsname(gateway_ip
)
299 gateways
.append(((gateway
, gateway_ip
, dns_name
), "gateway", skipped_gateways
, ""))
304 def gateway_reachable_via_ping(ip
, probes
):
305 return subprocess
.call(
306 ["ping", "-q", "-i", "0.2", "-l", "3", "-c",
307 "%d" % probes
, "-W", "5", ip
],
308 stdout
=open(os
.devnull
, "w"),
309 stderr
=subprocess
.STDOUT
,
313 # find hostname belonging to an ip address. We must not use
314 # reverse DNS but the Check_MK mechanisms, since we do not
315 # want to find the DNS name but the name of a matching host
317 def _ip_to_hostname(ip
):
318 if not cmk_base
.config_cache
.exists("ip_to_hostname"):
319 cache
= cmk_base
.config_cache
.get_dict("ip_to_hostname")
321 for host
in config
.all_active_realhosts():
323 cache
[ip_lookup
.lookup_ipv4_address(host
)] = host
327 cache
= cmk_base
.config_cache
.get_dict("ip_to_hostname")
332 def _ip_to_dnsname(ip
):
334 return socket
.gethostbyaddr(ip
)[0]