Commit | Line | Data |
---|---|---|
96779587 CH |
1 | /* |
2 | The software in this package is distributed under the GNU General | |
3 | Public License version 2 (with a special exception described below). | |
4 | ||
5 | A copy of GNU General Public License (GPL) is included in this distribution, | |
6 | in the file COPYING.GPL. | |
7 | ||
8 | As a special exception, if other files instantiate templates or use macros | |
9 | or inline functions from this file, or you compile this file and link it | |
10 | with other works to produce a work based on this file, this file | |
11 | does not by itself cause the resulting work to be covered | |
12 | by the GNU General Public License. | |
13 | ||
14 | However the source code for this file must still be made available | |
15 | in accordance with section (3) of the GNU General Public License. | |
16 | ||
17 | This exception does not invalidate any other reasons why a work based | |
18 | on this file might be covered by the GNU General Public License. | |
19 | ||
20 | Christian Herdtweck, Intra2net AG 2015 | |
21 | */ | |
22 | ||
c5b4902d | 23 | #include "dns/dnscache.h" |
96779587 | 24 | |
f636ba88 | 25 | #include <sstream> |
946356e1 | 26 | #include <fstream> |
96779587 | 27 | #include <logfunc.hpp> |
946356e1 | 28 | #include <filefunc.hxx> // I2n::file_exists |
ac012457 | 29 | #include <tmpfstream.hpp> |
e18c1337 | 30 | #include <boost/foreach.hpp> |
96779587 | 31 | #include <boost/bind.hpp> |
96779587 | 32 | #include <boost/asio/placeholders.hpp> |
946356e1 CH |
33 | #include <boost/serialization/serialization.hpp> |
34 | #include <boost/serialization/map.hpp> | |
35 | #include <boost/serialization/string.hpp> | |
36 | #include <boost/serialization/vector.hpp> | |
37 | #include <boost/archive/xml_oarchive.hpp> | |
38 | #include <boost/archive/xml_iarchive.hpp> | |
c98b5fcb | 39 | #include <boost/date_time/gregorian/gregorian_types.hpp> |
96779587 | 40 | |
dbe986b9 CH |
41 | #include "dns/dnsmaster.h" |
42 | ||
96779587 CH |
43 | using boost::bind; |
44 | using boost::posix_time::seconds; | |
45 | using I2n::Logger::GlobalLogger; | |
46 | ||
8d26221d | 47 | |
96779587 CH |
48 | namespace Config |
49 | { | |
77319ba8 | 50 | const int SAVE_TIMER_SECONDS = 60; |
81cf5681 | 51 | const int CACHE_TIME_WARP_THRESH_MINS = 10; |
4e691eb0 | 52 | const int CACHE_REMOVE_OUTDATED_DAYS = 60; |
96779587 CH |
53 | } |
54 | ||
8d26221d | 55 | |
f833126b CH |
56 | // ----------------------------------------------------------------------------- |
57 | // DNS Cache constructor / destructor | |
58 | // ----------------------------------------------------------------------------- | |
59 | ||
8d26221d CH |
60 | const string DnsCache::DoNotUseCacheFile = "do not use cache file!"; |
61 | ||
96779587 | 62 | DnsCache::DnsCache(const IoServiceItem &io_serv, |
f833126b CH |
63 | const std::string &cache_file, |
64 | const uint32_t min_time_between_resolves) | |
ad83004d CH |
65 | : IpCache() |
66 | , CnameCache() | |
96779587 CH |
67 | , SaveTimer( *io_serv ) |
68 | , CacheFile( cache_file ) | |
69 | , HasChanged( false ) | |
f833126b | 70 | , MinTimeBetweenResolves( min_time_between_resolves ) |
96779587 CH |
71 | { |
72 | // load cache from file | |
73 | load_from_cachefile(); | |
74 | ||
75 | // schedule next save | |
77319ba8 | 76 | (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) ); |
96779587 CH |
77 | SaveTimer.async_wait( bind( &DnsCache::schedule_save, this, |
78 | boost::asio::placeholders::error ) ); | |
79 | } | |
80 | ||
81 | ||
82 | DnsCache::~DnsCache() | |
83 | { | |
26b0f687 | 84 | GlobalLogger.info() << "DnsCache: being destructed"; |
e91538f0 | 85 | |
96779587 CH |
86 | // save one last time without re-scheduling the next save |
87 | save_to_cachefile(); | |
88 | ||
89 | // cancel save timer | |
90 | SaveTimer.cancel(); | |
91 | } | |
92 | ||
93 | ||
f833126b CH |
94 | // ----------------------------------------------------------------------------- |
95 | // LOAD / SAVE | |
96 | // ----------------------------------------------------------------------------- | |
97 | ||
96779587 CH |
98 | void DnsCache::schedule_save(const boost::system::error_code &error) |
99 | { | |
96779587 CH |
100 | if ( error == boost::asio::error::operation_aborted ) // cancelled |
101 | { | |
49b82a1d CH |
102 | GlobalLogger.info() << "DnsCache: SaveTimer was cancelled " |
103 | << "--> no save and no re-schedule of saving!"; | |
96779587 CH |
104 | return; |
105 | } | |
106 | else if (error) | |
107 | { | |
49b82a1d CH |
108 | GlobalLogger.info() << "DnsCache: Received error " << error |
109 | << " in schedule_save " | |
110 | << "--> no save now but re-schedule saving"; | |
96779587 CH |
111 | } |
112 | else | |
113 | save_to_cachefile(); | |
114 | ||
115 | // schedule next save | |
77319ba8 | 116 | (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) ); |
96779587 CH |
117 | SaveTimer.async_wait( bind( &DnsCache::schedule_save, this, |
118 | boost::asio::placeholders::error ) ); | |
119 | } | |
120 | ||
121 | void DnsCache::save_to_cachefile() | |
122 | { | |
123 | if (!HasChanged) | |
ad83004d | 124 | GlobalLogger.info() << "DnsCache: skip saving because has not changed"; |
946356e1 | 125 | else if (CacheFile.empty()) |
49b82a1d | 126 | GlobalLogger.info() |
ad83004d | 127 | << "DnsCache: skip saving because file name empty!"; |
8d26221d | 128 | else if (CacheFile == DoNotUseCacheFile) |
26b0f687 CH |
129 | GlobalLogger.info() << "DnsCache: configured not to use cache file"; |
130 | else | |
946356e1 | 131 | { |
26b0f687 CH |
132 | try |
133 | { | |
c98b5fcb CH |
134 | // clean up: remove very old entries |
135 | remove_old_entries(); | |
136 | ||
77319ba8 CH |
137 | // remember time of save |
138 | std::string cache_save_time_str = boost::posix_time::to_iso_string( | |
139 | boost::posix_time::second_clock::universal_time() ); | |
140 | ||
ac012457 | 141 | I2n::tmpofcopystream ofs( CacheFile.c_str() ); |
26b0f687 | 142 | boost::archive::xml_oarchive oa(ofs); |
26b0f687 CH |
143 | oa & BOOST_SERIALIZATION_NVP(IpCache); |
144 | oa & BOOST_SERIALIZATION_NVP(CnameCache); | |
77319ba8 | 145 | oa & BOOST_SERIALIZATION_NVP(cache_save_time_str); |
8d26221d CH |
146 | GlobalLogger.info() << "DnsCache: saved to cache file " |
147 | << CacheFile; | |
26b0f687 CH |
148 | |
149 | HasChanged = false; | |
150 | } | |
151 | catch (std::exception &exc) | |
152 | { | |
c1abff61 | 153 | GlobalLogger.warning() << "DnsCache: Saving failed: " << exc.what(); |
26b0f687 | 154 | } |
946356e1 | 155 | } |
96779587 CH |
156 | } |
157 | ||
96779587 CH |
158 | void DnsCache::load_from_cachefile() |
159 | { | |
946356e1 | 160 | if (CacheFile.empty()) |
49b82a1d | 161 | GlobalLogger.info() |
8d26221d CH |
162 | << "DnsCache: cannot load because cache file name is empty!"; |
163 | else if (CacheFile == DoNotUseCacheFile) | |
26b0f687 | 164 | GlobalLogger.info() << "DnsCache: configured not to use cache file"; |
946356e1 | 165 | else if ( !I2n::file_exists(CacheFile) ) |
ad83004d | 166 | GlobalLogger.warning() << "DnsCache: cannot load because cache file " |
946356e1 | 167 | << CacheFile << " does not exist!"; |
26b0f687 | 168 | else |
946356e1 | 169 | { |
26b0f687 CH |
170 | try |
171 | { | |
172 | std::ifstream ifs( CacheFile.c_str() ); | |
173 | boost::archive::xml_iarchive ia(ifs); | |
946356e1 | 174 | |
d92e7d02 TJ |
175 | ip_map_type new_IpCache; |
176 | cname_map_type new_CnameCache; | |
77319ba8 CH |
177 | std::string cache_save_time_str; |
178 | ||
d92e7d02 TJ |
179 | ia & BOOST_SERIALIZATION_NVP(new_IpCache); |
180 | ia & BOOST_SERIALIZATION_NVP(new_CnameCache); | |
77319ba8 CH |
181 | ia & BOOST_SERIALIZATION_NVP(cache_save_time_str); |
182 | ||
d92e7d02 | 183 | const boost::posix_time::ptime cache_save_time |
77319ba8 | 184 | = boost::posix_time::from_iso_string(cache_save_time_str); |
26b0f687 | 185 | GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile; |
77319ba8 | 186 | |
d92e7d02 TJ |
187 | // atomic switch over |
188 | IpCache.swap(new_IpCache); | |
189 | CnameCache.swap(new_CnameCache); | |
190 | ||
77319ba8 | 191 | check_timestamps(cache_save_time); |
26b0f687 CH |
192 | } |
193 | catch (boost::archive::archive_exception &exc) | |
194 | { | |
8d26221d CH |
195 | GlobalLogger.warning() |
196 | << "DnsCache: archive exception loading from " << CacheFile | |
197 | << ": " << exc.what(); | |
26b0f687 CH |
198 | } |
199 | catch (std::exception &exc) | |
200 | { | |
201 | GlobalLogger.warning() << "DnsCache: exception while loading from " | |
202 | << CacheFile << ": " << exc.what(); | |
203 | } | |
946356e1 | 204 | } |
96779587 CH |
205 | } |
206 | ||
26b0f687 | 207 | |
77319ba8 CH |
208 | /** |
209 | * @brief check that loaded cache really is from the past | |
210 | * | |
211 | * Added this to avoid trouble in case the system time is changed into the past. | |
64a85329 | 212 | * In that case would have TTLs here in cache that are valid much too long. |
77319ba8 CH |
213 | * |
214 | * Therefore check SaveCacheTime and if that is in the future, set all TTLs to 0 | |
215 | * | |
216 | * @returns true if had to re-set timestamps | |
217 | */ | |
218 | bool DnsCache::check_timestamps(const boost::posix_time::ptime &cache_save_time) | |
219 | { | |
220 | // check if CacheSaveTime is in the future | |
221 | boost::posix_time::ptime now | |
222 | = boost::posix_time::second_clock::universal_time() | |
81cf5681 | 223 | + boost::posix_time::minutes(Config::CACHE_TIME_WARP_THRESH_MINS); |
77319ba8 CH |
224 | if (now > cache_save_time) |
225 | { // Cache was saved in the past -- everything alright | |
226 | return false; | |
227 | } | |
228 | ||
229 | GlobalLogger.warning() << "DnsCache: loaded cache from the future (saved " | |
230 | << cache_save_time << ")! Resetting all TTLs to 0."; | |
231 | ||
232 | // reset TTLs in IP cache | |
81cf5681 | 233 | BOOST_FOREACH( ip_map_type::value_type &key_and_ip, IpCache ) |
77319ba8 | 234 | { |
81cf5681 CH |
235 | BOOST_FOREACH( HostAddress &address, key_and_ip.second ) |
236 | address.get_ttl().set_value(0); | |
77319ba8 CH |
237 | } |
238 | ||
239 | // reset TTLs in CNAME cache | |
81cf5681 CH |
240 | BOOST_FOREACH( cname_map_type::value_type &key_and_cname, CnameCache ) |
241 | key_and_cname.second.Ttl.set_value(0); | |
242 | ||
243 | HasChanged = true; | |
244 | ||
245 | //debug_print(); | |
77319ba8 CH |
246 | |
247 | return true; | |
248 | } | |
249 | ||
250 | ||
c98b5fcb CH |
251 | /** |
252 | * @brief remove entries from cache that are older than a certain threshold | |
253 | * | |
254 | * this also removes entres from IpCache with no IPs | |
255 | */ | |
256 | void DnsCache::remove_old_entries() | |
257 | { | |
258 | boost::posix_time::ptime thresh | |
259 | = boost::posix_time::second_clock::universal_time() | |
260 | - boost::gregorian::days( Config::CACHE_REMOVE_OUTDATED_DAYS ); | |
261 | ||
262 | // IP cache | |
c98b5fcb | 263 | { |
81cf5681 CH |
264 | ip_map_type::iterator it = IpCache.begin(); |
265 | ip_map_type::iterator it_end = IpCache.end(); | |
266 | bool some_ip_up_to_date; | |
267 | while (it != it_end) | |
c98b5fcb | 268 | { |
81cf5681 CH |
269 | some_ip_up_to_date = false; |
270 | BOOST_FOREACH( const HostAddress &address, (*it).second ) | |
c98b5fcb | 271 | { |
81cf5681 CH |
272 | if ( ! address.get_ttl().was_set_before(thresh) ) |
273 | { | |
274 | some_ip_up_to_date = true; | |
275 | break; | |
276 | } | |
c98b5fcb | 277 | } |
c98b5fcb | 278 | |
81cf5681 CH |
279 | if ( ! some_ip_up_to_date ) |
280 | { | |
281 | GlobalLogger.debug() << "DnsCache: Removing empty/outdated IP " | |
282 | << "list for " << (*it).first.first; | |
283 | IpCache.erase( (*it++).first ); | |
284 | } | |
285 | else | |
286 | ++it; | |
c98b5fcb CH |
287 | } |
288 | } | |
289 | ||
290 | // CNAME cache | |
c98b5fcb | 291 | { |
81cf5681 CH |
292 | cname_map_type::iterator it = CnameCache.begin(); |
293 | cname_map_type::iterator it_end = CnameCache.end(); | |
294 | while (it != it_end) | |
c98b5fcb | 295 | { |
81cf5681 CH |
296 | if ( (*it).second.Ttl.was_set_before( thresh ) ) |
297 | { | |
298 | GlobalLogger.debug() << "DnsCache: Removing outdated CNAME for " | |
299 | << (*it).first; | |
300 | CnameCache.erase( (*it++).first ); | |
301 | } | |
302 | else | |
303 | ++it; | |
c98b5fcb CH |
304 | } |
305 | } | |
306 | } | |
307 | ||
f833126b CH |
308 | // ----------------------------------------------------------------------------- |
309 | // UPDATE | |
310 | // ----------------------------------------------------------------------------- | |
311 | ||
8f00b3df CH |
312 | /* |
313 | * warn if hostname is empty and remove trailing dot | |
314 | * also warn if protocol is neither IPv4 nor IPv6 | |
315 | */ | |
316 | ip_map_key_type DnsCache::key_for_ips(const std::string &hostname, | |
317 | const DnsIpProtocol &protocol) const | |
96779587 | 318 | { |
26b0f687 CH |
319 | if (hostname.empty()) |
320 | { | |
49b82a1d | 321 | GlobalLogger.info() << "DnsCache: empty host!"; |
8f00b3df CH |
322 | return ip_map_key_type("", DNS_IPALL); |
323 | } | |
324 | if (protocol == DNS_IPALL) | |
325 | { | |
326 | GlobalLogger.info() << "DnsCache: neither IPv4 nor v6!"; | |
327 | return ip_map_key_type("", DNS_IPALL); | |
26b0f687 CH |
328 | } |
329 | ||
330 | // check whether last character is a dot | |
331 | if (hostname.rfind('.') == hostname.length()-1) | |
8f00b3df CH |
332 | return ip_map_key_type( hostname.substr(0, hostname.length()-1), |
333 | protocol ); | |
26b0f687 | 334 | else |
8f00b3df CH |
335 | return ip_map_key_type( hostname, |
336 | protocol ); | |
96779587 CH |
337 | } |
338 | ||
339 | ||
ad83004d | 340 | void DnsCache::update(const std::string &hostname, |
8f00b3df | 341 | const DnsIpProtocol &protocol, |
26b0f687 | 342 | const HostAddressVec &new_ips) |
ad83004d | 343 | { |
8f00b3df CH |
344 | // check for valid input arguments |
345 | ip_map_key_type key = key_for_ips(hostname, protocol); | |
346 | if ( key.first.empty() ) | |
347 | return; | |
348 | ||
349 | // ensure that there is never IP and CNAME for the same host | |
26b0f687 | 350 | if ( !get_cname(hostname).Host.empty() ) |
8f00b3df CH |
351 | { |
352 | GlobalLogger.info() << "DnsCache: Saving IPs for " << key.first | |
26b0f687 CH |
353 | << " removes CNAME to " << get_cname(hostname).Host << "!"; |
354 | update(hostname, Cname()); // overwrite with "empty" cname | |
355 | } | |
8f00b3df | 356 | |
f833126b CH |
357 | // ensure min ttl of MinTimeBetweenResolves |
358 | HostAddressVec ips_checked; | |
359 | BOOST_FOREACH( const HostAddress &addr, new_ips ) | |
360 | { | |
361 | if ( addr.get_ttl().get_value() < MinTimeBetweenResolves ) | |
362 | { | |
363 | GlobalLogger.info() << "DnsCache: Correcting TTL of IP for " | |
8f00b3df | 364 | << key.first << " from " << addr.get_ttl().get_value() << "s to " |
f833126b CH |
365 | << MinTimeBetweenResolves << "s because was too short"; |
366 | ips_checked.push_back( HostAddress( addr.get_ip(), | |
367 | MinTimeBetweenResolves) ); | |
368 | } | |
369 | else | |
370 | ips_checked.push_back(addr); | |
371 | } | |
372 | ||
8f00b3df | 373 | // write IPs into one log line |
f636ba88 | 374 | stringstream log_temp; |
8f00b3df | 375 | log_temp << "DnsCache: update IPs for " << key.first << " to " |
f636ba88 CH |
376 | << ips_checked.size() << "-list: "; |
377 | BOOST_FOREACH( const HostAddress &ip, ips_checked ) | |
378 | log_temp << ip.get_ip() << ", "; | |
379 | GlobalLogger.notice() << log_temp.str(); | |
f833126b CH |
380 | |
381 | IpCache[key] = ips_checked; | |
ad83004d CH |
382 | HasChanged = true; |
383 | } | |
384 | ||
385 | ||
8f00b3df CH |
386 | /* |
387 | * warn if hostname is empty and remove trailing dot | |
388 | */ | |
389 | cname_map_key_type DnsCache::key_for_cname(const std::string &hostname) const | |
390 | { | |
391 | if (hostname.empty()) | |
392 | { | |
393 | GlobalLogger.info() << "DnsCache: empty host!"; | |
394 | return ""; | |
395 | } | |
396 | ||
397 | // check whether last character is a dot | |
398 | if (hostname.rfind('.') == hostname.length()-1) | |
399 | return hostname.substr(0, hostname.length()-1); | |
400 | else | |
401 | return hostname; | |
402 | } | |
403 | ||
404 | ||
dbe986b9 | 405 | void DnsCache::update(const std::string &hostname, |
26b0f687 | 406 | const Cname &cname) |
ad83004d | 407 | { |
8f00b3df CH |
408 | // check for valid input arguments |
409 | cname_map_key_type key = key_for_cname(hostname); | |
410 | if ( key.empty() ) | |
411 | return; | |
412 | ||
413 | // ensure that there is never IP and CNAME for the same host | |
414 | int n_ips = get_ips(hostname, DNS_IPv4).size() | |
415 | + get_ips(hostname, DNS_IPv6).size(); | |
416 | if ( n_ips > 0 ) | |
417 | { | |
418 | GlobalLogger.info() << "DnsCache: Saving IPs for " << key | |
419 | << " removes CNAME to " << get_cname(hostname).Host << "!"; | |
49b82a1d | 420 | GlobalLogger.info() << "DnsCache: Saving CNAME for " << key |
8f00b3df CH |
421 | << " removes " << n_ips << " IPs for same host!"; |
422 | update(hostname, DNS_IPv4, HostAddressVec()); | |
423 | update(hostname, DNS_IPv6, HostAddressVec()); | |
ad83004d | 424 | } |
26b0f687 | 425 | |
8f00b3df CH |
426 | // remove possible trailing dot from cname's target host |
427 | Cname to_save = Cname(key_for_cname(cname.Host), // implicit cast to string | |
26b0f687 CH |
428 | cname.Ttl); |
429 | ||
f833126b CH |
430 | // ensure min ttl of MinTimeBetweenResolves |
431 | if ( to_save.Ttl.get_value() < MinTimeBetweenResolves ) | |
432 | { | |
433 | GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of " | |
8f00b3df | 434 | << key << " from " << to_save.Ttl.get_value() << "s to " |
f833126b CH |
435 | << MinTimeBetweenResolves << "s because was too short"; |
436 | to_save.Ttl = TimeToLive(MinTimeBetweenResolves); | |
437 | } | |
438 | ||
f636ba88 | 439 | GlobalLogger.notice() << "DnsCache: update CNAME for " << key |
8f00b3df | 440 | << " to " << to_save.Host; |
26b0f687 CH |
441 | CnameCache[key] = to_save; |
442 | HasChanged = true; | |
ad83004d CH |
443 | } |
444 | ||
445 | ||
f833126b CH |
446 | // ----------------------------------------------------------------------------- |
447 | // RETRIEVAL | |
448 | // ----------------------------------------------------------------------------- | |
449 | ||
23f51766 CH |
450 | /** |
451 | * @returns empty list if no (up to date) ips for hostname in cache | |
452 | */ | |
dbe986b9 | 453 | HostAddressVec DnsCache::get_ips(const std::string &hostname, |
8f00b3df | 454 | const DnsIpProtocol &protocol, |
dbe986b9 | 455 | const bool check_up_to_date) |
96779587 | 456 | { |
8f00b3df | 457 | ip_map_key_type key = key_for_ips(hostname, protocol); |
26b0f687 | 458 | HostAddressVec result = IpCache[key]; |
dbe986b9 CH |
459 | if (check_up_to_date) |
460 | { | |
461 | HostAddressVec result_up_to_date; | |
23f51766 CH |
462 | uint32_t threshold = static_cast<uint32_t>( |
463 | DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() ); | |
26b0f687 | 464 | uint32_t updated_ttl; |
dbe986b9 CH |
465 | BOOST_FOREACH( const HostAddress &addr, result ) |
466 | { | |
26b0f687 CH |
467 | updated_ttl = addr.get_ttl().get_updated_value(); |
468 | if (updated_ttl > threshold) | |
dbe986b9 | 469 | result_up_to_date.push_back(addr); |
26b0f687 CH |
470 | else |
471 | GlobalLogger.debug() << "DnsCache: do not return " | |
472 | << addr.get_ip().to_string() << " since TTL " | |
473 | << updated_ttl << "s is out of date (thresh=" | |
474 | << threshold << "s)"; | |
dbe986b9 | 475 | } |
dbe986b9 CH |
476 | result = result_up_to_date; |
477 | } | |
8f00b3df | 478 | /*GlobalLogger.debug() << "DnsCache: request IPs for " << key.first |
26b0f687 CH |
479 | << " --> " << result.size() << "-list"; |
480 | BOOST_FOREACH( const HostAddress &addr, result ) | |
481 | GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string() | |
fd62d09f | 482 | << " (TTL " << addr.get_ttl().get_updated_value() |
2a4dde8b | 483 | << "s)"; */ |
dbe986b9 | 484 | return result; |
96779587 CH |
485 | } |
486 | ||
23f51766 CH |
487 | /** |
488 | * @returns empty cname if no (up to date cname) for hostname in cache | |
489 | */ | |
490 | Cname DnsCache::get_cname(const std::string &hostname, | |
491 | const bool check_up_to_date) | |
ad83004d | 492 | { |
8f00b3df | 493 | cname_map_key_type key = key_for_cname(hostname); |
26b0f687 | 494 | Cname result_obj = CnameCache[key]; |
2a4dde8b | 495 | /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key |
fd62d09f | 496 | << " --> \"" << result_obj.Host << "\" (TTL " |
2a4dde8b | 497 | << result_obj.Ttl.get_updated_value() << "s)";*/ |
23f51766 CH |
498 | if (result_obj.Host.empty()) |
499 | return result_obj; | |
500 | ||
501 | else if (check_up_to_date) | |
dbe986b9 | 502 | { |
26b0f687 CH |
503 | if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>( |
504 | DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) ) | |
23f51766 | 505 | return result_obj; |
dbe986b9 CH |
506 | else |
507 | { | |
23f51766 CH |
508 | GlobalLogger.debug() << "DnsCache: CNAME is out of date"; |
509 | return Cname(); // same as if there was no cname for hostname | |
dbe986b9 CH |
510 | } |
511 | } | |
512 | else | |
23f51766 | 513 | return result_obj; |
ad83004d CH |
514 | } |
515 | ||
dbe986b9 CH |
516 | // underlying assumption in this function: for a hostname, the cache has either |
517 | // a list of IPs saved or a cname saved, but never both | |
518 | HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname, | |
8f00b3df | 519 | const DnsIpProtocol &protocol, |
dbe986b9 | 520 | const bool check_up_to_date) |
e18c1337 CH |
521 | { |
522 | std::string current_host = hostname; | |
23f51766 | 523 | Cname current_cname; |
8f00b3df | 524 | HostAddressVec result = get_ips(current_host, protocol, check_up_to_date); |
dbe986b9 | 525 | int n_recursions = 0; |
23f51766 | 526 | uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value |
cd71d095 CH |
527 | int max_recursion_count = DnsMaster::get_instance() |
528 | ->get_max_recursion_count(); | |
e18c1337 CH |
529 | while ( result.empty() ) |
530 | { | |
23f51766 | 531 | current_cname = get_cname(current_host, check_up_to_date); |
8d26221d | 532 | if (current_cname.Host.empty()) |
8f00b3df CH |
533 | break; // no ips (since result.empty()) and no cname |
534 | // --> will return empty result | |
8d26221d | 535 | |
8f00b3df | 536 | current_host = current_cname.Host; |
cd71d095 | 537 | if (++n_recursions >= max_recursion_count) |
dbe986b9 | 538 | { |
49b82a1d CH |
539 | GlobalLogger.info() << "DnsCache: reached recursion limit of " |
540 | << n_recursions << " in recursive IP retrieval of " | |
541 | << hostname << "!"; | |
dbe986b9 CH |
542 | break; |
543 | } | |
e18c1337 | 544 | else |
23f51766 | 545 | { |
fd62d09f CH |
546 | min_cname_ttl = min(min_cname_ttl, |
547 | current_cname.Ttl.get_updated_value()); | |
8f00b3df | 548 | result = get_ips(current_host, protocol, check_up_to_date); |
23f51766 CH |
549 | } |
550 | } | |
551 | ||
552 | GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in " | |
553 | << result.size() << "-list after " << n_recursions | |
554 | << " recursions"; | |
555 | ||
556 | // adjust ttl to min of ttl and min_cname_ttl | |
557 | if (n_recursions > 0) | |
558 | { | |
559 | TimeToLive cname_ttl(min_cname_ttl); | |
560 | ||
561 | BOOST_FOREACH( HostAddress &addr, result ) | |
562 | { | |
fd62d09f | 563 | if (addr.get_ttl().get_updated_value() > min_cname_ttl) |
8d26221d | 564 | { |
2a4dde8b | 565 | //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL"; |
23f51766 | 566 | addr.set_ttl(cname_ttl); |
8d26221d | 567 | } |
23f51766 | 568 | } |
e18c1337 | 569 | } |
23f51766 | 570 | |
e18c1337 CH |
571 | return result; |
572 | } | |
ad83004d | 573 | |
23f51766 | 574 | /** |
9d1b2726 CH |
575 | * from a list of CNAMEs find the first one that is out of date or empty |
576 | * | |
577 | * returns the hostname that is out of date or empty if all CNAMEs are | |
578 | * up-to-date | |
23f51766 CH |
579 | * |
580 | * required in ResolverBase::get_skipper | |
23f51766 CH |
581 | */ |
582 | std::string DnsCache::get_first_outdated_cname(const std::string &hostname, | |
583 | const uint32_t ttl_thresh) | |
584 | { | |
8d26221d | 585 | std::string first_outdated = hostname; |
23f51766 CH |
586 | Cname cname; |
587 | int n_recursions = 0; | |
cd71d095 CH |
588 | int max_recursion_count = DnsMaster::get_instance() |
589 | ->get_max_recursion_count(); | |
23f51766 CH |
590 | while (true) |
591 | { | |
cd71d095 | 592 | if (++n_recursions >= max_recursion_count) |
23f51766 | 593 | { |
49b82a1d CH |
594 | GlobalLogger.info() << "DnsCache: reached recursion limit of " |
595 | << n_recursions << " in search of outdated CNAMEs for " | |
596 | << hostname << "!"; | |
9d1b2726 CH |
597 | return first_outdated; // not really out of date but currently |
598 | } // our best guess | |
23f51766 | 599 | |
8d26221d CH |
600 | cname = get_cname(first_outdated); |
601 | if (cname.Host.empty()) | |
9d1b2726 CH |
602 | // reached end of cname list --> everything was up-to-date |
603 | return ""; | |
23f51766 CH |
604 | else if (cname.Ttl.get_updated_value() > ttl_thresh) |
605 | // cname is up to date --> continue looking | |
8d26221d | 606 | first_outdated = cname.Host; |
23f51766 | 607 | else |
9d1b2726 CH |
608 | // cname is out of date --> return its target |
609 | return cname.Host; | |
23f51766 | 610 | } |
9d1b2726 CH |
611 | // reach this point only if cname chain does not end with an IP |
612 | // --> all are up-to-date | |
613 | return ""; | |
23f51766 | 614 | } |
e638894d CH |
615 | |
616 | std::string DnsCache::get_cname_chain_str(const std::string &hostname) | |
617 | { | |
618 | std::stringstream temp; | |
619 | temp << hostname; | |
620 | std::string current_host = hostname; | |
621 | Cname current_cname; | |
622 | int n_recursions = 0; | |
623 | int max_recursion_count = DnsMaster::get_instance() | |
624 | ->get_max_recursion_count(); | |
625 | while (true) | |
626 | { | |
627 | if (n_recursions >= max_recursion_count) | |
628 | { | |
629 | temp << "..."; | |
630 | break; | |
631 | } | |
632 | ||
633 | current_cname = get_cname(current_host, false); | |
634 | if (current_cname.Host.empty()) | |
635 | break; | |
636 | else | |
637 | { | |
638 | current_host = current_cname.Host; | |
639 | temp << "-->" << current_host; | |
a176d17a | 640 | ++n_recursions; |
e638894d CH |
641 | } |
642 | } | |
643 | return temp.str(); | |
644 | } | |
81cf5681 CH |
645 | |
646 | ||
647 | // ----------------------------------------------------------------------------- | |
648 | // OTHER | |
649 | // ----------------------------------------------------------------------------- | |
650 | void DnsCache::debug_print() const | |
651 | { | |
652 | GlobalLogger.debug() << "DnsCache: IP Cache contents:"; | |
653 | stringstream log_temp; | |
654 | BOOST_FOREACH( const ip_map_type::value_type &key_and_ip, IpCache ) | |
655 | { | |
656 | // write IPs into one log line | |
657 | log_temp.str(""); | |
658 | log_temp << "DnsCache: " << key_and_ip.first.first << ": \t " | |
659 | << key_and_ip.second.size() << "-list "; | |
660 | BOOST_FOREACH( const HostAddress &ip, key_and_ip.second ) | |
661 | log_temp << ip.get_ip() << "+" << ip.get_ttl().get_updated_value() | |
662 | << "s, "; | |
663 | GlobalLogger.debug() << log_temp.str(); | |
664 | } | |
665 | ||
666 | GlobalLogger.debug() << "DnsCache: CNAME Cache contents:"; | |
667 | BOOST_FOREACH( const cname_map_type::value_type &key_and_cname, CnameCache ) | |
668 | GlobalLogger.debug() << "DnsCache: " << key_and_cname.first << ": \t " | |
669 | << key_and_cname.second.Host << "+" | |
670 | << key_and_cname.second.Ttl.get_updated_value() | |
671 | << "s"; | |
672 | } |