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/date_time/posix_time/posix_time.hpp>
32 #include <boost/asio/placeholders.hpp>
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>
40 #include "dns/dnsmaster.h"
43 using boost::posix_time::seconds;
44 using I2n::Logger::GlobalLogger;
49 int SaveTimerSeconds = 60;
52 // -----------------------------------------------------------------------------
54 // -----------------------------------------------------------------------------
61 Cname::Cname(const std::string &host, const uint32_t ttl)
66 Cname::Cname(const std::string &host, const TimeToLive &ttl)
72 // -----------------------------------------------------------------------------
73 // DNS Cache constructor / destructor
74 // -----------------------------------------------------------------------------
76 const string DnsCache::DoNotUseCacheFile = "do not use cache file!";
78 DnsCache::DnsCache(const IoServiceItem &io_serv,
79 const std::string &cache_file,
80 const uint32_t min_time_between_resolves)
83 , SaveTimer( *io_serv )
84 , CacheFile( cache_file )
86 , MinTimeBetweenResolves( min_time_between_resolves )
88 // load cache from file
89 load_from_cachefile();
92 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
93 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
94 boost::asio::placeholders::error ) );
100 GlobalLogger.info() << "DnsCache: being destructed";
102 // save one last time without re-scheduling the next save
110 // -----------------------------------------------------------------------------
112 // -----------------------------------------------------------------------------
114 void DnsCache::schedule_save(const boost::system::error_code &error)
116 // just in case: ensure SaveTimer is cancelled
117 SaveTimer.cancel(); // (will do nothing if already expired/cancelled)
119 if ( error == boost::asio::error::operation_aborted ) // cancelled
121 GlobalLogger.info() << "DnsCache: SaveTimer was cancelled "
122 << "--> no save and no re-schedule of saving!";
127 GlobalLogger.info() << "DnsCache: Received error " << error
128 << " in schedule_save "
129 << "--> no save now but re-schedule saving";
134 // schedule next save
135 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
136 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
137 boost::asio::placeholders::error ) );
140 void DnsCache::save_to_cachefile()
143 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
144 else if (CacheFile.empty())
146 << "DnsCache: skip saving because file name empty!";
147 else if (CacheFile == DoNotUseCacheFile)
148 GlobalLogger.info() << "DnsCache: configured not to use cache file";
153 std::ofstream ofs( CacheFile.c_str() );
154 boost::archive::xml_oarchive oa(ofs);
155 //oa << boost::serialization::make_nvp("IpCache", IpCache);
156 //oa << boost::serialization::make_nvp("CnameCache", CnameCache);
157 oa & BOOST_SERIALIZATION_NVP(IpCache);
158 oa & BOOST_SERIALIZATION_NVP(CnameCache);
159 GlobalLogger.info() << "DnsCache: saved to cache file "
164 catch (std::exception &exc)
166 GlobalLogger.warning() << "DnsCache: Saving failed: " << exc.what();
171 void DnsCache::load_from_cachefile()
173 if (CacheFile.empty())
175 << "DnsCache: cannot load because cache file name is empty!";
176 else if (CacheFile == DoNotUseCacheFile)
177 GlobalLogger.info() << "DnsCache: configured not to use cache file";
178 else if ( !I2n::file_exists(CacheFile) )
179 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
180 << CacheFile << " does not exist!";
185 std::ifstream ifs( CacheFile.c_str() );
186 boost::archive::xml_iarchive ia(ifs);
188 ia & BOOST_SERIALIZATION_NVP(IpCache);
189 ia & BOOST_SERIALIZATION_NVP(CnameCache);
190 GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
192 catch (boost::archive::archive_exception &exc)
194 GlobalLogger.warning()
195 << "DnsCache: archive exception loading from " << CacheFile
196 << ": " << exc.what();
198 catch (std::exception &exc)
200 GlobalLogger.warning() << "DnsCache: exception while loading from "
201 << CacheFile << ": " << exc.what();
207 // -----------------------------------------------------------------------------
209 // -----------------------------------------------------------------------------
212 * warn if hostname is empty and remove trailing dot
213 * also warn if protocol is neither IPv4 nor IPv6
215 ip_map_key_type DnsCache::key_for_ips(const std::string &hostname,
216 const DnsIpProtocol &protocol) const
218 if (hostname.empty())
220 GlobalLogger.info() << "DnsCache: empty host!";
221 return ip_map_key_type("", DNS_IPALL);
223 if (protocol == DNS_IPALL)
225 GlobalLogger.info() << "DnsCache: neither IPv4 nor v6!";
226 return ip_map_key_type("", DNS_IPALL);
229 // check whether last character is a dot
230 if (hostname.rfind('.') == hostname.length()-1)
231 return ip_map_key_type( hostname.substr(0, hostname.length()-1),
234 return ip_map_key_type( hostname,
239 void DnsCache::update(const std::string &hostname,
240 const DnsIpProtocol &protocol,
241 const HostAddressVec &new_ips)
243 // check for valid input arguments
244 ip_map_key_type key = key_for_ips(hostname, protocol);
245 if ( key.first.empty() )
248 // ensure that there is never IP and CNAME for the same host
249 if ( !get_cname(hostname).Host.empty() )
251 GlobalLogger.info() << "DnsCache: Saving IPs for " << key.first
252 << " removes CNAME to " << get_cname(hostname).Host << "!";
253 update(hostname, Cname()); // overwrite with "empty" cname
256 // ensure min ttl of MinTimeBetweenResolves
257 HostAddressVec ips_checked;
258 BOOST_FOREACH( const HostAddress &addr, new_ips )
260 if ( addr.get_ttl().get_value() < MinTimeBetweenResolves )
262 GlobalLogger.info() << "DnsCache: Correcting TTL of IP for "
263 << key.first << " from " << addr.get_ttl().get_value() << "s to "
264 << MinTimeBetweenResolves << "s because was too short";
265 ips_checked.push_back( HostAddress( addr.get_ip(),
266 MinTimeBetweenResolves) );
269 ips_checked.push_back(addr);
272 // write IPs into one log line
273 stringstream log_temp;
274 log_temp << "DnsCache: update IPs for " << key.first << " to "
275 << ips_checked.size() << "-list: ";
276 BOOST_FOREACH( const HostAddress &ip, ips_checked )
277 log_temp << ip.get_ip() << ", ";
278 GlobalLogger.notice() << log_temp.str();
280 IpCache[key] = ips_checked;
286 * warn if hostname is empty and remove trailing dot
288 cname_map_key_type DnsCache::key_for_cname(const std::string &hostname) const
290 if (hostname.empty())
292 GlobalLogger.info() << "DnsCache: empty host!";
296 // check whether last character is a dot
297 if (hostname.rfind('.') == hostname.length()-1)
298 return hostname.substr(0, hostname.length()-1);
304 void DnsCache::update(const std::string &hostname,
307 // check for valid input arguments
308 cname_map_key_type key = key_for_cname(hostname);
312 // ensure that there is never IP and CNAME for the same host
313 int n_ips = get_ips(hostname, DNS_IPv4).size()
314 + get_ips(hostname, DNS_IPv6).size();
317 GlobalLogger.info() << "DnsCache: Saving IPs for " << key
318 << " removes CNAME to " << get_cname(hostname).Host << "!";
319 GlobalLogger.info() << "DnsCache: Saving CNAME for " << key
320 << " removes " << n_ips << " IPs for same host!";
321 update(hostname, DNS_IPv4, HostAddressVec());
322 update(hostname, DNS_IPv6, HostAddressVec());
325 // remove possible trailing dot from cname's target host
326 Cname to_save = Cname(key_for_cname(cname.Host), // implicit cast to string
329 // ensure min ttl of MinTimeBetweenResolves
330 if ( to_save.Ttl.get_value() < MinTimeBetweenResolves )
332 GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of "
333 << key << " from " << to_save.Ttl.get_value() << "s to "
334 << MinTimeBetweenResolves << "s because was too short";
335 to_save.Ttl = TimeToLive(MinTimeBetweenResolves);
338 GlobalLogger.notice() << "DnsCache: update CNAME for " << key
339 << " to " << to_save.Host;
340 CnameCache[key] = to_save;
345 // -----------------------------------------------------------------------------
347 // -----------------------------------------------------------------------------
350 * @returns empty list if no (up to date) ips for hostname in cache
352 HostAddressVec DnsCache::get_ips(const std::string &hostname,
353 const DnsIpProtocol &protocol,
354 const bool check_up_to_date)
356 ip_map_key_type key = key_for_ips(hostname, protocol);
357 HostAddressVec result = IpCache[key];
358 if (check_up_to_date)
360 HostAddressVec result_up_to_date;
361 uint32_t threshold = static_cast<uint32_t>(
362 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
363 uint32_t updated_ttl;
364 BOOST_FOREACH( const HostAddress &addr, result )
366 updated_ttl = addr.get_ttl().get_updated_value();
367 if (updated_ttl > threshold)
368 result_up_to_date.push_back(addr);
370 GlobalLogger.debug() << "DnsCache: do not return "
371 << addr.get_ip().to_string() << " since TTL "
372 << updated_ttl << "s is out of date (thresh="
373 << threshold << "s)";
375 result = result_up_to_date;
377 /*GlobalLogger.debug() << "DnsCache: request IPs for " << key.first
378 << " --> " << result.size() << "-list";
379 BOOST_FOREACH( const HostAddress &addr, result )
380 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
381 << " (TTL " << addr.get_ttl().get_updated_value()
387 * @returns empty cname if no (up to date cname) for hostname in cache
389 Cname DnsCache::get_cname(const std::string &hostname,
390 const bool check_up_to_date)
392 cname_map_key_type key = key_for_cname(hostname);
393 Cname result_obj = CnameCache[key];
394 /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key
395 << " --> \"" << result_obj.Host << "\" (TTL "
396 << result_obj.Ttl.get_updated_value() << "s)";*/
397 if (result_obj.Host.empty())
400 else if (check_up_to_date)
402 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
403 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
407 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
408 return Cname(); // same as if there was no cname for hostname
415 // underlying assumption in this function: for a hostname, the cache has either
416 // a list of IPs saved or a cname saved, but never both
417 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
418 const DnsIpProtocol &protocol,
419 const bool check_up_to_date)
421 std::string current_host = hostname;
423 HostAddressVec result = get_ips(current_host, protocol, check_up_to_date);
424 int n_recursions = 0;
425 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
426 int max_recursion_count = DnsMaster::get_instance()
427 ->get_max_recursion_count();
428 while ( result.empty() )
430 current_cname = get_cname(current_host, check_up_to_date);
431 if (current_cname.Host.empty())
432 break; // no ips (since result.empty()) and no cname
433 // --> will return empty result
435 current_host = current_cname.Host;
436 if (++n_recursions >= max_recursion_count)
438 GlobalLogger.info() << "DnsCache: reached recursion limit of "
439 << n_recursions << " in recursive IP retrieval of "
445 min_cname_ttl = min(min_cname_ttl,
446 current_cname.Ttl.get_updated_value());
447 result = get_ips(current_host, protocol, check_up_to_date);
451 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
452 << result.size() << "-list after " << n_recursions
455 // adjust ttl to min of ttl and min_cname_ttl
456 if (n_recursions > 0)
458 TimeToLive cname_ttl(min_cname_ttl);
460 BOOST_FOREACH( HostAddress &addr, result )
462 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
464 //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
465 addr.set_ttl(cname_ttl);
474 * from a list of CNAMEs find the first one that is out of date or empty
476 * returns the hostname that is out of date or empty if all CNAMEs are
479 * required in ResolverBase::get_skipper
481 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
482 const uint32_t ttl_thresh)
484 std::string first_outdated = hostname;
486 int n_recursions = 0;
487 int max_recursion_count = DnsMaster::get_instance()
488 ->get_max_recursion_count();
491 if (++n_recursions >= max_recursion_count)
493 GlobalLogger.info() << "DnsCache: reached recursion limit of "
494 << n_recursions << " in search of outdated CNAMEs for "
496 return first_outdated; // not really out of date but currently
499 cname = get_cname(first_outdated);
500 if (cname.Host.empty())
501 // reached end of cname list --> everything was up-to-date
503 else if (cname.Ttl.get_updated_value() > ttl_thresh)
504 // cname is up to date --> continue looking
505 first_outdated = cname.Host;
507 // cname is out of date --> return its target
510 // reach this point only if cname chain does not end with an IP
511 // --> all are up-to-date
515 std::string DnsCache::get_cname_chain_str(const std::string &hostname)
517 std::stringstream temp;
519 std::string current_host = hostname;
521 int n_recursions = 0;
522 int max_recursion_count = DnsMaster::get_instance()
523 ->get_max_recursion_count();
526 if (n_recursions >= max_recursion_count)
532 current_cname = get_cname(current_host, false);
533 if (current_cname.Host.empty())
537 current_host = current_cname.Host;
538 temp << "-->" << current_host;