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.
29 import cmk
.utils
.paths
30 import cmk
.utils
.debug
31 import cmk
.utils
.store
as store
34 import cmk_base
.console
as console
35 import cmk_base
.config
as config
36 from cmk_base
.exceptions
import MKIPAddressLookupError
39 _enforce_localhost
= False
42 def enforce_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)
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
)):
84 # Now check, if IP address is hard coded by the user
86 ipa
= config
.ipaddresses
.get(hostname
)
88 ipa
= config
.ipv6addresses
.get(hostname
)
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
):
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?
108 return cache
[cache_id
]
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
121 if config
.is_no_ip_host(hostname
):
122 cache
[cache_id
] = None
125 # Now do the actual DNS lookup
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
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
138 except Exception as e
:
139 # DNS failed. Use cached IP address if present, even if caching
142 cache
[cache_id
] = cached_ip
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")
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
)
165 if cmk
.utils
.debug
.enabled():
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
:
177 # New version has (hostname, ip family) as key
178 if isinstance(ip_lookup_cache
.keys()[0], tuple):
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'
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
)
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
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
))
243 ip
= lookup_ipv4_address(hostname
)
245 ip
= lookup_ipv6_address(hostname
)
247 console
.verbose("%s\n" % ip
)
249 except Exception as e
:
250 failed
.append(hostname
)
251 console
.verbose("lookup failed: %s\n" % e
)
252 if cmk
.utils
.debug
.enabled():
256 return updated
, failed