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() << "DnsCache: 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_updated_value()
286 * @returns empty cname if no (up to date cname) for hostname in cache
288 Cname DnsCache::get_cname(const std::string &hostname,
289 const bool check_up_to_date)
291 std::string key = key_for_hostname(hostname);
292 Cname result_obj = CnameCache[key];
293 GlobalLogger.debug() << "DnsCache: request CNAME for " << key
294 << " --> \"" << result_obj.Host << "\" (TTL "
295 << result_obj.Ttl.get_updated_value() << "s)";
296 if (result_obj.Host.empty())
299 else if (check_up_to_date)
301 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
302 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
306 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
307 return Cname(); // same as if there was no cname for hostname
314 // underlying assumption in this function: for a hostname, the cache has either
315 // a list of IPs saved or a cname saved, but never both
316 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
317 const bool check_up_to_date)
319 std::string current_host = hostname;
321 HostAddressVec result = get_ips(current_host, check_up_to_date);
322 int n_recursions = 0;
323 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
324 while ( result.empty() )
326 current_cname = get_cname(current_host, check_up_to_date);
327 if (current_cname.Host.empty())
330 current_host = key_for_hostname(current_cname.Host);
331 if (++n_recursions >= Config::MaxRetrievalRecursions)
333 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
334 << n_recursions << " in recursive IP retrieval!";
339 min_cname_ttl = min(min_cname_ttl,
340 current_cname.Ttl.get_updated_value());
341 result = get_ips(current_host, check_up_to_date);
345 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
346 << result.size() << "-list after " << n_recursions
349 // adjust ttl to min of ttl and min_cname_ttl
350 if (n_recursions > 0)
352 TimeToLive cname_ttl(min_cname_ttl);
354 BOOST_FOREACH( HostAddress &addr, result )
356 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
358 GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
359 addr.set_ttl(cname_ttl);
368 * from a list of CNAMEs find the first one that is out of date or empty
370 * returns the hostname that is out of date or empty if all CNAMEs are
373 * required in ResolverBase::get_skipper
375 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
376 const uint32_t ttl_thresh)
378 std::string first_outdated = hostname;
380 int n_recursions = 0;
383 if (++n_recursions >= Config::MaxRetrievalRecursions)
385 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
386 << n_recursions << " in search of outdated CNAMEs!";
387 return first_outdated; // not really out of date but currently
390 cname = get_cname(first_outdated);
391 if (cname.Host.empty())
392 // reached end of cname list --> everything was up-to-date
394 else if (cname.Ttl.get_updated_value() > ttl_thresh)
395 // cname is up to date --> continue looking
396 first_outdated = cname.Host;
398 // cname is out of date --> return its target
401 // reach this point only if cname chain does not end with an IP
402 // --> all are up-to-date