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 if ( error == boost::asio::error::operation_aborted ) // cancelled
118 GlobalLogger.info() << "DnsCache: SaveTimer was cancelled "
119 << "--> no save and no re-schedule of saving!";
124 GlobalLogger.info() << "DnsCache: Received error " << error
125 << " in schedule_save "
126 << "--> no save now but re-schedule saving";
131 // schedule next save
132 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
133 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
134 boost::asio::placeholders::error ) );
137 void DnsCache::save_to_cachefile()
140 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
141 else if (CacheFile.empty())
143 << "DnsCache: skip saving because file name empty!";
144 else if (CacheFile == DoNotUseCacheFile)
145 GlobalLogger.info() << "DnsCache: configured not to use cache file";
150 std::ofstream ofs( CacheFile.c_str() );
151 boost::archive::xml_oarchive oa(ofs);
152 //oa << boost::serialization::make_nvp("IpCache", IpCache);
153 //oa << boost::serialization::make_nvp("CnameCache", CnameCache);
154 oa & BOOST_SERIALIZATION_NVP(IpCache);
155 oa & BOOST_SERIALIZATION_NVP(CnameCache);
156 GlobalLogger.info() << "DnsCache: saved to cache file "
161 catch (std::exception &exc)
163 GlobalLogger.warning() << "DnsCache: Saving failed: " << exc.what();
168 void DnsCache::load_from_cachefile()
170 if (CacheFile.empty())
172 << "DnsCache: cannot load because cache file name is empty!";
173 else if (CacheFile == DoNotUseCacheFile)
174 GlobalLogger.info() << "DnsCache: configured not to use cache file";
175 else if ( !I2n::file_exists(CacheFile) )
176 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
177 << CacheFile << " does not exist!";
182 std::ifstream ifs( CacheFile.c_str() );
183 boost::archive::xml_iarchive ia(ifs);
185 ia & BOOST_SERIALIZATION_NVP(IpCache);
186 ia & BOOST_SERIALIZATION_NVP(CnameCache);
187 GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
189 catch (boost::archive::archive_exception &exc)
191 GlobalLogger.warning()
192 << "DnsCache: archive exception loading from " << CacheFile
193 << ": " << exc.what();
195 catch (std::exception &exc)
197 GlobalLogger.warning() << "DnsCache: exception while loading from "
198 << CacheFile << ": " << exc.what();
204 // -----------------------------------------------------------------------------
206 // -----------------------------------------------------------------------------
209 * warn if hostname is empty and remove trailing dot
210 * also warn if protocol is neither IPv4 nor IPv6
212 ip_map_key_type DnsCache::key_for_ips(const std::string &hostname,
213 const DnsIpProtocol &protocol) const
215 if (hostname.empty())
217 GlobalLogger.info() << "DnsCache: empty host!";
218 return ip_map_key_type("", DNS_IPALL);
220 if (protocol == DNS_IPALL)
222 GlobalLogger.info() << "DnsCache: neither IPv4 nor v6!";
223 return ip_map_key_type("", DNS_IPALL);
226 // check whether last character is a dot
227 if (hostname.rfind('.') == hostname.length()-1)
228 return ip_map_key_type( hostname.substr(0, hostname.length()-1),
231 return ip_map_key_type( hostname,
236 void DnsCache::update(const std::string &hostname,
237 const DnsIpProtocol &protocol,
238 const HostAddressVec &new_ips)
240 // check for valid input arguments
241 ip_map_key_type key = key_for_ips(hostname, protocol);
242 if ( key.first.empty() )
245 // ensure that there is never IP and CNAME for the same host
246 if ( !get_cname(hostname).Host.empty() )
248 GlobalLogger.info() << "DnsCache: Saving IPs for " << key.first
249 << " removes CNAME to " << get_cname(hostname).Host << "!";
250 update(hostname, Cname()); // overwrite with "empty" cname
253 // ensure min ttl of MinTimeBetweenResolves
254 HostAddressVec ips_checked;
255 BOOST_FOREACH( const HostAddress &addr, new_ips )
257 if ( addr.get_ttl().get_value() < MinTimeBetweenResolves )
259 GlobalLogger.info() << "DnsCache: Correcting TTL of IP for "
260 << key.first << " from " << addr.get_ttl().get_value() << "s to "
261 << MinTimeBetweenResolves << "s because was too short";
262 ips_checked.push_back( HostAddress( addr.get_ip(),
263 MinTimeBetweenResolves) );
266 ips_checked.push_back(addr);
269 // write IPs into one log line
270 stringstream log_temp;
271 log_temp << "DnsCache: update IPs for " << key.first << " to "
272 << ips_checked.size() << "-list: ";
273 BOOST_FOREACH( const HostAddress &ip, ips_checked )
274 log_temp << ip.get_ip() << ", ";
275 GlobalLogger.notice() << log_temp.str();
277 IpCache[key] = ips_checked;
283 * warn if hostname is empty and remove trailing dot
285 cname_map_key_type DnsCache::key_for_cname(const std::string &hostname) const
287 if (hostname.empty())
289 GlobalLogger.info() << "DnsCache: empty host!";
293 // check whether last character is a dot
294 if (hostname.rfind('.') == hostname.length()-1)
295 return hostname.substr(0, hostname.length()-1);
301 void DnsCache::update(const std::string &hostname,
304 // check for valid input arguments
305 cname_map_key_type key = key_for_cname(hostname);
309 // ensure that there is never IP and CNAME for the same host
310 int n_ips = get_ips(hostname, DNS_IPv4).size()
311 + get_ips(hostname, DNS_IPv6).size();
314 GlobalLogger.info() << "DnsCache: Saving IPs for " << key
315 << " removes CNAME to " << get_cname(hostname).Host << "!";
316 GlobalLogger.info() << "DnsCache: Saving CNAME for " << key
317 << " removes " << n_ips << " IPs for same host!";
318 update(hostname, DNS_IPv4, HostAddressVec());
319 update(hostname, DNS_IPv6, HostAddressVec());
322 // remove possible trailing dot from cname's target host
323 Cname to_save = Cname(key_for_cname(cname.Host), // implicit cast to string
326 // ensure min ttl of MinTimeBetweenResolves
327 if ( to_save.Ttl.get_value() < MinTimeBetweenResolves )
329 GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of "
330 << key << " from " << to_save.Ttl.get_value() << "s to "
331 << MinTimeBetweenResolves << "s because was too short";
332 to_save.Ttl = TimeToLive(MinTimeBetweenResolves);
335 GlobalLogger.notice() << "DnsCache: update CNAME for " << key
336 << " to " << to_save.Host;
337 CnameCache[key] = to_save;
342 // -----------------------------------------------------------------------------
344 // -----------------------------------------------------------------------------
347 * @returns empty list if no (up to date) ips for hostname in cache
349 HostAddressVec DnsCache::get_ips(const std::string &hostname,
350 const DnsIpProtocol &protocol,
351 const bool check_up_to_date)
353 ip_map_key_type key = key_for_ips(hostname, protocol);
354 HostAddressVec result = IpCache[key];
355 if (check_up_to_date)
357 HostAddressVec result_up_to_date;
358 uint32_t threshold = static_cast<uint32_t>(
359 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
360 uint32_t updated_ttl;
361 BOOST_FOREACH( const HostAddress &addr, result )
363 updated_ttl = addr.get_ttl().get_updated_value();
364 if (updated_ttl > threshold)
365 result_up_to_date.push_back(addr);
367 GlobalLogger.debug() << "DnsCache: do not return "
368 << addr.get_ip().to_string() << " since TTL "
369 << updated_ttl << "s is out of date (thresh="
370 << threshold << "s)";
372 result = result_up_to_date;
374 /*GlobalLogger.debug() << "DnsCache: request IPs for " << key.first
375 << " --> " << result.size() << "-list";
376 BOOST_FOREACH( const HostAddress &addr, result )
377 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
378 << " (TTL " << addr.get_ttl().get_updated_value()
384 * @returns empty cname if no (up to date cname) for hostname in cache
386 Cname DnsCache::get_cname(const std::string &hostname,
387 const bool check_up_to_date)
389 cname_map_key_type key = key_for_cname(hostname);
390 Cname result_obj = CnameCache[key];
391 /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key
392 << " --> \"" << result_obj.Host << "\" (TTL "
393 << result_obj.Ttl.get_updated_value() << "s)";*/
394 if (result_obj.Host.empty())
397 else if (check_up_to_date)
399 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
400 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
404 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
405 return Cname(); // same as if there was no cname for hostname
412 // underlying assumption in this function: for a hostname, the cache has either
413 // a list of IPs saved or a cname saved, but never both
414 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
415 const DnsIpProtocol &protocol,
416 const bool check_up_to_date)
418 std::string current_host = hostname;
420 HostAddressVec result = get_ips(current_host, protocol, check_up_to_date);
421 int n_recursions = 0;
422 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
423 int max_recursion_count = DnsMaster::get_instance()
424 ->get_max_recursion_count();
425 while ( result.empty() )
427 current_cname = get_cname(current_host, check_up_to_date);
428 if (current_cname.Host.empty())
429 break; // no ips (since result.empty()) and no cname
430 // --> will return empty result
432 current_host = current_cname.Host;
433 if (++n_recursions >= max_recursion_count)
435 GlobalLogger.info() << "DnsCache: reached recursion limit of "
436 << n_recursions << " in recursive IP retrieval of "
442 min_cname_ttl = min(min_cname_ttl,
443 current_cname.Ttl.get_updated_value());
444 result = get_ips(current_host, protocol, check_up_to_date);
448 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
449 << result.size() << "-list after " << n_recursions
452 // adjust ttl to min of ttl and min_cname_ttl
453 if (n_recursions > 0)
455 TimeToLive cname_ttl(min_cname_ttl);
457 BOOST_FOREACH( HostAddress &addr, result )
459 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
461 //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
462 addr.set_ttl(cname_ttl);
471 * from a list of CNAMEs find the first one that is out of date or empty
473 * returns the hostname that is out of date or empty if all CNAMEs are
476 * required in ResolverBase::get_skipper
478 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
479 const uint32_t ttl_thresh)
481 std::string first_outdated = hostname;
483 int n_recursions = 0;
484 int max_recursion_count = DnsMaster::get_instance()
485 ->get_max_recursion_count();
488 if (++n_recursions >= max_recursion_count)
490 GlobalLogger.info() << "DnsCache: reached recursion limit of "
491 << n_recursions << " in search of outdated CNAMEs for "
493 return first_outdated; // not really out of date but currently
496 cname = get_cname(first_outdated);
497 if (cname.Host.empty())
498 // reached end of cname list --> everything was up-to-date
500 else if (cname.Ttl.get_updated_value() > ttl_thresh)
501 // cname is up to date --> continue looking
502 first_outdated = cname.Host;
504 // cname is out of date --> return its target
507 // reach this point only if cname chain does not end with an IP
508 // --> all are up-to-date
512 std::string DnsCache::get_cname_chain_str(const std::string &hostname)
514 std::stringstream temp;
516 std::string current_host = 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)
529 current_cname = get_cname(current_host, false);
530 if (current_cname.Host.empty())
534 current_host = current_cname.Host;
535 temp << "-->" << current_host;