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"
26 #include <logfunc.hpp>
27 #include <filefunc.hxx> // I2n::file_exists
28 #include <boost/foreach.hpp>
29 #include <boost/bind.hpp>
30 #include <boost/date_time/posix_time/posix_time.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 int SaveTimerSeconds = 60;
51 // -----------------------------------------------------------------------------
53 // -----------------------------------------------------------------------------
60 Cname::Cname(const std::string &host, const uint32_t ttl)
65 Cname::Cname(const std::string &host, const TimeToLive &ttl)
71 // -----------------------------------------------------------------------------
72 // DNS Cache constructor / destructor
73 // -----------------------------------------------------------------------------
75 const string DnsCache::DoNotUseCacheFile = "do not use cache file!";
77 DnsCache::DnsCache(const IoServiceItem &io_serv,
78 const std::string &cache_file,
79 const uint32_t min_time_between_resolves)
82 , SaveTimer( *io_serv )
83 , CacheFile( cache_file )
85 , MinTimeBetweenResolves( min_time_between_resolves )
87 // load cache from file
88 load_from_cachefile();
91 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
92 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
93 boost::asio::placeholders::error ) );
99 GlobalLogger.info() << "DnsCache: being destructed";
101 // save one last time without re-scheduling the next save
109 // -----------------------------------------------------------------------------
111 // -----------------------------------------------------------------------------
113 void DnsCache::schedule_save(const boost::system::error_code &error)
115 // just in case: ensure SaveTimer is cancelled
116 SaveTimer.cancel(); // (will do nothing if already expired/cancelled)
118 if ( error == boost::asio::error::operation_aborted ) // cancelled
120 GlobalLogger.info() << "DnsCache: SaveTimer was cancelled "
121 << "--> no save and no re-schedule of saving!";
126 GlobalLogger.info() << "DnsCache: Received error " << error
127 << " in schedule_save "
128 << "--> no save now but re-schedule saving";
133 // schedule next save
134 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
135 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
136 boost::asio::placeholders::error ) );
139 void DnsCache::save_to_cachefile()
142 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
143 else if (CacheFile.empty())
145 << "DnsCache: skip saving because file name empty!";
146 else if (CacheFile == DoNotUseCacheFile)
147 GlobalLogger.info() << "DnsCache: configured not to use cache file";
152 std::ofstream ofs( CacheFile.c_str() );
153 boost::archive::xml_oarchive oa(ofs);
154 //oa << boost::serialization::make_nvp("IpCache", IpCache);
155 //oa << boost::serialization::make_nvp("CnameCache", CnameCache);
156 oa & BOOST_SERIALIZATION_NVP(IpCache);
157 oa & BOOST_SERIALIZATION_NVP(CnameCache);
158 GlobalLogger.info() << "DnsCache: saved to cache file "
163 catch (std::exception &exc)
165 GlobalLogger.warning() << "DnsCache: Saving failed: " << exc.what();
170 void DnsCache::load_from_cachefile()
172 if (CacheFile.empty())
174 << "DnsCache: cannot load because cache file name is empty!";
175 else if (CacheFile == DoNotUseCacheFile)
176 GlobalLogger.info() << "DnsCache: configured not to use cache file";
177 else if ( !I2n::file_exists(CacheFile) )
178 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
179 << CacheFile << " does not exist!";
184 std::ifstream ifs( CacheFile.c_str() );
185 boost::archive::xml_iarchive ia(ifs);
187 ia & BOOST_SERIALIZATION_NVP(IpCache);
188 ia & BOOST_SERIALIZATION_NVP(CnameCache);
189 GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
191 catch (boost::archive::archive_exception &exc)
193 GlobalLogger.warning()
194 << "DnsCache: archive exception loading from " << CacheFile
195 << ": " << exc.what();
197 catch (std::exception &exc)
199 GlobalLogger.warning() << "DnsCache: exception while loading from "
200 << CacheFile << ": " << exc.what();
206 // -----------------------------------------------------------------------------
208 // -----------------------------------------------------------------------------
210 // warn if hostname is empty and remove trailing dot
211 std::string DnsCache::key_for_hostname(const std::string &hostname) const
213 if (hostname.empty())
215 GlobalLogger.info() << "DnsCache: empty host!";
219 // check whether last character is a dot
220 if (hostname.rfind('.') == hostname.length()-1)
221 return hostname.substr(0, hostname.length()-1);
227 void DnsCache::update(const std::string &hostname,
228 const HostAddressVec &new_ips)
230 std::string key = key_for_hostname(hostname);
231 if ( !get_cname(hostname).Host.empty() )
232 { // ensure that there is never IP and CNAME for the same host
233 GlobalLogger.info() << "DnsCache: Saving IPs for " << key
234 << " removes CNAME to " << get_cname(hostname).Host << "!";
235 update(hostname, Cname()); // overwrite with "empty" cname
237 // ensure min ttl of MinTimeBetweenResolves
238 HostAddressVec ips_checked;
239 BOOST_FOREACH( const HostAddress &addr, new_ips )
241 if ( addr.get_ttl().get_value() < MinTimeBetweenResolves )
243 GlobalLogger.info() << "DnsCache: Correcting TTL of IP for "
244 << hostname << " from " << addr.get_ttl().get_value() << "s to "
245 << MinTimeBetweenResolves << "s because was too short";
246 ips_checked.push_back( HostAddress( addr.get_ip(),
247 MinTimeBetweenResolves) );
250 ips_checked.push_back(addr);
253 GlobalLogger.info() << "DnsCache: update IPs for " << key
254 << " to " << ips_checked.size() << "-list";
256 IpCache[key] = ips_checked;
261 void DnsCache::update(const std::string &hostname,
264 std::string key = key_for_hostname(hostname);
265 if ( !get_ips(hostname).empty() )
266 { // ensure that there is never IP and CNAME for the same host
267 GlobalLogger.info() << "DnsCache: Saving CNAME for " << key
268 << " removes " << get_ips(hostname).size() << " IPs for same host!";
269 update(hostname, HostAddressVec()); // overwrite with empty IP list
272 // remove possible trailing dot from cname
273 Cname to_save = Cname(key_for_hostname(cname.Host),
276 // ensure min ttl of MinTimeBetweenResolves
277 if ( to_save.Ttl.get_value() < MinTimeBetweenResolves )
279 GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of "
280 << hostname << " from " << to_save.Ttl.get_value() << "s to "
281 << MinTimeBetweenResolves << "s because was too short";
282 to_save.Ttl = TimeToLive(MinTimeBetweenResolves);
285 GlobalLogger.info() << "DnsCache: update CNAME for " << key
286 << " to " << to_save.Host;
287 CnameCache[key] = to_save;
292 // -----------------------------------------------------------------------------
294 // -----------------------------------------------------------------------------
297 * @returns empty list if no (up to date) ips for hostname in cache
299 HostAddressVec DnsCache::get_ips(const std::string &hostname,
300 const bool check_up_to_date)
302 std::string key = key_for_hostname(hostname);
303 HostAddressVec result = IpCache[key];
304 if (check_up_to_date)
306 HostAddressVec result_up_to_date;
307 uint32_t threshold = static_cast<uint32_t>(
308 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
309 uint32_t updated_ttl;
310 BOOST_FOREACH( const HostAddress &addr, result )
312 updated_ttl = addr.get_ttl().get_updated_value();
313 if (updated_ttl > threshold)
314 result_up_to_date.push_back(addr);
316 GlobalLogger.debug() << "DnsCache: do not return "
317 << addr.get_ip().to_string() << " since TTL "
318 << updated_ttl << "s is out of date (thresh="
319 << threshold << "s)";
321 result = result_up_to_date;
323 /*GlobalLogger.debug() << "DnsCache: request IPs for " << key
324 << " --> " << result.size() << "-list";
325 BOOST_FOREACH( const HostAddress &addr, result )
326 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
327 << " (TTL " << addr.get_ttl().get_updated_value()
333 * @returns empty cname if no (up to date cname) for hostname in cache
335 Cname DnsCache::get_cname(const std::string &hostname,
336 const bool check_up_to_date)
338 std::string key = key_for_hostname(hostname);
339 Cname result_obj = CnameCache[key];
340 /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key
341 << " --> \"" << result_obj.Host << "\" (TTL "
342 << result_obj.Ttl.get_updated_value() << "s)";*/
343 if (result_obj.Host.empty())
346 else if (check_up_to_date)
348 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
349 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
353 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
354 return Cname(); // same as if there was no cname for hostname
361 // underlying assumption in this function: for a hostname, the cache has either
362 // a list of IPs saved or a cname saved, but never both
363 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
364 const bool check_up_to_date)
366 std::string current_host = hostname;
368 HostAddressVec result = get_ips(current_host, check_up_to_date);
369 int n_recursions = 0;
370 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
371 int max_recursion_count = DnsMaster::get_instance()
372 ->get_max_recursion_count();
373 while ( result.empty() )
375 current_cname = get_cname(current_host, check_up_to_date);
376 if (current_cname.Host.empty())
379 current_host = key_for_hostname(current_cname.Host);
380 if (++n_recursions >= max_recursion_count)
382 GlobalLogger.info() << "DnsCache: reached recursion limit of "
383 << n_recursions << " in recursive IP retrieval of "
389 min_cname_ttl = min(min_cname_ttl,
390 current_cname.Ttl.get_updated_value());
391 result = get_ips(current_host, check_up_to_date);
395 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
396 << result.size() << "-list after " << n_recursions
399 // adjust ttl to min of ttl and min_cname_ttl
400 if (n_recursions > 0)
402 TimeToLive cname_ttl(min_cname_ttl);
404 BOOST_FOREACH( HostAddress &addr, result )
406 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
408 //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
409 addr.set_ttl(cname_ttl);
418 * from a list of CNAMEs find the first one that is out of date or empty
420 * returns the hostname that is out of date or empty if all CNAMEs are
423 * required in ResolverBase::get_skipper
425 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
426 const uint32_t ttl_thresh)
428 std::string first_outdated = hostname;
430 int n_recursions = 0;
431 int max_recursion_count = DnsMaster::get_instance()
432 ->get_max_recursion_count();
435 if (++n_recursions >= max_recursion_count)
437 GlobalLogger.info() << "DnsCache: reached recursion limit of "
438 << n_recursions << " in search of outdated CNAMEs for "
440 return first_outdated; // not really out of date but currently
443 cname = get_cname(first_outdated);
444 if (cname.Host.empty())
445 // reached end of cname list --> everything was up-to-date
447 else if (cname.Ttl.get_updated_value() > ttl_thresh)
448 // cname is up to date --> continue looking
449 first_outdated = cname.Host;
451 // cname is out of date --> return its target
454 // reach this point only if cname chain does not end with an IP
455 // --> all are up-to-date