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;
47 int SaveTimerSeconds = 60;
48 int MaxRetrievalRecursions = 10;
56 Cname::Cname(const std::string &host, const uint32_t ttl)
61 Cname::Cname(const std::string &host, const TimeToLive &ttl)
66 DnsCache::DnsCache(const IoServiceItem &io_serv,
67 const std::string &cache_file)
70 , SaveTimer( *io_serv )
71 , CacheFile( cache_file )
74 // load cache from file
75 load_from_cachefile();
78 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
79 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
80 boost::asio::placeholders::error ) );
86 GlobalLogger.info() << "DnsCache: being destructed";
88 // save one last time without re-scheduling the next save
96 void DnsCache::schedule_save(const boost::system::error_code &error)
98 // just in case: ensure SaveTimer is cancelled
99 SaveTimer.cancel(); // (will do nothing if already expired/cancelled)
101 if ( error == boost::asio::error::operation_aborted ) // cancelled
103 GlobalLogger.error() << "DnsCache: SaveTimer was cancelled "
104 << "--> no save and no re-schedule of saving!";
109 GlobalLogger.error() << "DnsCache: Received error " << error
110 << " in schedule_save "
111 << "--> no save now but re-schedule saving";
116 // schedule next save
117 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
118 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
119 boost::asio::placeholders::error ) );
122 void DnsCache::save_to_cachefile()
125 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
126 else if (CacheFile.empty())
127 GlobalLogger.warning()
128 << "DnsCache: skip saving because file name empty!";
129 else if (CacheFile == DO_NOT_USE_CACHE_FILE)
130 GlobalLogger.info() << "DnsCache: configured not to use cache file";
135 std::ofstream ofs( CacheFile.c_str() );
136 boost::archive::xml_oarchive oa(ofs);
137 //oa << boost::serialization::make_nvp("IpCache", IpCache);
138 //oa << boost::serialization::make_nvp("CnameCache", CnameCache);
139 oa & BOOST_SERIALIZATION_NVP(IpCache);
140 oa & BOOST_SERIALIZATION_NVP(CnameCache);
141 GlobalLogger.info() << "DnsCache: saved to cache file " << CacheFile;
145 catch (std::exception &exc)
147 GlobalLogger.warning() << "Saving failed: " << exc.what();
152 void DnsCache::load_from_cachefile()
154 if (CacheFile.empty())
155 GlobalLogger.warning()
156 << "DnsCache: cannot load because cache file name is empty!";
157 else if (CacheFile == DO_NOT_USE_CACHE_FILE)
158 GlobalLogger.info() << "DnsCache: configured not to use cache file";
159 else if ( !I2n::file_exists(CacheFile) )
160 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
161 << CacheFile << " does not exist!";
166 std::ifstream ifs( CacheFile.c_str() );
167 boost::archive::xml_iarchive ia(ifs);
169 ia & BOOST_SERIALIZATION_NVP(IpCache);
170 ia & BOOST_SERIALIZATION_NVP(CnameCache);
171 GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
173 catch (boost::archive::archive_exception &exc)
175 GlobalLogger.warning() << "DnsCache: archive exception loading from "
176 << CacheFile << ": " << exc.what();
178 catch (std::exception &exc)
180 GlobalLogger.warning() << "DnsCache: exception while loading from "
181 << CacheFile << ": " << exc.what();
187 // warn if hostname is empty and remove trailing dot
188 std::string DnsCache::key_for_hostname(const std::string &hostname) const
190 if (hostname.empty())
192 GlobalLogger.warning() << "DnsCache: empty host!";
196 // check whether last character is a dot
197 if (hostname.rfind('.') == hostname.length()-1)
198 return hostname.substr(0, hostname.length()-1);
204 void DnsCache::update(const std::string &hostname,
205 const HostAddressVec &new_ips)
207 std::string key = key_for_hostname(hostname);
208 if ( !get_cname(hostname).Host.empty() )
209 { // ensure that there is never IP and CNAME for the same host
210 GlobalLogger.warning() << "DnsCache: Saving IPs for " << key
211 << " removes CNAME to " << get_cname(hostname).Host << "!";
212 update(hostname, Cname()); // overwrite with "empty" cname
214 GlobalLogger.debug() << "DnsCache: update IPs for " << key
215 << " to " << new_ips.size() << "-list";
216 IpCache[key] = new_ips;
221 void DnsCache::update(const std::string &hostname,
224 std::string key = key_for_hostname(hostname);
225 if ( !get_ips(hostname).empty() )
226 { // ensure that there is never IP and CNAME for the same host
227 GlobalLogger.warning() << "DnsCache: Saving CNAME for " << key
228 << " removes " << get_ips(hostname).size() << " IPs for same host!";
229 update(hostname, HostAddressVec()); // overwrite with empty IP list
232 // remove possible trailing dot from cname
233 Cname to_save = Cname(key_for_hostname(cname.Host),
236 GlobalLogger.info() << "DnsCache: update CNAME for " << key
237 << " to " << to_save.Host;
238 CnameCache[key] = to_save;
244 * @returns empty list if no (up to date) ips for hostname in cache
246 HostAddressVec DnsCache::get_ips(const std::string &hostname,
247 const bool check_up_to_date)
249 std::string key = key_for_hostname(hostname);
250 HostAddressVec result = IpCache[key];
251 if (check_up_to_date)
253 HostAddressVec result_up_to_date;
254 uint32_t threshold = static_cast<uint32_t>(
255 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
256 uint32_t updated_ttl;
257 BOOST_FOREACH( const HostAddress &addr, result )
259 updated_ttl = addr.get_ttl().get_updated_value();
260 if (updated_ttl > threshold)
261 result_up_to_date.push_back(addr);
263 GlobalLogger.debug() << "DnsCache: do not return "
264 << addr.get_ip().to_string() << " since TTL "
265 << updated_ttl << "s is out of date (thresh="
266 << threshold << "s)";
268 result = result_up_to_date;
270 GlobalLogger.debug() << "DnsCache: request IPs for " << key
271 << " --> " << result.size() << "-list";
272 BOOST_FOREACH( const HostAddress &addr, result )
273 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
274 << " (TTL " << addr.get_ttl().get_value() << "s)";
279 * @returns empty cname if no (up to date cname) for hostname in cache
281 Cname DnsCache::get_cname(const std::string &hostname,
282 const bool check_up_to_date)
284 std::string key = key_for_hostname(hostname);
285 Cname result_obj = CnameCache[key];
286 GlobalLogger.debug() << "DnsCache: request CNAME for " << key
287 << " --> \"" << result_obj.Host << "\"";
288 if (result_obj.Host.empty())
291 else if (check_up_to_date)
293 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
294 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
298 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
299 return Cname(); // same as if there was no cname for hostname
306 // underlying assumption in this function: for a hostname, the cache has either
307 // a list of IPs saved or a cname saved, but never both
308 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
309 const bool check_up_to_date)
311 std::string current_host = hostname;
313 HostAddressVec result = get_ips(current_host);
314 int n_recursions = 0;
315 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
316 while ( result.empty() )
318 current_cname = get_cname(current_host, check_up_to_date);
319 current_host = key_for_hostname(current_cname.Host);
320 if (current_host.empty())
322 else if (++n_recursions >= Config::MaxRetrievalRecursions)
324 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
325 << n_recursions << " in recursive IP retrieval!";
330 min_cname_ttl = min(min_cname_ttl, current_cname.Ttl.get_value());
331 result = get_ips(current_host, check_up_to_date);
335 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
336 << result.size() << "-list after " << n_recursions
339 // adjust ttl to min of ttl and min_cname_ttl
340 if (n_recursions > 0)
342 TimeToLive cname_ttl(min_cname_ttl);
344 BOOST_FOREACH( HostAddress &addr, result )
346 if (addr.get_ttl().get_value() > min_cname_ttl)
347 addr.set_ttl(cname_ttl);
355 * from a list of CNAMEs find the first one that is out of date
357 * required in ResolverBase::get_skipper
359 * Assume you have the following situation in cache with TTLs below:
360 * hostname --> cname1 --> cname2 --> ... --> cnameN [--> IP]
361 * 100 0 --> return cname2
362 * 100 100 100 100 --> return cnameN
363 * ( with N < Config::MaxRetrievalRecursions )
364 * hostname --> IP (no cnames involved) --> return hostname
366 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
367 const uint32_t ttl_thresh)
369 std::string up_to_date_host = hostname;
371 int n_recursions = 0;
374 if (++n_recursions >= Config::MaxRetrievalRecursions)
376 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
377 << n_recursions << " in search of outdated CNAMEs!";
381 cname = get_cname(up_to_date_host);
382 if (key_for_hostname(cname.Host).empty())
383 // reached end of cname list
385 else if (cname.Ttl.get_updated_value() > ttl_thresh)
386 // cname is up to date --> continue looking
387 up_to_date_host = cname.Host;
389 // cname is out of date --> return that
392 return up_to_date_host;
395 // (created using vim -- the world's best text editor)