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 // -----------------------------------------------------------------------------
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.info() << "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.info() << "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 stringstream log_temp;
255 log_temp << "DnsCache: update IPs for " << key << " to "
256 << ips_checked.size() << "-list: ";
257 BOOST_FOREACH( const HostAddress &ip, ips_checked )
258 log_temp << ip.get_ip() << ", ";
259 GlobalLogger.notice() << log_temp.str();
261 IpCache[key] = ips_checked;
266 void DnsCache::update(const std::string &hostname,
269 std::string key = key_for_hostname(hostname);
270 if ( !get_ips(hostname).empty() )
271 { // ensure that there is never IP and CNAME for the same host
272 GlobalLogger.info() << "DnsCache: Saving CNAME for " << key
273 << " removes " << get_ips(hostname).size() << " IPs for same host!";
274 update(hostname, HostAddressVec()); // overwrite with empty IP list
277 // remove possible trailing dot from cname
278 Cname to_save = Cname(key_for_hostname(cname.Host),
281 // ensure min ttl of MinTimeBetweenResolves
282 if ( to_save.Ttl.get_value() < MinTimeBetweenResolves )
284 GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of "
285 << hostname << " from " << to_save.Ttl.get_value() << "s to "
286 << MinTimeBetweenResolves << "s because was too short";
287 to_save.Ttl = TimeToLive(MinTimeBetweenResolves);
290 GlobalLogger.notice() << "DnsCache: update CNAME for " << key
291 << " to " << to_save.Host;
292 CnameCache[key] = to_save;
297 // -----------------------------------------------------------------------------
299 // -----------------------------------------------------------------------------
302 * @returns empty list if no (up to date) ips for hostname in cache
304 HostAddressVec DnsCache::get_ips(const std::string &hostname,
305 const bool check_up_to_date)
307 std::string key = key_for_hostname(hostname);
308 HostAddressVec result = IpCache[key];
309 if (check_up_to_date)
311 HostAddressVec result_up_to_date;
312 uint32_t threshold = static_cast<uint32_t>(
313 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
314 uint32_t updated_ttl;
315 BOOST_FOREACH( const HostAddress &addr, result )
317 updated_ttl = addr.get_ttl().get_updated_value();
318 if (updated_ttl > threshold)
319 result_up_to_date.push_back(addr);
321 GlobalLogger.debug() << "DnsCache: do not return "
322 << addr.get_ip().to_string() << " since TTL "
323 << updated_ttl << "s is out of date (thresh="
324 << threshold << "s)";
326 result = result_up_to_date;
328 /*GlobalLogger.debug() << "DnsCache: request IPs for " << key
329 << " --> " << result.size() << "-list";
330 BOOST_FOREACH( const HostAddress &addr, result )
331 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
332 << " (TTL " << addr.get_ttl().get_updated_value()
338 * @returns empty cname if no (up to date cname) for hostname in cache
340 Cname DnsCache::get_cname(const std::string &hostname,
341 const bool check_up_to_date)
343 std::string key = key_for_hostname(hostname);
344 Cname result_obj = CnameCache[key];
345 /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key
346 << " --> \"" << result_obj.Host << "\" (TTL "
347 << result_obj.Ttl.get_updated_value() << "s)";*/
348 if (result_obj.Host.empty())
351 else if (check_up_to_date)
353 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
354 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
358 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
359 return Cname(); // same as if there was no cname for hostname
366 // underlying assumption in this function: for a hostname, the cache has either
367 // a list of IPs saved or a cname saved, but never both
368 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
369 const bool check_up_to_date)
371 std::string current_host = hostname;
373 HostAddressVec result = get_ips(current_host, check_up_to_date);
374 int n_recursions = 0;
375 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
376 int max_recursion_count = DnsMaster::get_instance()
377 ->get_max_recursion_count();
378 while ( result.empty() )
380 current_cname = get_cname(current_host, check_up_to_date);
381 if (current_cname.Host.empty())
384 current_host = key_for_hostname(current_cname.Host);
385 if (++n_recursions >= max_recursion_count)
387 GlobalLogger.info() << "DnsCache: reached recursion limit of "
388 << n_recursions << " in recursive IP retrieval of "
394 min_cname_ttl = min(min_cname_ttl,
395 current_cname.Ttl.get_updated_value());
396 result = get_ips(current_host, check_up_to_date);
400 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
401 << result.size() << "-list after " << n_recursions
404 // adjust ttl to min of ttl and min_cname_ttl
405 if (n_recursions > 0)
407 TimeToLive cname_ttl(min_cname_ttl);
409 BOOST_FOREACH( HostAddress &addr, result )
411 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
413 //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
414 addr.set_ttl(cname_ttl);
423 * from a list of CNAMEs find the first one that is out of date or empty
425 * returns the hostname that is out of date or empty if all CNAMEs are
428 * required in ResolverBase::get_skipper
430 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
431 const uint32_t ttl_thresh)
433 std::string first_outdated = hostname;
435 int n_recursions = 0;
436 int max_recursion_count = DnsMaster::get_instance()
437 ->get_max_recursion_count();
440 if (++n_recursions >= max_recursion_count)
442 GlobalLogger.info() << "DnsCache: reached recursion limit of "
443 << n_recursions << " in search of outdated CNAMEs for "
445 return first_outdated; // not really out of date but currently
448 cname = get_cname(first_outdated);
449 if (cname.Host.empty())
450 // reached end of cname list --> everything was up-to-date
452 else if (cname.Ttl.get_updated_value() > ttl_thresh)
453 // cname is up to date --> continue looking
454 first_outdated = cname.Host;
456 // cname is out of date --> return its target
459 // reach this point only if cname chain does not end with an IP
460 // --> all are up-to-date