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 DnsCache::DnsCache(const IoServiceItem &io_serv,
62 const std::string &cache_file)
65 , SaveTimer( *io_serv )
66 , CacheFile( cache_file )
69 // load cache from file
70 load_from_cachefile();
73 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
74 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
75 boost::asio::placeholders::error ) );
81 GlobalLogger.info() << "Dns Cache is being destructed";
83 // save one last time without re-scheduling the next save
91 void DnsCache::schedule_save(const boost::system::error_code &error)
93 // just in case: ensure SaveTimer is cancelled
94 SaveTimer.cancel(); // (will do nothing if already expired/cancelled)
96 if ( error == boost::asio::error::operation_aborted ) // cancelled
98 GlobalLogger.error() << "DnsCache: SaveTimer was cancelled "
99 << "--> no save and no re-schedule of saving!";
104 GlobalLogger.error() << "DnsCache: Received error " << error
105 << " in schedule_save "
106 << "--> no save now but re-schedule saving";
111 // schedule next save
112 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
113 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
114 boost::asio::placeholders::error ) );
117 void DnsCache::save_to_cachefile()
121 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
124 else if (CacheFile.empty())
126 GlobalLogger.warning()
127 << "DnsCache: skip saving because file name empty!";
133 std::ofstream ofs( CacheFile.c_str() );
134 boost::archive::xml_oarchive oa(ofs);
135 oa << boost::serialization::make_nvp("IpCache", IpCache);
136 oa << boost::serialization::make_nvp("CnameCache", CnameCache);
137 GlobalLogger.info() << "DnsCache: saved to cache file " << CacheFile;
141 catch (std::exception &exc)
143 GlobalLogger.warning() << "Saving failed: " << exc.what();
148 void DnsCache::load_from_cachefile()
150 if (CacheFile.empty())
152 GlobalLogger.warning()
153 << "DnsCache: cannot load because cache file name is empty!";
156 else if ( !I2n::file_exists(CacheFile) )
158 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
159 << CacheFile << " does not exist!";
164 HostAddressVec cache;
166 std::ifstream ifs( CacheFile.c_str() );
167 boost::archive::xml_iarchive ia(ifs);
169 ia >> boost::serialization::make_nvp("IpCache", cache);
170 ia >> boost::serialization::make_nvp("CnameCache", cache);
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();
185 void DnsCache::update(const std::string &hostname,
186 const HostAddressVec &new_data)
188 GlobalLogger.info() << "DnsCache: update IPs for " << hostname
189 << " to " << new_data.size() << "-list";
190 IpCache[hostname] = new_data;
195 void DnsCache::update(const std::string &hostname,
198 GlobalLogger.info() << "DnsCache: update CNAME for " << hostname
199 << " to " << cname.Host;
200 CnameCache[hostname] = cname;
205 void DnsCache::update(const std::string &hostname,
206 const uint32_t new_ttl)
208 GlobalLogger.info() << "DnsCache: ensure TTL for IPs for " << hostname
209 << " is below " << new_ttl << "s";
210 HostAddressVec ips = IpCache[hostname];
211 TimeToLive current_ttl;
212 BOOST_FOREACH( HostAddress &addr, ips )
214 current_ttl = addr.get_ttl();
215 if (current_ttl.get_value() > new_ttl)
217 current_ttl.set_value(new_ttl);
218 addr.set_ttl(current_ttl);
222 IpCache[hostname] = ips;
227 * @returns empty list if no (up to date) ips for hostname in cache
229 HostAddressVec DnsCache::get_ips(const std::string &hostname,
230 const bool check_up_to_date)
232 HostAddressVec result = IpCache[hostname];
233 if (check_up_to_date)
235 HostAddressVec result_up_to_date;
236 uint32_t threshold = static_cast<uint32_t>(
237 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
238 BOOST_FOREACH( const HostAddress &addr, result )
240 if (addr.get_ttl().get_updated_value() > threshold)
241 result_up_to_date.push_back(addr);
243 GlobalLogger.debug() << "DnsCache: From cached list of size "
244 << result.size() << " return " << result_up_to_date.size()
245 << " since rest out of date";
246 result = result_up_to_date;
248 GlobalLogger.info() << "DnsCache: request IPs for " << hostname
249 << " --> " << result.size() << "-list";
254 * @returns empty cname if no (up to date cname) for hostname in cache
256 Cname DnsCache::get_cname(const std::string &hostname,
257 const bool check_up_to_date)
259 Cname result_obj = CnameCache[hostname];
260 GlobalLogger.info() << "DnsCache: request CNAME for " << hostname
261 << " --> \"" << result_obj.Host << "\"";
262 if (result_obj.Host.empty())
265 else if (check_up_to_date)
267 if (result_obj.Ttl.get_updated_value() > DnsMaster::get_instance()
268 ->get_resolved_ip_ttl_threshold())
272 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
273 return Cname(); // same as if there was no cname for hostname
280 // underlying assumption in this function: for a hostname, the cache has either
281 // a list of IPs saved or a cname saved, but never both
282 HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
283 const bool check_up_to_date)
285 std::string current_host = hostname;
287 HostAddressVec result = get_ips(current_host);
288 int n_recursions = 0;
289 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
290 while ( result.empty() )
292 current_cname = get_cname(current_host, check_up_to_date);
293 current_host = current_cname.Host;
294 if (current_host.empty())
296 else if (++n_recursions >= Config::MaxRetrievalRecursions)
298 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
299 << n_recursions << " in recursive IP retrieval!";
304 min_cname_ttl = min(min_cname_ttl, current_cname.Ttl.get_value());
305 result = get_ips(current_host, check_up_to_date);
309 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
310 << result.size() << "-list after " << n_recursions
313 // adjust ttl to min of ttl and min_cname_ttl
314 if (n_recursions > 0)
316 TimeToLive cname_ttl(min_cname_ttl);
318 BOOST_FOREACH( HostAddress &addr, result )
320 if (addr.get_ttl().get_value() > min_cname_ttl)
321 addr.set_ttl(cname_ttl);
329 * from a list of CNAMEs find the first one that is out of date
331 * required in ResolverBase::get_skipper
333 * Assume you have the following situation in cache with TTLs below:
334 * hostname --> cname1 --> cname2 --> ... --> cnameN [--> IP]
335 * 100 0 --> return cname2
336 * 100 100 100 100 --> return cnameN
337 * ( with N < Config::MaxRetrievalRecursions )
338 * hostname --> IP (no cnames involved) --> return hostname
340 std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
341 const uint32_t ttl_thresh)
343 std::string up_to_date_host = hostname;
345 int n_recursions = 0;
348 if (++n_recursions >= Config::MaxRetrievalRecursions)
350 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
351 << n_recursions << " in search of outdated CNAMEs!";
355 cname = get_cname(up_to_date_host);
356 if (cname.Host.empty())
357 // reached end of cname list
359 else if (cname.Ttl.get_updated_value() > ttl_thresh)
360 // cname is up to date --> continue looking
361 up_to_date_host = cname.Host;
363 // cname is out of date --> return that
366 return up_to_date_host;
369 // (created using vim -- the world's best text editor)