2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
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.
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.
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.
20 Christian Herdtweck, Intra2net AG 2015
23 #include "dns/dnscache.h"
27 #include <logfunc.hpp>
28 #include <filefunc.hxx> // I2n::file_exists
29 #include <boost/foreach.hpp>
30 #include <boost/bind.hpp>
31 #include <boost/asio/placeholders.hpp>
32 #include <boost/serialization/serialization.hpp>
33 #include <boost/serialization/map.hpp>
34 #include <boost/serialization/string.hpp>
35 #include <boost/serialization/vector.hpp>
36 #include <boost/archive/xml_oarchive.hpp>
37 #include <boost/archive/xml_iarchive.hpp>
38 #include <boost/date_time/gregorian/gregorian_types.hpp>
40 #include "dns/dnsmaster.h"
43 using boost::posix_time::seconds;
44 using I2n::Logger::GlobalLogger;
49 const int SAVE_TIMER_SECONDS = 60;
50 const int CACHE_TIME_WARP_THRESH_MINS = 10;
51 const int CACHE_REMOVE_OUTDATED_DAYS = 60;
55 // -----------------------------------------------------------------------------
56 // DNS Cache constructor / destructor
57 // -----------------------------------------------------------------------------
59 const string DnsCache::DoNotUseCacheFile = "do not use cache file!";
61 DnsCache::DnsCache(const IoServiceItem &io_serv,
62 const std::string &cache_file,
63 const uint32_t min_time_between_resolves)
66 , SaveTimer( *io_serv )
67 , CacheFile( cache_file )
69 , MinTimeBetweenResolves( min_time_between_resolves )
71 // load cache from file
72 load_from_cachefile();
75 (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
76 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
77 boost::asio::placeholders::error ) );
83 GlobalLogger.info() << "DnsCache: being destructed";
85 // save one last time without re-scheduling the next save
93 // -----------------------------------------------------------------------------
95 // -----------------------------------------------------------------------------
97 void DnsCache::schedule_save(const boost::system::error_code &error)
99 if ( error == boost::asio::error::operation_aborted ) // cancelled
101 GlobalLogger.info() << "DnsCache: SaveTimer was cancelled "
102 << "--> no save and no re-schedule of saving!";
107 GlobalLogger.info() << "DnsCache: Received error " << error
108 << " in schedule_save "
109 << "--> no save now but re-schedule saving";
114 // schedule next save
115 (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
116 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
117 boost::asio::placeholders::error ) );
120 void DnsCache::save_to_cachefile()
123 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
124 else if (CacheFile.empty())
126 << "DnsCache: skip saving because file name empty!";
127 else if (CacheFile == DoNotUseCacheFile)
128 GlobalLogger.info() << "DnsCache: configured not to use cache file";
133 // clean up: remove very old entries
134 remove_old_entries();
136 // remember time of save
137 std::string cache_save_time_str = boost::posix_time::to_iso_string(
138 boost::posix_time::second_clock::universal_time() );
140 std::ofstream ofs( CacheFile.c_str() );
141 boost::archive::xml_oarchive oa(ofs);
142 oa & BOOST_SERIALIZATION_NVP(IpCache);
143 oa & BOOST_SERIALIZATION_NVP(CnameCache);
144 oa & BOOST_SERIALIZATION_NVP(cache_save_time_str);
145 GlobalLogger.info() << "DnsCache: saved to cache file "
150 catch (std::exception &exc)
152 GlobalLogger.warning() << "DnsCache: Saving failed: " << exc.what();
157 void DnsCache::load_from_cachefile()
159 if (CacheFile.empty())
161 << "DnsCache: cannot load because cache file name is empty!";
162 else if (CacheFile == DoNotUseCacheFile)
163 GlobalLogger.info() << "DnsCache: configured not to use cache file";
164 else if ( !I2n::file_exists(CacheFile) )
165 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
166 << CacheFile << " does not exist!";
171 std::ifstream ifs( CacheFile.c_str() );
172 boost::archive::xml_iarchive ia(ifs);
174 std::string cache_save_time_str;
176 ia & BOOST_SERIALIZATION_NVP(IpCache);
177 ia & BOOST_SERIALIZATION_NVP(CnameCache);
178 ia & BOOST_SERIALIZATION_NVP(cache_save_time_str);
180 boost::posix_time::ptime cache_save_time
181 = boost::posix_time::from_iso_string(cache_save_time_str);
182 GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
184 check_timestamps(cache_save_time);
186 catch (boost::archive::archive_exception &exc)
188 GlobalLogger.warning()
189 << "DnsCache: archive exception loading from " << CacheFile
190 << ": " << exc.what();
192 catch (std::exception &exc)
194 GlobalLogger.warning() << "DnsCache: exception while loading from "
195 << CacheFile << ": " << exc.what();
202 * @brief check that loaded cache really is from the past
204 * Added this to avoid trouble in case the system time is changed into the past.
205 * In that case would have TTLs here in cache that are valid much too long.
207 * Therefore check SaveCacheTime and if that is in the future, set all TTLs to 0
209 * @returns true if had to re-set timestamps
211 bool DnsCache::check_timestamps(const boost::posix_time::ptime &cache_save_time)
213 // check if CacheSaveTime is in the future
214 boost::posix_time::ptime now
215 = boost::posix_time::second_clock::universal_time()
216 + boost::posix_time::minutes(Config::CACHE_TIME_WARP_THRESH_MINS);
217 if (now > cache_save_time)
218 { // Cache was saved in the past -- everything alright
222 GlobalLogger.warning() << "DnsCache: loaded cache from the future (saved "
223 << cache_save_time << ")! Resetting all TTLs to 0.";
225 // reset TTLs in IP cache
226 BOOST_FOREACH( ip_map_type::value_type &key_and_ip, IpCache )
228 BOOST_FOREACH( HostAddress &address, key_and_ip.second )
229 address.get_ttl().set_value(0);
232 // reset TTLs in CNAME cache
233 BOOST_FOREACH( cname_map_type::value_type &key_and_cname, CnameCache )
234 key_and_cname.second.Ttl.set_value(0);
245 * @brief remove entries from cache that are older than a certain threshold
247 * this also removes entres from IpCache with no IPs
249 void DnsCache::remove_old_entries()
251 boost::posix_time::ptime thresh
252 = boost::posix_time::second_clock::universal_time()
253 - boost::gregorian::days( Config::CACHE_REMOVE_OUTDATED_DAYS );
257 ip_map_type::iterator it = IpCache.begin();
258 ip_map_type::iterator it_end = IpCache.end();
259 bool some_ip_up_to_date;
262 some_ip_up_to_date = false;
263 BOOST_FOREACH( const HostAddress &address, (*it).second )
265 if ( ! address.get_ttl().was_set_before(thresh) )
267 some_ip_up_to_date = true;
272 if ( ! some_ip_up_to_date )
274 GlobalLogger.debug() << "DnsCache: Removing empty/outdated IP "
275 << "list for " << (*it).first.first;
276 IpCache.erase( (*it++).first );
285 cname_map_type::iterator it = CnameCache.begin();
286 cname_map_type::iterator it_end = CnameCache.end();
289 if ( (*it).second.Ttl.was_set_before( thresh ) )
291 GlobalLogger.debug() << "DnsCache: Removing outdated CNAME for "
293 CnameCache.erase( (*it++).first );
301 // -----------------------------------------------------------------------------
303 // -----------------------------------------------------------------------------
306 * warn if hostname is empty and remove trailing dot
307 * also warn if protocol is neither IPv4 nor IPv6
309 ip_map_key_type DnsCache::key_for_ips(const std::string &hostname,
310 const DnsIpProtocol &protocol) const
312 if (hostname.empty())
314 GlobalLogger.info() << "DnsCache: empty host!";
315 return ip_map_key_type("", DNS_IPALL);
317 if (protocol == DNS_IPALL)
319 GlobalLogger.info() << "DnsCache: neither IPv4 nor v6!";
320 return ip_map_key_type("", DNS_IPALL);
323 // check whether last character is a dot
324 if (hostname.rfind('.') == hostname.length()-1)
325 return ip_map_key_type( hostname.substr(0, hostname.length()-1),
328 return ip_map_key_type( hostname,
333 void DnsCache::update(const std::string &hostname,
334 const DnsIpProtocol &protocol,
335 const HostAddressVec &new_ips)
337 // check for valid input arguments
338 ip_map_key_type key = key_for_ips(hostname, protocol);
339 if ( key.first.empty() )
342 // ensure that there is never IP and CNAME for the same host
343 if ( !get_cname(hostname).Host.empty() )
345 GlobalLogger.info() << "DnsCache: Saving IPs for " << key.first
346 << " removes CNAME to " << get_cname(hostname).Host << "!";
347 update(hostname, Cname()); // overwrite with "empty" cname
350 // ensure min ttl of MinTimeBetweenResolves
351 HostAddressVec ips_checked;
352 BOOST_FOREACH( const HostAddress &addr, new_ips )
354 if ( addr.get_ttl().get_value() < MinTimeBetweenResolves )
356 GlobalLogger.info() << "DnsCache: Correcting TTL of IP for "
357 << key.first << " from " << addr.get_ttl().get_value() << "s to "
358 << MinTimeBetweenResolves << "s because was too short";
359 ips_checked.push_back( HostAddress( addr.get_ip(),
360 MinTimeBetweenResolves) );
363 ips_checked.push_back(addr);
366 // write IPs into one log line
367 stringstream log_temp;
368 log_temp << "DnsCache: update IPs for " << key.first << " to "
369 << ips_checked.size() << "-list: ";
370 BOOST_FOREACH( const HostAddress &ip, ips_checked )
371 log_temp << ip.get_ip() << ", ";
372 GlobalLogger.notice() << log_temp.str();
374 IpCache[key] = ips_checked;
380 * warn if hostname is empty and remove trailing dot
382 cname_map_key_type DnsCache::key_for_cname(const std::string &hostname) const
384 if (hostname.empty())
386 GlobalLogger.info() << "DnsCache: empty host!";
390 // check whether last character is a dot
391 if (hostname.rfind('.') == hostname.length()-1)
392 return hostname.substr(0, hostname.length()-1);
398 void DnsCache::update(const std::string &hostname,
401 // check for valid input arguments
402 cname_map_key_type key = key_for_cname(hostname);
406 // ensure that there is never IP and CNAME for the same host
407 int n_ips = get_ips(hostname, DNS_IPv4).size()
408 + get_ips(hostname, DNS_IPv6).size();
411 GlobalLogger.info() << "DnsCache: Saving IPs for " << key
412 << " removes CNAME to " << get_cname(hostname).Host << "!";
413 GlobalLogger.info() << "DnsCache: Saving CNAME for " << key
414 << " removes " << n_ips << " IPs for same host!";
415 update(hostname, DNS_IPv4, HostAddressVec());
416 update(hostname, DNS_IPv6, HostAddressVec());
419 // remove possible trailing dot from cname's target host
420 Cname to_save = Cname(key_for_cname(cname.Host), // implicit cast to string
423 // ensure min ttl of MinTimeBetweenResolves
424 if ( to_save.Ttl.get_value() < MinTimeBetweenResolves )
426 GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of "
427 << key << " from " << to_save.Ttl.get_value() << "s to "
428 << MinTimeBetweenResolves << "s because was too short";
429 to_save.Ttl = TimeToLive(MinTimeBetweenResolves);
432 GlobalLogger.notice() << "DnsCache: update CNAME for " << key
433 << " to " << to_save.Host;
434 CnameCache[key] = to_save;
439 // -----------------------------------------------------------------------------
441 // -----------------------------------------------------------------------------
444 * @returns empty list if no (up to date) ips for hostname in cache
446 HostAddressVec DnsCache::get_ips(const std::string &hostname,
447 const DnsIpProtocol &protocol,
448 const bool check_up_to_date)
450 ip_map_key_type key = key_for_ips(hostname, protocol);
451 HostAddressVec result = IpCache[key];
452 if (check_up_to_date)
454 HostAddressVec result_up_to_date;
455 uint32_t threshold = static_cast<uint32_t>(
456 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
457 uint32_t updated_ttl;
458 BOOST_FOREACH( const HostAddress &addr, result )
460 updated_ttl = addr.get_ttl().get_updated_value();
461 if (updated_ttl > threshold)
462 result_up_to_date.push_back(addr);
464 GlobalLogger.debug() << "DnsCache: do not return "
465 << addr.get_ip().to_string() << " since TTL "
466 << updated_ttl << "s is out of date (thresh="
467 << threshold << "s)";
469 result = result_up_to_date;
471 /*GlobalLogger.debug() << "DnsCache: request IPs for " << key.first
472 << " --> " << result.size() << "-list";
473 BOOST_FOREACH( const HostAddress &addr, result )
474 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
475 << " (TTL " << addr.get_ttl().get_updated_value()
481 * @returns empty cname if no (up to date cname) for hostname in cache
483 Cname DnsCache::get_cname(const std::string &hostname,
484 const bool check_up_to_date)
486 cname_map_key_type key = key_for_cname(hostname);
487 Cname result_obj = CnameCache[key];
488 /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key
489 << " --> \"" << result_obj.Host << "\" (TTL "
490 << result_obj.Ttl.get_updated_value() << "s)";*/
491 if (result_obj.Host.empty())
494 else if (check_up_to_date)
496 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
497 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
501 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
502 return Cname(); // same as if there was no cname for hostname
509 // underlying assumption in this function: for a hostname, the cache has either
510 // a list of IPs saved or a cname saved, but never both
511 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
512 const DnsIpProtocol &protocol,
513 const bool check_up_to_date)
515 std::string current_host = hostname;
517 HostAddressVec result = get_ips(current_host, protocol, check_up_to_date);
518 int n_recursions = 0;
519 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
520 int max_recursion_count = DnsMaster::get_instance()
521 ->get_max_recursion_count();
522 while ( result.empty() )
524 current_cname = get_cname(current_host, check_up_to_date);
525 if (current_cname.Host.empty())
526 break; // no ips (since result.empty()) and no cname
527 // --> will return empty result
529 current_host = current_cname.Host;
530 if (++n_recursions >= max_recursion_count)
532 GlobalLogger.info() << "DnsCache: reached recursion limit of "
533 << n_recursions << " in recursive IP retrieval of "
539 min_cname_ttl = min(min_cname_ttl,
540 current_cname.Ttl.get_updated_value());
541 result = get_ips(current_host, protocol, check_up_to_date);
545 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
546 << result.size() << "-list after " << n_recursions
549 // adjust ttl to min of ttl and min_cname_ttl
550 if (n_recursions > 0)
552 TimeToLive cname_ttl(min_cname_ttl);
554 BOOST_FOREACH( HostAddress &addr, result )
556 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
558 //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
559 addr.set_ttl(cname_ttl);
568 * from a list of CNAMEs find the first one that is out of date or empty
570 * returns the hostname that is out of date or empty if all CNAMEs are
573 * required in ResolverBase::get_skipper
575 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
576 const uint32_t ttl_thresh)
578 std::string first_outdated = hostname;
580 int n_recursions = 0;
581 int max_recursion_count = DnsMaster::get_instance()
582 ->get_max_recursion_count();
585 if (++n_recursions >= max_recursion_count)
587 GlobalLogger.info() << "DnsCache: reached recursion limit of "
588 << n_recursions << " in search of outdated CNAMEs for "
590 return first_outdated; // not really out of date but currently
593 cname = get_cname(first_outdated);
594 if (cname.Host.empty())
595 // reached end of cname list --> everything was up-to-date
597 else if (cname.Ttl.get_updated_value() > ttl_thresh)
598 // cname is up to date --> continue looking
599 first_outdated = cname.Host;
601 // cname is out of date --> return its target
604 // reach this point only if cname chain does not end with an IP
605 // --> all are up-to-date
609 std::string DnsCache::get_cname_chain_str(const std::string &hostname)
611 std::stringstream temp;
613 std::string current_host = hostname;
615 int n_recursions = 0;
616 int max_recursion_count = DnsMaster::get_instance()
617 ->get_max_recursion_count();
620 if (n_recursions >= max_recursion_count)
626 current_cname = get_cname(current_host, false);
627 if (current_cname.Host.empty())
631 current_host = current_cname.Host;
632 temp << "-->" << current_host;
640 // -----------------------------------------------------------------------------
642 // -----------------------------------------------------------------------------
643 void DnsCache::debug_print() const
645 GlobalLogger.debug() << "DnsCache: IP Cache contents:";
646 stringstream log_temp;
647 BOOST_FOREACH( const ip_map_type::value_type &key_and_ip, IpCache )
649 // write IPs into one log line
651 log_temp << "DnsCache: " << key_and_ip.first.first << ": \t "
652 << key_and_ip.second.size() << "-list ";
653 BOOST_FOREACH( const HostAddress &ip, key_and_ip.second )
654 log_temp << ip.get_ip() << "+" << ip.get_ttl().get_updated_value()
656 GlobalLogger.debug() << log_temp.str();
659 GlobalLogger.debug() << "DnsCache: CNAME Cache contents:";
660 BOOST_FOREACH( const cname_map_type::value_type &key_and_cname, CnameCache )
661 GlobalLogger.debug() << "DnsCache: " << key_and_cname.first << ": \t "
662 << key_and_cname.second.Host << "+"
663 << key_and_cname.second.Ttl.get_updated_value()