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_HOURS = 24;
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 cast 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::hours( Config::CACHE_TIME_WARP_THRESH_HOURS );
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( const ip_map_type::value_type key_and_ip, IpCache )
228 HostAddressVec addr_reset;
229 BOOST_FOREACH( HostAddress address, key_and_ip.second )
230 addr_reset.push_back( HostAddress( address.get_ip(), 0 ) );
231 IpCache[key_and_ip.first] = addr_reset;
234 // reset TTLs in CNAME cache
235 BOOST_FOREACH( const cname_map_type::value_type key_and_cname, CnameCache )
236 CnameCache[key_and_cname.first] = Cname( key_and_cname.second.Host,
244 * @brief remove entries from cache that are older than a certain threshold
246 * this also removes entres from IpCache with no IPs
248 void DnsCache::remove_old_entries()
250 boost::posix_time::ptime thresh
251 = boost::posix_time::second_clock::universal_time()
252 - boost::gregorian::days( Config::CACHE_REMOVE_OUTDATED_DAYS );
255 bool some_ip_up_to_date;
256 BOOST_FOREACH( const ip_map_type::value_type key_and_ip, IpCache )
258 some_ip_up_to_date = false;
259 BOOST_FOREACH( HostAddress address, key_and_ip.second )
261 if ( ! address.get_ttl().was_set_before(thresh) )
263 some_ip_up_to_date = true;
268 if ( ! some_ip_up_to_date )
270 GlobalLogger.debug() << "DnsCache: Removing empty/outdated IP list "
271 << "for " << key_and_ip.first.first;
272 IpCache.erase( key_and_ip.first );
277 BOOST_FOREACH( const cname_map_type::value_type key_and_cname, CnameCache )
279 if ( key_and_cname.second.Ttl.was_set_before( thresh ) )
281 GlobalLogger.debug() << "DnsCache: Removing outdated CNAME for "
282 << key_and_cname.first;
283 CnameCache.erase( key_and_cname.first );
288 // -----------------------------------------------------------------------------
290 // -----------------------------------------------------------------------------
293 * warn if hostname is empty and remove trailing dot
294 * also warn if protocol is neither IPv4 nor IPv6
296 ip_map_key_type DnsCache::key_for_ips(const std::string &hostname,
297 const DnsIpProtocol &protocol) const
299 if (hostname.empty())
301 GlobalLogger.info() << "DnsCache: empty host!";
302 return ip_map_key_type("", DNS_IPALL);
304 if (protocol == DNS_IPALL)
306 GlobalLogger.info() << "DnsCache: neither IPv4 nor v6!";
307 return ip_map_key_type("", DNS_IPALL);
310 // check whether last character is a dot
311 if (hostname.rfind('.') == hostname.length()-1)
312 return ip_map_key_type( hostname.substr(0, hostname.length()-1),
315 return ip_map_key_type( hostname,
320 void DnsCache::update(const std::string &hostname,
321 const DnsIpProtocol &protocol,
322 const HostAddressVec &new_ips)
324 // check for valid input arguments
325 ip_map_key_type key = key_for_ips(hostname, protocol);
326 if ( key.first.empty() )
329 // ensure that there is never IP and CNAME for the same host
330 if ( !get_cname(hostname).Host.empty() )
332 GlobalLogger.info() << "DnsCache: Saving IPs for " << key.first
333 << " removes CNAME to " << get_cname(hostname).Host << "!";
334 update(hostname, Cname()); // overwrite with "empty" cname
337 // ensure min ttl of MinTimeBetweenResolves
338 HostAddressVec ips_checked;
339 BOOST_FOREACH( const HostAddress &addr, new_ips )
341 if ( addr.get_ttl().get_value() < MinTimeBetweenResolves )
343 GlobalLogger.info() << "DnsCache: Correcting TTL of IP for "
344 << key.first << " from " << addr.get_ttl().get_value() << "s to "
345 << MinTimeBetweenResolves << "s because was too short";
346 ips_checked.push_back( HostAddress( addr.get_ip(),
347 MinTimeBetweenResolves) );
350 ips_checked.push_back(addr);
353 // write IPs into one log line
354 stringstream log_temp;
355 log_temp << "DnsCache: update IPs for " << key.first << " to "
356 << ips_checked.size() << "-list: ";
357 BOOST_FOREACH( const HostAddress &ip, ips_checked )
358 log_temp << ip.get_ip() << ", ";
359 GlobalLogger.notice() << log_temp.str();
361 IpCache[key] = ips_checked;
367 * warn if hostname is empty and remove trailing dot
369 cname_map_key_type DnsCache::key_for_cname(const std::string &hostname) const
371 if (hostname.empty())
373 GlobalLogger.info() << "DnsCache: empty host!";
377 // check whether last character is a dot
378 if (hostname.rfind('.') == hostname.length()-1)
379 return hostname.substr(0, hostname.length()-1);
385 void DnsCache::update(const std::string &hostname,
388 // check for valid input arguments
389 cname_map_key_type key = key_for_cname(hostname);
393 // ensure that there is never IP and CNAME for the same host
394 int n_ips = get_ips(hostname, DNS_IPv4).size()
395 + get_ips(hostname, DNS_IPv6).size();
398 GlobalLogger.info() << "DnsCache: Saving IPs for " << key
399 << " removes CNAME to " << get_cname(hostname).Host << "!";
400 GlobalLogger.info() << "DnsCache: Saving CNAME for " << key
401 << " removes " << n_ips << " IPs for same host!";
402 update(hostname, DNS_IPv4, HostAddressVec());
403 update(hostname, DNS_IPv6, HostAddressVec());
406 // remove possible trailing dot from cname's target host
407 Cname to_save = Cname(key_for_cname(cname.Host), // implicit cast to string
410 // ensure min ttl of MinTimeBetweenResolves
411 if ( to_save.Ttl.get_value() < MinTimeBetweenResolves )
413 GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of "
414 << key << " from " << to_save.Ttl.get_value() << "s to "
415 << MinTimeBetweenResolves << "s because was too short";
416 to_save.Ttl = TimeToLive(MinTimeBetweenResolves);
419 GlobalLogger.notice() << "DnsCache: update CNAME for " << key
420 << " to " << to_save.Host;
421 CnameCache[key] = to_save;
426 // -----------------------------------------------------------------------------
428 // -----------------------------------------------------------------------------
431 * @returns empty list if no (up to date) ips for hostname in cache
433 HostAddressVec DnsCache::get_ips(const std::string &hostname,
434 const DnsIpProtocol &protocol,
435 const bool check_up_to_date)
437 ip_map_key_type key = key_for_ips(hostname, protocol);
438 HostAddressVec result = IpCache[key];
439 if (check_up_to_date)
441 HostAddressVec result_up_to_date;
442 uint32_t threshold = static_cast<uint32_t>(
443 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
444 uint32_t updated_ttl;
445 BOOST_FOREACH( const HostAddress &addr, result )
447 updated_ttl = addr.get_ttl().get_updated_value();
448 if (updated_ttl > threshold)
449 result_up_to_date.push_back(addr);
451 GlobalLogger.debug() << "DnsCache: do not return "
452 << addr.get_ip().to_string() << " since TTL "
453 << updated_ttl << "s is out of date (thresh="
454 << threshold << "s)";
456 result = result_up_to_date;
458 /*GlobalLogger.debug() << "DnsCache: request IPs for " << key.first
459 << " --> " << result.size() << "-list";
460 BOOST_FOREACH( const HostAddress &addr, result )
461 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
462 << " (TTL " << addr.get_ttl().get_updated_value()
468 * @returns empty cname if no (up to date cname) for hostname in cache
470 Cname DnsCache::get_cname(const std::string &hostname,
471 const bool check_up_to_date)
473 cname_map_key_type key = key_for_cname(hostname);
474 Cname result_obj = CnameCache[key];
475 /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key
476 << " --> \"" << result_obj.Host << "\" (TTL "
477 << result_obj.Ttl.get_updated_value() << "s)";*/
478 if (result_obj.Host.empty())
481 else if (check_up_to_date)
483 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
484 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
488 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
489 return Cname(); // same as if there was no cname for hostname
496 // underlying assumption in this function: for a hostname, the cache has either
497 // a list of IPs saved or a cname saved, but never both
498 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
499 const DnsIpProtocol &protocol,
500 const bool check_up_to_date)
502 std::string current_host = hostname;
504 HostAddressVec result = get_ips(current_host, protocol, check_up_to_date);
505 int n_recursions = 0;
506 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
507 int max_recursion_count = DnsMaster::get_instance()
508 ->get_max_recursion_count();
509 while ( result.empty() )
511 current_cname = get_cname(current_host, check_up_to_date);
512 if (current_cname.Host.empty())
513 break; // no ips (since result.empty()) and no cname
514 // --> will return empty result
516 current_host = current_cname.Host;
517 if (++n_recursions >= max_recursion_count)
519 GlobalLogger.info() << "DnsCache: reached recursion limit of "
520 << n_recursions << " in recursive IP retrieval of "
526 min_cname_ttl = min(min_cname_ttl,
527 current_cname.Ttl.get_updated_value());
528 result = get_ips(current_host, protocol, check_up_to_date);
532 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
533 << result.size() << "-list after " << n_recursions
536 // adjust ttl to min of ttl and min_cname_ttl
537 if (n_recursions > 0)
539 TimeToLive cname_ttl(min_cname_ttl);
541 BOOST_FOREACH( HostAddress &addr, result )
543 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
545 //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
546 addr.set_ttl(cname_ttl);
555 * from a list of CNAMEs find the first one that is out of date or empty
557 * returns the hostname that is out of date or empty if all CNAMEs are
560 * required in ResolverBase::get_skipper
562 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
563 const uint32_t ttl_thresh)
565 std::string first_outdated = hostname;
567 int n_recursions = 0;
568 int max_recursion_count = DnsMaster::get_instance()
569 ->get_max_recursion_count();
572 if (++n_recursions >= max_recursion_count)
574 GlobalLogger.info() << "DnsCache: reached recursion limit of "
575 << n_recursions << " in search of outdated CNAMEs for "
577 return first_outdated; // not really out of date but currently
580 cname = get_cname(first_outdated);
581 if (cname.Host.empty())
582 // reached end of cname list --> everything was up-to-date
584 else if (cname.Ttl.get_updated_value() > ttl_thresh)
585 // cname is up to date --> continue looking
586 first_outdated = cname.Host;
588 // cname is out of date --> return its target
591 // reach this point only if cname chain does not end with an IP
592 // --> all are up-to-date
596 std::string DnsCache::get_cname_chain_str(const std::string &hostname)
598 std::stringstream temp;
600 std::string current_host = hostname;
602 int n_recursions = 0;
603 int max_recursion_count = DnsMaster::get_instance()
604 ->get_max_recursion_count();
607 if (n_recursions >= max_recursion_count)
613 current_cname = get_cname(current_host, false);
614 if (current_cname.Host.empty())
618 current_host = current_cname.Host;
619 temp << "-->" << current_host;