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>
39 #include "dns/dnsmaster.h"
42 using boost::posix_time::seconds;
43 using I2n::Logger::GlobalLogger;
48 const int SAVE_TIMER_SECONDS = 60;
49 const int CACHE_TIME_WARP_THRESH_HOURS = 24;
50 const int CACHE_REMOVE_OUTDATED_DAYS = 60;
54 // -----------------------------------------------------------------------------
55 // DNS Cache constructor / destructor
56 // -----------------------------------------------------------------------------
58 const string DnsCache::DoNotUseCacheFile = "do not use cache file!";
60 DnsCache::DnsCache(const IoServiceItem &io_serv,
61 const std::string &cache_file,
62 const uint32_t min_time_between_resolves)
65 , SaveTimer( *io_serv )
66 , CacheFile( cache_file )
68 , MinTimeBetweenResolves( min_time_between_resolves )
70 // load cache from file
71 load_from_cachefile();
74 (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
75 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
76 boost::asio::placeholders::error ) );
82 GlobalLogger.info() << "DnsCache: being destructed";
84 // save one last time without re-scheduling the next save
92 // -----------------------------------------------------------------------------
94 // -----------------------------------------------------------------------------
96 void DnsCache::schedule_save(const boost::system::error_code &error)
98 if ( error == boost::asio::error::operation_aborted ) // cancelled
100 GlobalLogger.info() << "DnsCache: SaveTimer was cancelled "
101 << "--> no save and no re-schedule of saving!";
106 GlobalLogger.info() << "DnsCache: Received error " << error
107 << " in schedule_save "
108 << "--> no save now but re-schedule saving";
113 // schedule next save
114 (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
115 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
116 boost::asio::placeholders::error ) );
119 void DnsCache::save_to_cachefile()
122 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
123 else if (CacheFile.empty())
125 << "DnsCache: skip saving because file name empty!";
126 else if (CacheFile == DoNotUseCacheFile)
127 GlobalLogger.info() << "DnsCache: configured not to use cache file";
132 // remember time of save
133 std::string cache_save_time_str = boost::posix_time::to_iso_string(
134 boost::posix_time::second_clock::universal_time() );
136 std::ofstream ofs( CacheFile.c_str() );
137 boost::archive::xml_oarchive oa(ofs);
138 oa & BOOST_SERIALIZATION_NVP(IpCache);
139 oa & BOOST_SERIALIZATION_NVP(CnameCache);
140 oa & BOOST_SERIALIZATION_NVP(cache_save_time_str);
141 GlobalLogger.info() << "DnsCache: saved to cache file "
146 catch (std::exception &exc)
148 GlobalLogger.warning() << "DnsCache: Saving failed: " << exc.what();
153 void DnsCache::load_from_cachefile()
155 if (CacheFile.empty())
157 << "DnsCache: cannot load because cache file name is empty!";
158 else if (CacheFile == DoNotUseCacheFile)
159 GlobalLogger.info() << "DnsCache: configured not to use cache file";
160 else if ( !I2n::file_exists(CacheFile) )
161 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
162 << CacheFile << " does not exist!";
167 std::ifstream ifs( CacheFile.c_str() );
168 boost::archive::xml_iarchive ia(ifs);
170 std::string cache_save_time_str;
172 ia & BOOST_SERIALIZATION_NVP(IpCache);
173 ia & BOOST_SERIALIZATION_NVP(CnameCache);
174 ia & BOOST_SERIALIZATION_NVP(cache_save_time_str);
176 boost::posix_time::ptime cache_save_time
177 = boost::posix_time::from_iso_string(cache_save_time_str);
178 GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
180 check_timestamps(cache_save_time);
182 catch (boost::archive::archive_exception &exc)
184 GlobalLogger.warning()
185 << "DnsCache: archive exception loading from " << CacheFile
186 << ": " << exc.what();
188 catch (std::exception &exc)
190 GlobalLogger.warning() << "DnsCache: exception while loading from "
191 << CacheFile << ": " << exc.what();
198 * @brief check that loaded cache really is from the past
200 * Added this to avoid trouble in case the system time is changed into the past.
201 * In that cast would have TTLs here in cache that are valid much too long.
203 * Therefore check SaveCacheTime and if that is in the future, set all TTLs to 0
205 * @returns true if had to re-set timestamps
207 bool DnsCache::check_timestamps(const boost::posix_time::ptime &cache_save_time)
209 // check if CacheSaveTime is in the future
210 boost::posix_time::ptime now
211 = boost::posix_time::second_clock::universal_time()
212 + boost::posix_time::hours( Config::CACHE_TIME_WARP_THRESH_HOURS );
213 if (now > cache_save_time)
214 { // Cache was saved in the past -- everything alright
218 GlobalLogger.warning() << "DnsCache: loaded cache from the future (saved "
219 << cache_save_time << ")! Resetting all TTLs to 0.";
221 // reset TTLs in IP cache
222 BOOST_FOREACH( const ip_map_type::value_type key_and_ip, IpCache )
224 HostAddressVec addr_reset;
225 BOOST_FOREACH( HostAddress address, key_and_ip.second )
226 addr_reset.push_back( HostAddress( address.get_ip(), 0 ) );
227 IpCache[key_and_ip.first] = addr_reset;
230 // reset TTLs in CNAME cache
231 BOOST_FOREACH( const cname_map_type::value_type key_and_cname, CnameCache )
232 CnameCache[key_and_cname.first] = Cname( key_and_cname.second.Host,
239 // -----------------------------------------------------------------------------
241 // -----------------------------------------------------------------------------
244 * warn if hostname is empty and remove trailing dot
245 * also warn if protocol is neither IPv4 nor IPv6
247 ip_map_key_type DnsCache::key_for_ips(const std::string &hostname,
248 const DnsIpProtocol &protocol) const
250 if (hostname.empty())
252 GlobalLogger.info() << "DnsCache: empty host!";
253 return ip_map_key_type("", DNS_IPALL);
255 if (protocol == DNS_IPALL)
257 GlobalLogger.info() << "DnsCache: neither IPv4 nor v6!";
258 return ip_map_key_type("", DNS_IPALL);
261 // check whether last character is a dot
262 if (hostname.rfind('.') == hostname.length()-1)
263 return ip_map_key_type( hostname.substr(0, hostname.length()-1),
266 return ip_map_key_type( hostname,
271 void DnsCache::update(const std::string &hostname,
272 const DnsIpProtocol &protocol,
273 const HostAddressVec &new_ips)
275 // check for valid input arguments
276 ip_map_key_type key = key_for_ips(hostname, protocol);
277 if ( key.first.empty() )
280 // ensure that there is never IP and CNAME for the same host
281 if ( !get_cname(hostname).Host.empty() )
283 GlobalLogger.info() << "DnsCache: Saving IPs for " << key.first
284 << " removes CNAME to " << get_cname(hostname).Host << "!";
285 update(hostname, Cname()); // overwrite with "empty" cname
288 // ensure min ttl of MinTimeBetweenResolves
289 HostAddressVec ips_checked;
290 BOOST_FOREACH( const HostAddress &addr, new_ips )
292 if ( addr.get_ttl().get_value() < MinTimeBetweenResolves )
294 GlobalLogger.info() << "DnsCache: Correcting TTL of IP for "
295 << key.first << " from " << addr.get_ttl().get_value() << "s to "
296 << MinTimeBetweenResolves << "s because was too short";
297 ips_checked.push_back( HostAddress( addr.get_ip(),
298 MinTimeBetweenResolves) );
301 ips_checked.push_back(addr);
304 // write IPs into one log line
305 stringstream log_temp;
306 log_temp << "DnsCache: update IPs for " << key.first << " to "
307 << ips_checked.size() << "-list: ";
308 BOOST_FOREACH( const HostAddress &ip, ips_checked )
309 log_temp << ip.get_ip() << ", ";
310 GlobalLogger.notice() << log_temp.str();
312 IpCache[key] = ips_checked;
318 * warn if hostname is empty and remove trailing dot
320 cname_map_key_type DnsCache::key_for_cname(const std::string &hostname) const
322 if (hostname.empty())
324 GlobalLogger.info() << "DnsCache: empty host!";
328 // check whether last character is a dot
329 if (hostname.rfind('.') == hostname.length()-1)
330 return hostname.substr(0, hostname.length()-1);
336 void DnsCache::update(const std::string &hostname,
339 // check for valid input arguments
340 cname_map_key_type key = key_for_cname(hostname);
344 // ensure that there is never IP and CNAME for the same host
345 int n_ips = get_ips(hostname, DNS_IPv4).size()
346 + get_ips(hostname, DNS_IPv6).size();
349 GlobalLogger.info() << "DnsCache: Saving IPs for " << key
350 << " removes CNAME to " << get_cname(hostname).Host << "!";
351 GlobalLogger.info() << "DnsCache: Saving CNAME for " << key
352 << " removes " << n_ips << " IPs for same host!";
353 update(hostname, DNS_IPv4, HostAddressVec());
354 update(hostname, DNS_IPv6, HostAddressVec());
357 // remove possible trailing dot from cname's target host
358 Cname to_save = Cname(key_for_cname(cname.Host), // implicit cast to string
361 // ensure min ttl of MinTimeBetweenResolves
362 if ( to_save.Ttl.get_value() < MinTimeBetweenResolves )
364 GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of "
365 << key << " from " << to_save.Ttl.get_value() << "s to "
366 << MinTimeBetweenResolves << "s because was too short";
367 to_save.Ttl = TimeToLive(MinTimeBetweenResolves);
370 GlobalLogger.notice() << "DnsCache: update CNAME for " << key
371 << " to " << to_save.Host;
372 CnameCache[key] = to_save;
377 // -----------------------------------------------------------------------------
379 // -----------------------------------------------------------------------------
382 * @returns empty list if no (up to date) ips for hostname in cache
384 HostAddressVec DnsCache::get_ips(const std::string &hostname,
385 const DnsIpProtocol &protocol,
386 const bool check_up_to_date)
388 ip_map_key_type key = key_for_ips(hostname, protocol);
389 HostAddressVec result = IpCache[key];
390 if (check_up_to_date)
392 HostAddressVec result_up_to_date;
393 uint32_t threshold = static_cast<uint32_t>(
394 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
395 uint32_t updated_ttl;
396 BOOST_FOREACH( const HostAddress &addr, result )
398 updated_ttl = addr.get_ttl().get_updated_value();
399 if (updated_ttl > threshold)
400 result_up_to_date.push_back(addr);
402 GlobalLogger.debug() << "DnsCache: do not return "
403 << addr.get_ip().to_string() << " since TTL "
404 << updated_ttl << "s is out of date (thresh="
405 << threshold << "s)";
407 result = result_up_to_date;
409 /*GlobalLogger.debug() << "DnsCache: request IPs for " << key.first
410 << " --> " << result.size() << "-list";
411 BOOST_FOREACH( const HostAddress &addr, result )
412 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
413 << " (TTL " << addr.get_ttl().get_updated_value()
419 * @returns empty cname if no (up to date cname) for hostname in cache
421 Cname DnsCache::get_cname(const std::string &hostname,
422 const bool check_up_to_date)
424 cname_map_key_type key = key_for_cname(hostname);
425 Cname result_obj = CnameCache[key];
426 /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key
427 << " --> \"" << result_obj.Host << "\" (TTL "
428 << result_obj.Ttl.get_updated_value() << "s)";*/
429 if (result_obj.Host.empty())
432 else if (check_up_to_date)
434 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
435 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
439 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
440 return Cname(); // same as if there was no cname for hostname
447 // underlying assumption in this function: for a hostname, the cache has either
448 // a list of IPs saved or a cname saved, but never both
449 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
450 const DnsIpProtocol &protocol,
451 const bool check_up_to_date)
453 std::string current_host = hostname;
455 HostAddressVec result = get_ips(current_host, protocol, check_up_to_date);
456 int n_recursions = 0;
457 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
458 int max_recursion_count = DnsMaster::get_instance()
459 ->get_max_recursion_count();
460 while ( result.empty() )
462 current_cname = get_cname(current_host, check_up_to_date);
463 if (current_cname.Host.empty())
464 break; // no ips (since result.empty()) and no cname
465 // --> will return empty result
467 current_host = current_cname.Host;
468 if (++n_recursions >= max_recursion_count)
470 GlobalLogger.info() << "DnsCache: reached recursion limit of "
471 << n_recursions << " in recursive IP retrieval of "
477 min_cname_ttl = min(min_cname_ttl,
478 current_cname.Ttl.get_updated_value());
479 result = get_ips(current_host, protocol, check_up_to_date);
483 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
484 << result.size() << "-list after " << n_recursions
487 // adjust ttl to min of ttl and min_cname_ttl
488 if (n_recursions > 0)
490 TimeToLive cname_ttl(min_cname_ttl);
492 BOOST_FOREACH( HostAddress &addr, result )
494 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
496 //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
497 addr.set_ttl(cname_ttl);
506 * from a list of CNAMEs find the first one that is out of date or empty
508 * returns the hostname that is out of date or empty if all CNAMEs are
511 * required in ResolverBase::get_skipper
513 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
514 const uint32_t ttl_thresh)
516 std::string first_outdated = hostname;
518 int n_recursions = 0;
519 int max_recursion_count = DnsMaster::get_instance()
520 ->get_max_recursion_count();
523 if (++n_recursions >= max_recursion_count)
525 GlobalLogger.info() << "DnsCache: reached recursion limit of "
526 << n_recursions << " in search of outdated CNAMEs for "
528 return first_outdated; // not really out of date but currently
531 cname = get_cname(first_outdated);
532 if (cname.Host.empty())
533 // reached end of cname list --> everything was up-to-date
535 else if (cname.Ttl.get_updated_value() > ttl_thresh)
536 // cname is up to date --> continue looking
537 first_outdated = cname.Host;
539 // cname is out of date --> return its target
542 // reach this point only if cname chain does not end with an IP
543 // --> all are up-to-date
547 std::string DnsCache::get_cname_chain_str(const std::string &hostname)
549 std::stringstream temp;
551 std::string current_host = hostname;
553 int n_recursions = 0;
554 int max_recursion_count = DnsMaster::get_instance()
555 ->get_max_recursion_count();
558 if (n_recursions >= max_recursion_count)
564 current_cname = get_cname(current_host, false);
565 if (current_cname.Host.empty())
569 current_host = current_cname.Host;
570 temp << "-->" << current_host;