Licenses: Updated the list of licenses and added a PDF containing all license texts
[check_mk.git] / cmk_base / ip_lookup.py
blob36621e48be98ab248c7d60323018bccbaff4251d
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
28 import errno
29 import os
31 import cmk.utils.paths
32 import cmk.utils.debug
33 import cmk.utils.store as store
35 import cmk_base
36 import cmk_base.console as console
37 import cmk_base.config as config
38 from cmk_base.exceptions import MKIPAddressLookupError
40 _fake_dns = False
41 _enforce_localhost = False
44 def enforce_fake_dns(address):
45 global _fake_dns
46 _fake_dns = address
49 def enforce_localhost():
50 global _enforce_localhost
51 _enforce_localhost = True
54 def lookup_ipv4_address(hostname):
55 return lookup_ip_address(hostname, 4)
58 def lookup_ipv6_address(hostname):
59 return lookup_ip_address(hostname, 6)
62 # Determine the IP address of a host. It returns either an IP address or, when
63 # a hostname is configured as IP address, the hostname.
64 # Or raise an exception when a hostname can not be resolved on the first
65 # try to resolve a hostname. On later tries to resolve a hostname it
66 # returns None instead of raising an exception.
67 # FIXME: This different handling is bad. Clean this up!
68 def lookup_ip_address(hostname, family=None):
69 if family is None: # choose primary family
70 family = 6 if config.is_ipv6_primary(hostname) else 4
72 # Quick hack, where all IP addresses are faked (--fake-dns)
73 if _fake_dns:
74 return _fake_dns
75 if config.fake_dns:
76 return config.fake_dns
78 host_config = config.get_config_cache().get_host_config(hostname)
80 # Honor simulation mode und usewalk hosts. Never contact the network.
81 if config.simulation_mode or _enforce_localhost or \
82 (host_config.is_usewalk_host and host_config.is_snmp_host):
83 if family == 4:
84 return "127.0.0.1"
86 return "::1"
88 # Now check, if IP address is hard coded by the user
89 if family == 4:
90 ipa = config.ipaddresses.get(hostname)
91 else:
92 ipa = config.ipv6addresses.get(hostname)
94 if ipa:
95 return ipa
97 # Hosts listed in dyndns hosts always use dynamic DNS lookup.
98 # The use their hostname as IP address at all places
99 if config.in_binary_hostlist(hostname, config.dyndns_hosts):
100 return hostname
102 return cached_dns_lookup(hostname, family)
105 # Variables needed during the renaming of hosts (see automation.py)
106 def cached_dns_lookup(hostname, family):
107 cache = cmk_base.config_cache.get_dict("cached_dns_lookup")
108 cache_id = hostname, family
110 # Address has already been resolved in prior call to this function?
111 try:
112 return cache[cache_id]
113 except KeyError:
114 pass
116 # Prepare file based fall-back DNS cache in case resolution fails
117 # TODO: Find a place where this only called once!
118 ip_lookup_cache = _initialize_ip_lookup_cache()
120 cached_ip = ip_lookup_cache.get(cache_id)
121 if cached_ip and config.use_dns_cache:
122 cache[cache_id] = cached_ip
123 return cached_ip
125 if config.is_no_ip_host(hostname):
126 cache[cache_id] = None
127 return None
129 # Now do the actual DNS lookup
130 try:
131 ipa = socket.getaddrinfo(hostname, None, family == 4 and socket.AF_INET or
132 socket.AF_INET6)[0][4][0]
134 # Update our cached address if that has changed or was missing
135 if ipa != cached_ip:
136 console.verbose("Updating IPv%d DNS cache for %s: %s\n" % (family, hostname, ipa))
137 _update_ip_lookup_cache(cache_id, ipa)
139 cache[cache_id] = ipa # Update in-memory-cache
140 return ipa
142 except Exception as e:
143 # DNS failed. Use cached IP address if present, even if caching
144 # is disabled.
145 if cached_ip:
146 cache[cache_id] = cached_ip
147 return cached_ip
148 else:
149 cache[cache_id] = None
150 raise MKIPAddressLookupError(
151 "Failed to lookup IPv%d address of %s via DNS: %s" % (family, hostname, e))
154 def _initialize_ip_lookup_cache():
155 # Already created and initialized. Simply return it!
156 if cmk_base.config_cache.exists("ip_lookup"):
157 return cmk_base.config_cache.get_dict("ip_lookup")
159 ip_lookup_cache = cmk_base.config_cache.get_dict("ip_lookup")
161 try:
162 data_from_file = store.load_data_from_file(cmk.utils.paths.var_dir + '/ipaddresses.cache',
164 ip_lookup_cache.update(data_from_file)
166 # be compatible to old caches which were created by Check_MK without IPv6 support
167 _convert_legacy_ip_lookup_cache(ip_lookup_cache)
168 except:
169 if cmk.utils.debug.enabled():
170 raise
171 # TODO: Would be better to log it somewhere to make the failure transparent
173 return ip_lookup_cache
176 def _convert_legacy_ip_lookup_cache(ip_lookup_cache):
177 ip_lookup_cache = cmk_base.config_cache.get_dict("ip_lookup")
178 if not ip_lookup_cache:
179 return
181 # New version has (hostname, ip family) as key
182 if isinstance(ip_lookup_cache.keys()[0], tuple):
183 return
185 new_cache = {}
186 for key, val in ip_lookup_cache.items():
187 new_cache[(key, 4)] = val
188 ip_lookup_cache.clear()
189 ip_lookup_cache.update(new_cache)
192 def _update_ip_lookup_cache(cache_id, ipa):
193 ip_lookup_cache = cmk_base.config_cache.get_dict("ip_lookup")
195 # Read already known data
196 cache_path = _cache_path()
197 try:
198 data_from_file = cmk.utils.store.load_data_from_file(cache_path, default={}, lock=True)
200 _convert_legacy_ip_lookup_cache(data_from_file)
201 ip_lookup_cache.update(data_from_file)
202 ip_lookup_cache[cache_id] = ipa
204 # (I don't like this)
205 # TODO: this file always grows... there should be a cleanup mechanism
206 # maybe on "cmk --update-dns-cache"
207 # The cache_path is already locked from a previous function call..
208 cmk.utils.store.save_data_to_file(cache_path, ip_lookup_cache)
209 finally:
210 cmk.utils.store.release_lock(cache_path)
213 def _cache_path():
214 return cmk.utils.paths.var_dir + "/ipaddresses.cache"
217 def update_dns_cache():
218 updated = 0
219 failed = []
221 console.verbose("Cleaning up existing DNS cache...\n")
222 try:
223 os.unlink(_cache_path())
224 except OSError as e:
225 if e.errno != errno.ENOENT:
226 raise
228 console.verbose("Updating DNS cache...\n")
229 for hostname in config.all_active_hosts():
230 # Use intelligent logic. This prevents DNS lookups for hosts
231 # with statically configured addresses, etc.
232 for family in [4, 6]:
233 if (family == 4 and config.is_ipv4_host(hostname)) \
234 or (family == 6 and config.is_ipv6_host(hostname)):
235 console.verbose("%s (IPv%d)..." % (hostname, family))
236 try:
237 if family == 4:
238 ip = lookup_ipv4_address(hostname)
239 else:
240 ip = lookup_ipv6_address(hostname)
242 console.verbose("%s\n" % ip)
243 updated += 1
244 except Exception as e:
245 failed.append(hostname)
246 console.verbose("lookup failed: %s\n" % e)
247 if cmk.utils.debug.enabled():
248 raise
249 continue
251 return updated, failed