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.
31 import cmk
.utils
.paths
32 import cmk
.utils
.debug
33 import cmk
.utils
.store
as store
36 import cmk_base
.console
as console
37 import cmk_base
.config
as config
38 from cmk_base
.exceptions
import MKIPAddressLookupError
41 _enforce_localhost
= False
44 def enforce_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)
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
):
88 # Now check, if IP address is hard coded by the user
90 ipa
= config
.ipaddresses
.get(hostname
)
92 ipa
= config
.ipv6addresses
.get(hostname
)
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
):
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?
112 return cache
[cache_id
]
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
125 if config
.is_no_ip_host(hostname
):
126 cache
[cache_id
] = None
129 # Now do the actual DNS lookup
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
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
142 except Exception as e
:
143 # DNS failed. Use cached IP address if present, even if caching
146 cache
[cache_id
] = cached_ip
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")
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
)
169 if cmk
.utils
.debug
.enabled():
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
:
181 # New version has (hostname, ip family) as key
182 if isinstance(ip_lookup_cache
.keys()[0], tuple):
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()
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
)
210 cmk
.utils
.store
.release_lock(cache_path
)
214 return cmk
.utils
.paths
.var_dir
+ "/ipaddresses.cache"
217 def update_dns_cache():
221 console
.verbose("Cleaning up existing DNS cache...\n")
223 os
.unlink(_cache_path())
225 if e
.errno
!= errno
.ENOENT
:
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
))
238 ip
= lookup_ipv4_address(hostname
)
240 ip
= lookup_ipv6_address(hostname
)
242 console
.verbose("%s\n" % ip
)
244 except Exception as e
:
245 failed
.append(hostname
)
246 console
.verbose("lookup failed: %s\n" % e
)
247 if cmk
.utils
.debug
.enabled():
251 return updated
, failed