simplified dns (no self-made recursion); merge PingScheduler and PingRotate; make...
[pingcheck] / src / dns / dnscache.cpp
CommitLineData
96779587
CH
1/*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
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.
13
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.
16
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.
19
20 Christian Herdtweck, Intra2net AG 2015
21 */
22
c5b4902d 23#include "dns/dnscache.h"
96779587 24
946356e1 25#include <fstream>
96779587 26#include <logfunc.hpp>
946356e1 27#include <filefunc.hxx> // I2n::file_exists
e18c1337 28#include <boost/foreach.hpp>
96779587
CH
29#include <boost/bind.hpp>
30#include <boost/date_time/posix_time/posix_time.hpp>
31#include <boost/asio/placeholders.hpp>
946356e1
CH
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>
96779587 38
dbe986b9
CH
39#include "dns/dnsmaster.h"
40
96779587
CH
41using boost::bind;
42using boost::posix_time::seconds;
43using I2n::Logger::GlobalLogger;
44
45namespace Config
46{
47 int SaveTimerSeconds = 60;
dbe986b9 48 int MaxRetrievalRecursions = 10;
96779587
CH
49}
50
23f51766
CH
51Cname::Cname()
52 : Host()
53 , Ttl()
54{}
55
56Cname::Cname(const std::string &host, const uint32_t ttl)
57 : Host( host )
58 , Ttl( ttl )
59{}
60
96779587
CH
61DnsCache::DnsCache(const IoServiceItem &io_serv,
62 const std::string &cache_file)
ad83004d
CH
63 : IpCache()
64 , CnameCache()
96779587
CH
65 , SaveTimer( *io_serv )
66 , CacheFile( cache_file )
67 , HasChanged( false )
68{
69 // load cache from file
70 load_from_cachefile();
71
72 // schedule next save
73 (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
74 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
75 boost::asio::placeholders::error ) );
76}
77
78
79DnsCache::~DnsCache()
80{
e91538f0
CH
81 GlobalLogger.info() << "Dns Cache is being destructed";
82
96779587
CH
83 // save one last time without re-scheduling the next save
84 save_to_cachefile();
85
86 // cancel save timer
87 SaveTimer.cancel();
88}
89
90
91void DnsCache::schedule_save(const boost::system::error_code &error)
92{
93 // just in case: ensure SaveTimer is cancelled
94 SaveTimer.cancel(); // (will do nothing if already expired/cancelled)
95
96 if ( error == boost::asio::error::operation_aborted ) // cancelled
97 {
ad83004d 98 GlobalLogger.error() << "DnsCache: SaveTimer was cancelled "
96779587
CH
99 << "--> no save and no re-schedule of saving!";
100 return;
101 }
102 else if (error)
103 {
ad83004d 104 GlobalLogger.error() << "DnsCache: Received error " << error
96779587
CH
105 << " in schedule_save "
106 << "--> no save now but re-schedule saving";
107 }
108 else
109 save_to_cachefile();
110
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 ) );
115}
116
117void DnsCache::save_to_cachefile()
118{
119 if (!HasChanged)
120 {
ad83004d 121 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
96779587
CH
122 return;
123 }
946356e1
CH
124 else if (CacheFile.empty())
125 {
126 GlobalLogger.warning()
ad83004d 127 << "DnsCache: skip saving because file name empty!";
946356e1
CH
128 return;
129 }
96779587 130
946356e1
CH
131 try
132 {
133 std::ofstream ofs( CacheFile.c_str() );
134 boost::archive::xml_oarchive oa(ofs);
ad83004d
CH
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;
946356e1
CH
138
139 HasChanged = false;
140 }
141 catch (std::exception &exc)
142 {
143 GlobalLogger.warning() << "Saving failed: " << exc.what();
144 }
96779587
CH
145}
146
147
148void DnsCache::load_from_cachefile()
149{
946356e1
CH
150 if (CacheFile.empty())
151 {
152 GlobalLogger.warning()
ad83004d 153 << "DnsCache: cannot load because cache file name is empty!";
946356e1
CH
154 return;
155 }
156 else if ( !I2n::file_exists(CacheFile) )
157 {
ad83004d 158 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
946356e1
CH
159 << CacheFile << " does not exist!";
160 return;
161 }
162 try
163 {
164 HostAddressVec cache;
165
166 std::ifstream ifs( CacheFile.c_str() );
167 boost::archive::xml_iarchive ia(ifs);
168
ad83004d
CH
169 ia >> boost::serialization::make_nvp("IpCache", cache);
170 ia >> boost::serialization::make_nvp("CnameCache", cache);
171 GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
946356e1
CH
172 }
173 catch (boost::archive::archive_exception &exc)
174 {
ad83004d 175 GlobalLogger.warning() << "DnsCache: archive exception loading from "
946356e1
CH
176 << CacheFile << ": " << exc.what();
177 }
178 catch (std::exception &exc)
179 {
ad83004d 180 GlobalLogger.warning() << "DnsCache: exception while loading from "
946356e1
CH
181 << CacheFile << ": " << exc.what();
182 }
96779587
CH
183}
184
96779587 185void DnsCache::update(const std::string &hostname,
4e7b6ff9 186 const HostAddressVec &new_data)
96779587 187{
ad83004d 188 GlobalLogger.info() << "DnsCache: update IPs for " << hostname
96779587 189 << " to " << new_data.size() << "-list";
ad83004d 190 IpCache[hostname] = new_data;
96779587
CH
191 HasChanged = true;
192}
193
194
ad83004d 195void DnsCache::update(const std::string &hostname,
dbe986b9 196 const Cname &cname)
ad83004d
CH
197{
198 GlobalLogger.info() << "DnsCache: update CNAME for " << hostname
23f51766 199 << " to " << cname.Host;
ad83004d
CH
200 CnameCache[hostname] = cname;
201 HasChanged = true;
202}
203
204
dbe986b9 205void DnsCache::update(const std::string &hostname,
ad83004d
CH
206 const uint32_t new_ttl)
207{
208 GlobalLogger.info() << "DnsCache: ensure TTL for IPs for " << hostname
e18c1337 209 << " is below " << new_ttl << "s";
ad83004d 210 HostAddressVec ips = IpCache[hostname];
ad83004d
CH
211 TimeToLive current_ttl;
212 BOOST_FOREACH( HostAddress &addr, ips )
213 {
214 current_ttl = addr.get_ttl();
215 if (current_ttl.get_value() > new_ttl)
216 {
217 current_ttl.set_value(new_ttl);
218 addr.set_ttl(current_ttl);
219 HasChanged = true;
220 }
221 }
e18c1337 222 IpCache[hostname] = ips;
ad83004d
CH
223}
224
225
23f51766
CH
226/**
227 * @returns empty list if no (up to date) ips for hostname in cache
228 */
dbe986b9
CH
229HostAddressVec DnsCache::get_ips(const std::string &hostname,
230 const bool check_up_to_date)
96779587 231{
dbe986b9
CH
232 HostAddressVec result = IpCache[hostname];
233 if (check_up_to_date)
234 {
235 HostAddressVec result_up_to_date;
23f51766
CH
236 uint32_t threshold = static_cast<uint32_t>(
237 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
dbe986b9
CH
238 BOOST_FOREACH( const HostAddress &addr, result )
239 {
240 if (addr.get_ttl().get_updated_value() > threshold)
241 result_up_to_date.push_back(addr);
242 }
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;
247 }
ad83004d 248 GlobalLogger.info() << "DnsCache: request IPs for " << hostname
dbe986b9
CH
249 << " --> " << result.size() << "-list";
250 return result;
96779587
CH
251}
252
23f51766
CH
253/**
254 * @returns empty cname if no (up to date cname) for hostname in cache
255 */
256Cname DnsCache::get_cname(const std::string &hostname,
257 const bool check_up_to_date)
ad83004d 258{
dbe986b9 259 Cname result_obj = CnameCache[hostname];
ad83004d 260 GlobalLogger.info() << "DnsCache: request CNAME for " << hostname
23f51766
CH
261 << " --> \"" << result_obj.Host << "\"";
262 if (result_obj.Host.empty())
263 return result_obj;
264
265 else if (check_up_to_date)
dbe986b9 266 {
23f51766 267 if (result_obj.Ttl.get_updated_value() > DnsMaster::get_instance()
dbe986b9 268 ->get_resolved_ip_ttl_threshold())
23f51766 269 return result_obj;
dbe986b9
CH
270 else
271 {
23f51766
CH
272 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
273 return Cname(); // same as if there was no cname for hostname
dbe986b9
CH
274 }
275 }
276 else
23f51766 277 return result_obj;
ad83004d
CH
278}
279
dbe986b9
CH
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
282HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
283 const bool check_up_to_date)
e18c1337
CH
284{
285 std::string current_host = hostname;
23f51766 286 Cname current_cname;
dbe986b9
CH
287 HostAddressVec result = get_ips(current_host);
288 int n_recursions = 0;
23f51766 289 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
e18c1337
CH
290 while ( result.empty() )
291 {
23f51766
CH
292 current_cname = get_cname(current_host, check_up_to_date);
293 current_host = current_cname.Host;
e18c1337
CH
294 if (current_host.empty())
295 break;
dbe986b9
CH
296 else if (++n_recursions >= Config::MaxRetrievalRecursions)
297 {
298 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
299 << n_recursions << " in recursive IP retrieval!";
300 break;
301 }
e18c1337 302 else
23f51766
CH
303 {
304 min_cname_ttl = min(min_cname_ttl, current_cname.Ttl.get_value());
dbe986b9 305 result = get_ips(current_host, check_up_to_date);
23f51766
CH
306 }
307 }
308
309 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
310 << result.size() << "-list after " << n_recursions
311 << " recursions";
312
313 // adjust ttl to min of ttl and min_cname_ttl
314 if (n_recursions > 0)
315 {
316 TimeToLive cname_ttl(min_cname_ttl);
317
318 BOOST_FOREACH( HostAddress &addr, result )
319 {
320 if (addr.get_ttl().get_value() > min_cname_ttl)
321 addr.set_ttl(cname_ttl);
322 }
e18c1337 323 }
23f51766 324
e18c1337
CH
325 return result;
326}
ad83004d 327
23f51766
CH
328/**
329 * from a list of CNAMEs find the first one that is out of date
330 *
331 * required in ResolverBase::get_skipper
332 *
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
339 */
340std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
341 const uint32_t ttl_thresh)
342{
343 std::string up_to_date_host = hostname;
344 Cname cname;
345 int n_recursions = 0;
346 while (true)
347 {
348 if (++n_recursions >= Config::MaxRetrievalRecursions)
349 {
350 GlobalLogger.warning() << "DnsCache: reached recursion limit of "
351 << n_recursions << " in search of outdated CNAMEs!";
352 break;
353 }
354
355 cname = get_cname(up_to_date_host);
356 if (cname.Host.empty())
357 // reached end of cname list
358 break;
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;
362 else
363 // cname is out of date --> return that
364 break;
365 }
366 return up_to_date_host;
367}
368
96779587
CH
369// (created using vim -- the world's best text editor)
370