Refactoring: Moved check parameters from unsorted.py to dedicated modules (CMK-1393)
[check_mk.git] / cmk_base / ip_lookup.py
bloba841e4d27da744dd4eea01fb7bd73e9f63e4d86c
1 #!/usr/bin/env python
2 # -*- encoding: utf-8; py-indent-offset: 4 -*-
3 # +------------------------------------------------------------------+
4 # | ____ _ _ __ __ _ __ |
5 # | / ___| |__ ___ ___| | __ | \/ | |/ / |
6 # | | | | '_ \ / _ \/ __| |/ / | |\/| | ' / |
7 # | | |___| | | | __/ (__| < | | | | . \ |
8 # | \____|_| |_|\___|\___|_|\_\___|_| |_|_|\_\ |
9 # | |
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.
27 import socket
29 import cmk.utils.paths
30 import cmk.utils.debug
31 import cmk.utils.store as store
33 import cmk_base
34 import cmk_base.console as console
35 import cmk_base.config as config
36 from cmk_base.exceptions import MKIPAddressLookupError
38 _fake_dns = False
39 _enforce_localhost = False
42 def enforce_fake_dns(address):
43 global _fake_dns
44 _fake_dns = address
47 def enforce_localhost():
48 global _enforce_localhost
49 _enforce_localhost = True
52 def lookup_ipv4_address(hostname):
53 return lookup_ip_address(hostname, 4)
56 def lookup_ipv6_address(hostname):
57 return lookup_ip_address(hostname, 6)
60 # Determine the IP address of a host. It returns either an IP address or, when
61 # a hostname is configured as IP address, the hostname.
62 # Or raise an exception when a hostname can not be resolved on the first
63 # try to resolve a hostname. On later tries to resolve a hostname it
64 # returns None instead of raising an exception.
65 # FIXME: This different handling is bad. Clean this up!
66 def lookup_ip_address(hostname, family=None):
67 if family is None: # choose primary family
68 family = 6 if config.is_ipv6_primary(hostname) else 4
70 # Quick hack, where all IP addresses are faked (--fake-dns)
71 if _fake_dns:
72 return _fake_dns
73 if config.fake_dns:
74 return config.fake_dns
76 # Honor simulation mode und usewalk hosts. Never contact the network.
77 elif config.simulation_mode or _enforce_localhost or \
78 (config.is_usewalk_host(hostname) and config.is_snmp_host(hostname)):
79 if family == 4:
80 return "127.0.0.1"
82 return "::1"
84 # Now check, if IP address is hard coded by the user
85 if family == 4:
86 ipa = config.ipaddresses.get(hostname)
87 else:
88 ipa = config.ipv6addresses.get(hostname)
90 if ipa:
91 return ipa
93 # Hosts listed in dyndns hosts always use dynamic DNS lookup.
94 # The use their hostname as IP address at all places
95 if config.in_binary_hostlist(hostname, config.dyndns_hosts):
96 return hostname
98 return cached_dns_lookup(hostname, family)
101 # Variables needed during the renaming of hosts (see automation.py)
102 def cached_dns_lookup(hostname, family):
103 cache = cmk_base.config_cache.get_dict("cached_dns_lookup")
104 cache_id = hostname, family
106 # Address has already been resolved in prior call to this function?
107 try:
108 return cache[cache_id]
109 except KeyError:
110 pass
112 # Prepare file based fall-back DNS cache in case resolution fails
113 # TODO: Find a place where this only called once!
114 ip_lookup_cache = _initialize_ip_lookup_cache()
116 cached_ip = ip_lookup_cache.get(cache_id)
117 if cached_ip and config.use_dns_cache:
118 cache[cache_id] = cached_ip
119 return cached_ip
121 if config.is_no_ip_host(hostname):
122 cache[cache_id] = None
123 return None
125 # Now do the actual DNS lookup
126 try:
127 ipa = socket.getaddrinfo(hostname, None, family == 4 and socket.AF_INET or
128 socket.AF_INET6)[0][4][0]
130 # Update our cached address if that has changed or was missing
131 if ipa != cached_ip:
132 console.verbose("Updating IPv%d DNS cache for %s: %s\n" % (family, hostname, ipa))
133 _update_ip_lookup_cache(cache_id, ipa)
135 cache[cache_id] = ipa # Update in-memory-cache
136 return ipa
138 except Exception as e:
139 # DNS failed. Use cached IP address if present, even if caching
140 # is disabled.
141 if cached_ip:
142 cache[cache_id] = cached_ip
143 return cached_ip
144 else:
145 cache[cache_id] = None
146 raise MKIPAddressLookupError(
147 "Failed to lookup IPv%d address of %s via DNS: %s" % (family, hostname, e))
150 def _initialize_ip_lookup_cache():
151 # Already created and initialized. Simply return it!
152 if cmk_base.config_cache.exists("ip_lookup"):
153 return cmk_base.config_cache.get_dict("ip_lookup")
155 ip_lookup_cache = cmk_base.config_cache.get_dict("ip_lookup")
157 try:
158 data_from_file = store.load_data_from_file(cmk.utils.paths.var_dir + '/ipaddresses.cache',
160 ip_lookup_cache.update(data_from_file)
162 # be compatible to old caches which were created by Check_MK without IPv6 support
163 _convert_legacy_ip_lookup_cache(ip_lookup_cache)
164 except:
165 if cmk.utils.debug.enabled():
166 raise
167 # TODO: Would be better to log it somewhere to make the failure transparent
169 return ip_lookup_cache
172 def _convert_legacy_ip_lookup_cache(ip_lookup_cache):
173 ip_lookup_cache = cmk_base.config_cache.get_dict("ip_lookup")
174 if not ip_lookup_cache:
175 return
177 # New version has (hostname, ip family) as key
178 if isinstance(ip_lookup_cache.keys()[0], tuple):
179 return
181 new_cache = {}
182 for key, val in ip_lookup_cache.items():
183 new_cache[(key, 4)] = val
184 ip_lookup_cache.clear()
185 ip_lookup_cache.update(new_cache)
188 def _update_ip_lookup_cache(cache_id, ipa):
189 ip_lookup_cache = cmk_base.config_cache.get_dict("ip_lookup")
191 # Read already known data
192 cache_path = cmk.utils.paths.var_dir + '/ipaddresses.cache'
193 try:
194 data_from_file = cmk.utils.store.load_data_from_file(cache_path, default={}, lock=True)
196 _convert_legacy_ip_lookup_cache(data_from_file)
197 ip_lookup_cache.update(data_from_file)
198 ip_lookup_cache[cache_id] = ipa
200 # (I don't like this)
201 # TODO: this file always grows... there should be a cleanup mechanism
202 # maybe on "cmk --update-dns-cache"
203 # The cache_path is already locked from a previous function call..
204 cmk.utils.store.save_data_to_file(cache_path, ip_lookup_cache)
205 finally:
206 cmk.utils.store.release_lock(cache_path)
209 # TODO: remove unused code?
210 def _write_ip_lookup_cache():
211 ip_lookup_cache = cmk_base.config_cache.get_dict("ip_lookup")
213 cache_path = cmk.utils.paths.var_dir + '/ipaddresses.cache'
215 # Read already known data
216 data_from_file = cmk.utils.store.load_data_from_file(cache_path, default={}, lock=True)
217 data_from_file.update(ip_lookup_cache)
218 # The lock from the previous call is released in the following function
219 # (I don't like this)
220 # TODO: this file always grows... there should be a cleanup mechanism
221 # maybe on "cmk --update-dns-cache"
222 cmk.utils.store.save_data_to_file(cache_path, data_from_file, pretty=False)
225 def update_dns_cache():
226 # Temporarily disable *use* of cache, we want to force an update
227 # TODO: Cleanup this hacky config override! Better add some global flag
228 # that is exactly meant for this situation.
229 config.use_dns_cache = False
230 updated = 0
231 failed = []
233 console.verbose("Updating DNS cache...\n")
234 for hostname in config.all_active_hosts():
235 # Use intelligent logic. This prevents DNS lookups for hosts
236 # with statically configured addresses, etc.
237 for family in [4, 6]:
238 if (family == 4 and config.is_ipv4_host(hostname)) \
239 or (family == 6 and config.is_ipv6_host(hostname)):
240 console.verbose("%s (IPv%d)..." % (hostname, family))
241 try:
242 if family == 4:
243 ip = lookup_ipv4_address(hostname)
244 else:
245 ip = lookup_ipv6_address(hostname)
247 console.verbose("%s\n" % ip)
248 updated += 1
249 except Exception as e:
250 failed.append(hostname)
251 console.verbose("lookup failed: %s\n" % e)
252 if cmk.utils.debug.enabled():
253 raise
254 continue
256 return updated, failed