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;
49 int MaxRetrievalRecursions = 10;
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.error() << "DnsCache: SaveTimer was cancelled "
122 << "--> no save and no re-schedule of saving!";
127 GlobalLogger.error() << "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())
145 GlobalLogger.warning()
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())
174 GlobalLogger.warning()
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 // -----------------------------------------------------------------------------
211 // warn if hostname is empty and remove trailing dot
212 std::string DnsCache::key_for_hostname(const std::string &hostname) const
214 if (hostname.empty())
216 GlobalLogger.warning() << "DnsCache: empty host!";
220 // check whether last character is a dot
221 if (hostname.rfind('.') == hostname.length()-1)
222 return hostname.substr(0, hostname.length()-1);
228 void DnsCache::update(const std::string &hostname,
229 const HostAddressVec &new_ips)
231 std::string key = key_for_hostname(hostname);
232 if ( !get_cname(hostname).Host.empty() )
233 { // ensure that there is never IP and CNAME for the same host
234 GlobalLogger.warning() << "DnsCache: Saving IPs for " << key
235 << " removes CNAME to " << get_cname(hostname).Host << "!";
236 update(hostname, Cname()); // overwrite with "empty" cname
238 // ensure min ttl of MinTimeBetweenResolves
239 HostAddressVec ips_checked;
240 BOOST_FOREACH( const HostAddress &addr, new_ips )
242 if ( addr.get_ttl().get_value() < MinTimeBetweenResolves )
244 GlobalLogger.info() << "DnsCache: Correcting TTL of IP for "
245 << hostname << " from " << addr.get_ttl().get_value() << "s to "
246 << MinTimeBetweenResolves << "s because was too short";
247 ips_checked.push_back( HostAddress( addr.get_ip(),
248 MinTimeBetweenResolves) );
251 ips_checked.push_back(addr);
254 GlobalLogger.info() << "DnsCache: update IPs for " << key
255 << " to " << ips_checked.size() << "-list";
257 IpCache[key] = ips_checked;
262 void DnsCache::update(const std::string &hostname,
265 std::string key = key_for_hostname(hostname);
266 if ( !get_ips(hostname).empty() )
267 { // ensure that there is never IP and CNAME for the same host
268 GlobalLogger.warning() << "DnsCache: Saving CNAME for " << key
269 << " removes " << get_ips(hostname).size() << " IPs for same host!";
270 update(hostname, HostAddressVec()); // overwrite with empty IP list
273 // remove possible trailing dot from cname
274 Cname to_save = Cname(key_for_hostname(cname.Host),
277 // ensure min ttl of MinTimeBetweenResolves
278 if ( to_save.Ttl.get_value() < MinTimeBetweenResolves )
280 GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of "
281 << hostname << " from " << to_save.Ttl.get_value() << "s to "
282 << MinTimeBetweenResolves << "s because was too short";
283 to_save.Ttl = TimeToLive(MinTimeBetweenResolves);
286 GlobalLogger.info() << "DnsCache: update CNAME for " << key
287 << " to " << to_save.Host;
288 CnameCache[key] = to_save;
293 // -----------------------------------------------------------------------------
295 // -----------------------------------------------------------------------------
298 * @returns empty list if no (up to date) ips for hostname in cache
300 HostAddressVec DnsCache::get_ips(const std::string &hostname,
301 const bool check_up_to_date)
303 std::string key = key_for_hostname(hostname);
304 HostAddressVec result = IpCache[key];
305 if (check_up_to_date)
307 HostAddressVec result_up_to_date;
308 uint32_t threshold = static_cast<uint32_t>(
309 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
310 uint32_t updated_ttl;
311 BOOST_FOREACH( const HostAddress &addr, result )
313 updated_ttl = addr.get_ttl().get_updated_value();
314 if (updated_ttl > threshold)
315 result_up_to_date.push_back(addr);
317 GlobalLogger.debug() << "DnsCache: do not return "
318 << addr.get_ip().to_string() << " since TTL "
319 << updated_ttl << "s is out of date (thresh="
320 << threshold << "s)";
322 result = result_up_to_date;
324 /*GlobalLogger.debug() << "DnsCache: request IPs for " << key
325 << " --> " << result.size() << "-list";
326 BOOST_FOREACH( const HostAddress &addr, result )
327 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
328 << " (TTL " << addr.get_ttl().get_updated_value()
334 * @returns empty cname if no (up to date cname) for hostname in cache
336 Cname DnsCache::get_cname(const std::string &hostname,
337 const bool check_up_to_date)
339 std::string key = key_for_hostname(hostname);
340 Cname result_obj = CnameCache[key];
341 /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key
342 << " --> \"" << result_obj.Host << "\" (TTL "
343 << result_obj.Ttl.get_updated_value() << "s)";*/
344 if (result_obj.Host.empty())
347 else if (check_up_to_date)
349 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
350 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
354 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
355 return Cname(); // same as if there was no cname for hostname
362 // underlying assumption in this function: for a hostname, the cache has either
363 // a list of IPs saved or a cname saved, but never both
364 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
365 const bool check_up_to_date)
367 std::string current_host = hostname;
369 HostAddressVec result = get_ips(current_host, check_up_to_date);
370 int n_recursions = 0;
371 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
372 while ( result.empty() )
374 current_cname = get_cname(current_host, check_up_to_date);
375 if (current_cname.Host.empty())
378 current_host = key_for_hostname(current_cname.Host);
379 if (++n_recursions >= Config::MaxRetrievalRecursions)
381 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
382 << n_recursions << " in recursive IP retrieval!";
387 min_cname_ttl = min(min_cname_ttl,
388 current_cname.Ttl.get_updated_value());
389 result = get_ips(current_host, check_up_to_date);
393 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
394 << result.size() << "-list after " << n_recursions
397 // adjust ttl to min of ttl and min_cname_ttl
398 if (n_recursions > 0)
400 TimeToLive cname_ttl(min_cname_ttl);
402 BOOST_FOREACH( HostAddress &addr, result )
404 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
406 //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
407 addr.set_ttl(cname_ttl);
416 * from a list of CNAMEs find the first one that is out of date or empty
418 * returns the hostname that is out of date or empty if all CNAMEs are
421 * required in ResolverBase::get_skipper
423 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
424 const uint32_t ttl_thresh)
426 std::string first_outdated = hostname;
428 int n_recursions = 0;
431 if (++n_recursions >= Config::MaxRetrievalRecursions)
433 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
434 << n_recursions << " in search of outdated CNAMEs!";
435 return first_outdated; // not really out of date but currently
438 cname = get_cname(first_outdated);
439 if (cname.Host.empty())
440 // reached end of cname list --> everything was up-to-date
442 else if (cname.Ttl.get_updated_value() > ttl_thresh)
443 // cname is up to date --> continue looking
444 first_outdated = cname.Host;
446 // cname is out of date --> return its target
449 // reach this point only if cname chain does not end with an IP
450 // --> all are up-to-date