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
367 * required in ResolverBase::get_skipper
369 * Assume you have the following situation in cache with TTLs below:
370 * hostname --> cname1 --> cname2 --> ... --> cnameN [--> IP]
371 * 100 0 --> return cname2
372 * 100 100 100 100 --> return cnameN
373 * ( with N < Config::MaxRetrievalRecursions )
374 * hostname --> IP (no cnames involved) --> return hostname
376 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
377 const uint32_t ttl_thresh)
379 std::string first_outdated = hostname;
381 int n_recursions = 0;
384 if (++n_recursions >= Config::MaxRetrievalRecursions)
386 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
387 << n_recursions << " in search of outdated CNAMEs!";
391 cname = get_cname(first_outdated);
392 if (cname.Host.empty())
393 // reached end of cname list
395 else if (cname.Ttl.get_updated_value() > ttl_thresh)
396 // cname is up to date --> continue looking
397 first_outdated = cname.Host;
399 { // cname is out of date --> return its target
400 first_outdated = cname.Host;
404 return first_outdated;
407 // (created using vim -- the world's best text editor)