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;
57 Cname::Cname(const std::string &host, const uint32_t ttl)
62 Cname::Cname(const std::string &host, const TimeToLive &ttl)
68 const string DnsCache::DoNotUseCacheFile = "do not use cache file!";
70 DnsCache::DnsCache(const IoServiceItem &io_serv,
71 const std::string &cache_file)
74 , SaveTimer( *io_serv )
75 , CacheFile( cache_file )
78 // load cache from file
79 load_from_cachefile();
82 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
83 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
84 boost::asio::placeholders::error ) );
90 GlobalLogger.info() << "DnsCache: being destructed";
92 // save one last time without re-scheduling the next save
100 void DnsCache::schedule_save(const boost::system::error_code &error)
102 // just in case: ensure SaveTimer is cancelled
103 SaveTimer.cancel(); // (will do nothing if already expired/cancelled)
105 if ( error == boost::asio::error::operation_aborted ) // cancelled
107 GlobalLogger.error() << "DnsCache: SaveTimer was cancelled "
108 << "--> no save and no re-schedule of saving!";
113 GlobalLogger.error() << "DnsCache: Received error " << error
114 << " in schedule_save "
115 << "--> no save now but re-schedule saving";
120 // schedule next save
121 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
122 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
123 boost::asio::placeholders::error ) );
126 void DnsCache::save_to_cachefile()
129 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
130 else if (CacheFile.empty())
131 GlobalLogger.warning()
132 << "DnsCache: skip saving because file name empty!";
133 else if (CacheFile == DoNotUseCacheFile)
134 GlobalLogger.info() << "DnsCache: configured not to use cache file";
139 std::ofstream ofs( CacheFile.c_str() );
140 boost::archive::xml_oarchive oa(ofs);
141 //oa << boost::serialization::make_nvp("IpCache", IpCache);
142 //oa << boost::serialization::make_nvp("CnameCache", CnameCache);
143 oa & BOOST_SERIALIZATION_NVP(IpCache);
144 oa & BOOST_SERIALIZATION_NVP(CnameCache);
145 GlobalLogger.info() << "DnsCache: saved to cache file "
150 catch (std::exception &exc)
152 GlobalLogger.warning() << "Saving failed: " << exc.what();
157 void DnsCache::load_from_cachefile()
159 if (CacheFile.empty())
160 GlobalLogger.warning()
161 << "DnsCache: cannot load because cache file name is empty!";
162 else if (CacheFile == DoNotUseCacheFile)
163 GlobalLogger.info() << "DnsCache: configured not to use cache file";
164 else if ( !I2n::file_exists(CacheFile) )
165 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
166 << CacheFile << " does not exist!";
171 std::ifstream ifs( CacheFile.c_str() );
172 boost::archive::xml_iarchive ia(ifs);
174 ia & BOOST_SERIALIZATION_NVP(IpCache);
175 ia & BOOST_SERIALIZATION_NVP(CnameCache);
176 GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
178 catch (boost::archive::archive_exception &exc)
180 GlobalLogger.warning()
181 << "DnsCache: archive exception loading from " << CacheFile
182 << ": " << exc.what();
184 catch (std::exception &exc)
186 GlobalLogger.warning() << "DnsCache: exception while loading from "
187 << CacheFile << ": " << exc.what();
193 // warn if hostname is empty and remove trailing dot
194 std::string DnsCache::key_for_hostname(const std::string &hostname) const
196 if (hostname.empty())
198 GlobalLogger.warning() << "DnsCache: empty host!";
202 // check whether last character is a dot
203 if (hostname.rfind('.') == hostname.length()-1)
204 return hostname.substr(0, hostname.length()-1);
210 void DnsCache::update(const std::string &hostname,
211 const HostAddressVec &new_ips)
213 std::string key = key_for_hostname(hostname);
214 if ( !get_cname(hostname).Host.empty() )
215 { // ensure that there is never IP and CNAME for the same host
216 GlobalLogger.warning() << "DnsCache: Saving IPs for " << key
217 << " removes CNAME to " << get_cname(hostname).Host << "!";
218 update(hostname, Cname()); // overwrite with "empty" cname
220 GlobalLogger.debug() << "DnsCache: update IPs for " << key
221 << " to " << new_ips.size() << "-list";
222 IpCache[key] = new_ips;
227 void DnsCache::update(const std::string &hostname,
230 std::string key = key_for_hostname(hostname);
231 if ( !get_ips(hostname).empty() )
232 { // ensure that there is never IP and CNAME for the same host
233 GlobalLogger.warning() << "DnsCache: Saving CNAME for " << key
234 << " removes " << get_ips(hostname).size() << " IPs for same host!";
235 update(hostname, HostAddressVec()); // overwrite with empty IP list
238 // remove possible trailing dot from cname
239 Cname to_save = Cname(key_for_hostname(cname.Host),
242 GlobalLogger.info() << "DnsCache: update CNAME for " << key
243 << " to " << to_save.Host;
244 CnameCache[key] = to_save;
250 * @returns empty list if no (up to date) ips for hostname in cache
252 HostAddressVec DnsCache::get_ips(const std::string &hostname,
253 const bool check_up_to_date)
255 std::string key = key_for_hostname(hostname);
256 HostAddressVec result = IpCache[key];
257 if (check_up_to_date)
259 HostAddressVec result_up_to_date;
260 uint32_t threshold = static_cast<uint32_t>(
261 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
262 uint32_t updated_ttl;
263 BOOST_FOREACH( const HostAddress &addr, result )
265 updated_ttl = addr.get_ttl().get_updated_value();
266 if (updated_ttl > threshold)
267 result_up_to_date.push_back(addr);
269 GlobalLogger.debug() << "DnsCache: do not return "
270 << addr.get_ip().to_string() << " since TTL "
271 << updated_ttl << "s is out of date (thresh="
272 << threshold << "s)";
274 result = result_up_to_date;
276 GlobalLogger.debug() << "DnsCache: request IPs for " << key
277 << " --> " << result.size() << "-list";
278 BOOST_FOREACH( const HostAddress &addr, result )
279 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
280 << " (TTL " << addr.get_ttl().get_value() << "s)";
285 * @returns empty cname if no (up to date cname) for hostname in cache
287 Cname DnsCache::get_cname(const std::string &hostname,
288 const bool check_up_to_date)
290 std::string key = key_for_hostname(hostname);
291 Cname result_obj = CnameCache[key];
292 GlobalLogger.debug() << "DnsCache: request CNAME for " << key
293 << " --> \"" << result_obj.Host << "\"";
294 if (result_obj.Host.empty())
297 else if (check_up_to_date)
299 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
300 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
304 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
305 return Cname(); // same as if there was no cname for hostname
312 // underlying assumption in this function: for a hostname, the cache has either
313 // a list of IPs saved or a cname saved, but never both
314 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
315 const bool check_up_to_date)
317 std::string current_host = hostname;
319 HostAddressVec result = get_ips(current_host);
320 int n_recursions = 0;
321 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
322 while ( result.empty() )
324 current_cname = get_cname(current_host, check_up_to_date);
325 if (current_cname.Host.empty())
328 current_host = key_for_hostname(current_cname.Host);
329 if (++n_recursions >= Config::MaxRetrievalRecursions)
331 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
332 << n_recursions << " in recursive IP retrieval!";
337 min_cname_ttl = min(min_cname_ttl, current_cname.Ttl.get_value());
338 result = get_ips(current_host, check_up_to_date);
342 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
343 << result.size() << "-list after " << n_recursions
346 // adjust ttl to min of ttl and min_cname_ttl
347 if (n_recursions > 0)
349 TimeToLive cname_ttl(min_cname_ttl);
351 BOOST_FOREACH( HostAddress &addr, result )
353 if (addr.get_ttl().get_value() > min_cname_ttl)
355 GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
356 addr.set_ttl(cname_ttl);
365 * from a list of CNAMEs find the first one that is out of date or empty
367 * returns the hostname that is out of date or empty if all CNAMEs are
370 * required in ResolverBase::get_skipper
372 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
373 const uint32_t ttl_thresh)
375 std::string first_outdated = hostname;
377 int n_recursions = 0;
380 if (++n_recursions >= Config::MaxRetrievalRecursions)
382 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
383 << n_recursions << " in search of outdated CNAMEs!";
384 return first_outdated; // not really out of date but currently
387 cname = get_cname(first_outdated);
388 if (cname.Host.empty())
389 // reached end of cname list --> everything was up-to-date
391 else if (cname.Ttl.get_updated_value() > ttl_thresh)
392 // cname is up to date --> continue looking
393 first_outdated = cname.Host;
395 // cname is out of date --> return its target
398 // reach this point only if cname chain does not end with an IP
399 // --> all are up-to-date
403 // (created using vim -- the world's best text editor)