moved 3 lines of Cname constructors from dnscache.cpp to cname.h
[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
f636ba88 25#include <sstream>
946356e1 26#include <fstream>
96779587 27#include <logfunc.hpp>
946356e1 28#include <filefunc.hxx> // I2n::file_exists
e18c1337 29#include <boost/foreach.hpp>
96779587 30#include <boost/bind.hpp>
96779587 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
8d26221d 45
96779587
CH
46namespace Config
47{
77319ba8
CH
48 const int SAVE_TIMER_SECONDS = 60;
49 const int CACHE_TIME_WARP_THRESH_HOURS = 24;
4e691eb0 50 const int CACHE_REMOVE_OUTDATED_DAYS = 60;
96779587
CH
51}
52
8d26221d 53
f833126b
CH
54// -----------------------------------------------------------------------------
55// DNS Cache constructor / destructor
56// -----------------------------------------------------------------------------
57
8d26221d
CH
58const string DnsCache::DoNotUseCacheFile = "do not use cache file!";
59
96779587 60DnsCache::DnsCache(const IoServiceItem &io_serv,
f833126b
CH
61 const std::string &cache_file,
62 const uint32_t min_time_between_resolves)
ad83004d
CH
63 : IpCache()
64 , CnameCache()
96779587
CH
65 , SaveTimer( *io_serv )
66 , CacheFile( cache_file )
67 , HasChanged( false )
f833126b 68 , MinTimeBetweenResolves( min_time_between_resolves )
96779587
CH
69{
70 // load cache from file
71 load_from_cachefile();
72
73 // schedule next save
77319ba8 74 (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
96779587
CH
75 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
76 boost::asio::placeholders::error ) );
77}
78
79
80DnsCache::~DnsCache()
81{
26b0f687 82 GlobalLogger.info() << "DnsCache: being destructed";
e91538f0 83
96779587
CH
84 // save one last time without re-scheduling the next save
85 save_to_cachefile();
86
87 // cancel save timer
88 SaveTimer.cancel();
89}
90
91
f833126b
CH
92// -----------------------------------------------------------------------------
93// LOAD / SAVE
94// -----------------------------------------------------------------------------
95
96779587
CH
96void DnsCache::schedule_save(const boost::system::error_code &error)
97{
96779587
CH
98 if ( error == boost::asio::error::operation_aborted ) // cancelled
99 {
49b82a1d
CH
100 GlobalLogger.info() << "DnsCache: SaveTimer was cancelled "
101 << "--> no save and no re-schedule of saving!";
96779587
CH
102 return;
103 }
104 else if (error)
105 {
49b82a1d
CH
106 GlobalLogger.info() << "DnsCache: Received error " << error
107 << " in schedule_save "
108 << "--> no save now but re-schedule saving";
96779587
CH
109 }
110 else
111 save_to_cachefile();
112
113 // schedule next save
77319ba8 114 (void) SaveTimer.expires_from_now( seconds( Config::SAVE_TIMER_SECONDS ) );
96779587
CH
115 SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
116 boost::asio::placeholders::error ) );
117}
118
119void DnsCache::save_to_cachefile()
120{
121 if (!HasChanged)
ad83004d 122 GlobalLogger.info() << "DnsCache: skip saving because has not changed";
946356e1 123 else if (CacheFile.empty())
49b82a1d 124 GlobalLogger.info()
ad83004d 125 << "DnsCache: skip saving because file name empty!";
8d26221d 126 else if (CacheFile == DoNotUseCacheFile)
26b0f687
CH
127 GlobalLogger.info() << "DnsCache: configured not to use cache file";
128 else
946356e1 129 {
26b0f687
CH
130 try
131 {
77319ba8
CH
132 // remember time of save
133 std::string cache_save_time_str = boost::posix_time::to_iso_string(
134 boost::posix_time::second_clock::universal_time() );
135
26b0f687
CH
136 std::ofstream ofs( CacheFile.c_str() );
137 boost::archive::xml_oarchive oa(ofs);
26b0f687
CH
138 oa & BOOST_SERIALIZATION_NVP(IpCache);
139 oa & BOOST_SERIALIZATION_NVP(CnameCache);
77319ba8 140 oa & BOOST_SERIALIZATION_NVP(cache_save_time_str);
8d26221d
CH
141 GlobalLogger.info() << "DnsCache: saved to cache file "
142 << CacheFile;
26b0f687
CH
143
144 HasChanged = false;
145 }
146 catch (std::exception &exc)
147 {
c1abff61 148 GlobalLogger.warning() << "DnsCache: Saving failed: " << exc.what();
26b0f687 149 }
946356e1 150 }
96779587
CH
151}
152
96779587
CH
153void DnsCache::load_from_cachefile()
154{
946356e1 155 if (CacheFile.empty())
49b82a1d 156 GlobalLogger.info()
8d26221d
CH
157 << "DnsCache: cannot load because cache file name is empty!";
158 else if (CacheFile == DoNotUseCacheFile)
26b0f687 159 GlobalLogger.info() << "DnsCache: configured not to use cache file";
946356e1 160 else if ( !I2n::file_exists(CacheFile) )
ad83004d 161 GlobalLogger.warning() << "DnsCache: cannot load because cache file "
946356e1 162 << CacheFile << " does not exist!";
26b0f687 163 else
946356e1 164 {
26b0f687
CH
165 try
166 {
167 std::ifstream ifs( CacheFile.c_str() );
168 boost::archive::xml_iarchive ia(ifs);
946356e1 169
77319ba8
CH
170 std::string cache_save_time_str;
171
26b0f687
CH
172 ia & BOOST_SERIALIZATION_NVP(IpCache);
173 ia & BOOST_SERIALIZATION_NVP(CnameCache);
77319ba8
CH
174 ia & BOOST_SERIALIZATION_NVP(cache_save_time_str);
175
176 boost::posix_time::ptime cache_save_time
177 = boost::posix_time::from_iso_string(cache_save_time_str);
26b0f687 178 GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
77319ba8
CH
179
180 check_timestamps(cache_save_time);
26b0f687
CH
181 }
182 catch (boost::archive::archive_exception &exc)
183 {
8d26221d
CH
184 GlobalLogger.warning()
185 << "DnsCache: archive exception loading from " << CacheFile
186 << ": " << exc.what();
26b0f687
CH
187 }
188 catch (std::exception &exc)
189 {
190 GlobalLogger.warning() << "DnsCache: exception while loading from "
191 << CacheFile << ": " << exc.what();
192 }
946356e1 193 }
96779587
CH
194}
195
26b0f687 196
77319ba8
CH
197/**
198 * @brief check that loaded cache really is from the past
199 *
200 * Added this to avoid trouble in case the system time is changed into the past.
201 * In that cast would have TTLs here in cache that are valid much too long.
202 *
203 * Therefore check SaveCacheTime and if that is in the future, set all TTLs to 0
204 *
205 * @returns true if had to re-set timestamps
206 */
207bool DnsCache::check_timestamps(const boost::posix_time::ptime &cache_save_time)
208{
209 // check if CacheSaveTime is in the future
210 boost::posix_time::ptime now
211 = boost::posix_time::second_clock::universal_time()
212 + boost::posix_time::hours( Config::CACHE_TIME_WARP_THRESH_HOURS );
213 if (now > cache_save_time)
214 { // Cache was saved in the past -- everything alright
215 return false;
216 }
217
218 GlobalLogger.warning() << "DnsCache: loaded cache from the future (saved "
219 << cache_save_time << ")! Resetting all TTLs to 0.";
220
221 // reset TTLs in IP cache
222 BOOST_FOREACH( const ip_map_type::value_type key_and_ip, IpCache )
223 {
224 HostAddressVec addr_reset;
225 BOOST_FOREACH( HostAddress address, key_and_ip.second )
226 addr_reset.push_back( HostAddress( address.get_ip(), 0 ) );
227 IpCache[key_and_ip.first] = addr_reset;
228 }
229
230 // reset TTLs in CNAME cache
231 BOOST_FOREACH( const cname_map_type::value_type key_and_cname, CnameCache )
232 CnameCache[key_and_cname.first] = Cname( key_and_cname.second.Host,
233 TimeToLive(0) );
234
235 return true;
236}
237
238
f833126b
CH
239// -----------------------------------------------------------------------------
240// UPDATE
241// -----------------------------------------------------------------------------
242
8f00b3df
CH
243/*
244 * warn if hostname is empty and remove trailing dot
245 * also warn if protocol is neither IPv4 nor IPv6
246 */
247ip_map_key_type DnsCache::key_for_ips(const std::string &hostname,
248 const DnsIpProtocol &protocol) const
96779587 249{
26b0f687
CH
250 if (hostname.empty())
251 {
49b82a1d 252 GlobalLogger.info() << "DnsCache: empty host!";
8f00b3df
CH
253 return ip_map_key_type("", DNS_IPALL);
254 }
255 if (protocol == DNS_IPALL)
256 {
257 GlobalLogger.info() << "DnsCache: neither IPv4 nor v6!";
258 return ip_map_key_type("", DNS_IPALL);
26b0f687
CH
259 }
260
261 // check whether last character is a dot
262 if (hostname.rfind('.') == hostname.length()-1)
8f00b3df
CH
263 return ip_map_key_type( hostname.substr(0, hostname.length()-1),
264 protocol );
26b0f687 265 else
8f00b3df
CH
266 return ip_map_key_type( hostname,
267 protocol );
96779587
CH
268}
269
270
ad83004d 271void DnsCache::update(const std::string &hostname,
8f00b3df 272 const DnsIpProtocol &protocol,
26b0f687 273 const HostAddressVec &new_ips)
ad83004d 274{
8f00b3df
CH
275 // check for valid input arguments
276 ip_map_key_type key = key_for_ips(hostname, protocol);
277 if ( key.first.empty() )
278 return;
279
280 // ensure that there is never IP and CNAME for the same host
26b0f687 281 if ( !get_cname(hostname).Host.empty() )
8f00b3df
CH
282 {
283 GlobalLogger.info() << "DnsCache: Saving IPs for " << key.first
26b0f687
CH
284 << " removes CNAME to " << get_cname(hostname).Host << "!";
285 update(hostname, Cname()); // overwrite with "empty" cname
286 }
8f00b3df 287
f833126b
CH
288 // ensure min ttl of MinTimeBetweenResolves
289 HostAddressVec ips_checked;
290 BOOST_FOREACH( const HostAddress &addr, new_ips )
291 {
292 if ( addr.get_ttl().get_value() < MinTimeBetweenResolves )
293 {
294 GlobalLogger.info() << "DnsCache: Correcting TTL of IP for "
8f00b3df 295 << key.first << " from " << addr.get_ttl().get_value() << "s to "
f833126b
CH
296 << MinTimeBetweenResolves << "s because was too short";
297 ips_checked.push_back( HostAddress( addr.get_ip(),
298 MinTimeBetweenResolves) );
299 }
300 else
301 ips_checked.push_back(addr);
302 }
303
8f00b3df 304 // write IPs into one log line
f636ba88 305 stringstream log_temp;
8f00b3df 306 log_temp << "DnsCache: update IPs for " << key.first << " to "
f636ba88
CH
307 << ips_checked.size() << "-list: ";
308 BOOST_FOREACH( const HostAddress &ip, ips_checked )
309 log_temp << ip.get_ip() << ", ";
310 GlobalLogger.notice() << log_temp.str();
f833126b
CH
311
312 IpCache[key] = ips_checked;
ad83004d
CH
313 HasChanged = true;
314}
315
316
8f00b3df
CH
317/*
318 * warn if hostname is empty and remove trailing dot
319 */
320cname_map_key_type DnsCache::key_for_cname(const std::string &hostname) const
321{
322 if (hostname.empty())
323 {
324 GlobalLogger.info() << "DnsCache: empty host!";
325 return "";
326 }
327
328 // check whether last character is a dot
329 if (hostname.rfind('.') == hostname.length()-1)
330 return hostname.substr(0, hostname.length()-1);
331 else
332 return hostname;
333}
334
335
dbe986b9 336void DnsCache::update(const std::string &hostname,
26b0f687 337 const Cname &cname)
ad83004d 338{
8f00b3df
CH
339 // check for valid input arguments
340 cname_map_key_type key = key_for_cname(hostname);
341 if ( key.empty() )
342 return;
343
344 // ensure that there is never IP and CNAME for the same host
345 int n_ips = get_ips(hostname, DNS_IPv4).size()
346 + get_ips(hostname, DNS_IPv6).size();
347 if ( n_ips > 0 )
348 {
349 GlobalLogger.info() << "DnsCache: Saving IPs for " << key
350 << " removes CNAME to " << get_cname(hostname).Host << "!";
49b82a1d 351 GlobalLogger.info() << "DnsCache: Saving CNAME for " << key
8f00b3df
CH
352 << " removes " << n_ips << " IPs for same host!";
353 update(hostname, DNS_IPv4, HostAddressVec());
354 update(hostname, DNS_IPv6, HostAddressVec());
ad83004d 355 }
26b0f687 356
8f00b3df
CH
357 // remove possible trailing dot from cname's target host
358 Cname to_save = Cname(key_for_cname(cname.Host), // implicit cast to string
26b0f687
CH
359 cname.Ttl);
360
f833126b
CH
361 // ensure min ttl of MinTimeBetweenResolves
362 if ( to_save.Ttl.get_value() < MinTimeBetweenResolves )
363 {
364 GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of "
8f00b3df 365 << key << " from " << to_save.Ttl.get_value() << "s to "
f833126b
CH
366 << MinTimeBetweenResolves << "s because was too short";
367 to_save.Ttl = TimeToLive(MinTimeBetweenResolves);
368 }
369
f636ba88 370 GlobalLogger.notice() << "DnsCache: update CNAME for " << key
8f00b3df 371 << " to " << to_save.Host;
26b0f687
CH
372 CnameCache[key] = to_save;
373 HasChanged = true;
ad83004d
CH
374}
375
376
f833126b
CH
377// -----------------------------------------------------------------------------
378// RETRIEVAL
379// -----------------------------------------------------------------------------
380
23f51766
CH
381/**
382 * @returns empty list if no (up to date) ips for hostname in cache
383 */
dbe986b9 384HostAddressVec DnsCache::get_ips(const std::string &hostname,
8f00b3df 385 const DnsIpProtocol &protocol,
dbe986b9 386 const bool check_up_to_date)
96779587 387{
8f00b3df 388 ip_map_key_type key = key_for_ips(hostname, protocol);
26b0f687 389 HostAddressVec result = IpCache[key];
dbe986b9
CH
390 if (check_up_to_date)
391 {
392 HostAddressVec result_up_to_date;
23f51766
CH
393 uint32_t threshold = static_cast<uint32_t>(
394 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() );
26b0f687 395 uint32_t updated_ttl;
dbe986b9
CH
396 BOOST_FOREACH( const HostAddress &addr, result )
397 {
26b0f687
CH
398 updated_ttl = addr.get_ttl().get_updated_value();
399 if (updated_ttl > threshold)
dbe986b9 400 result_up_to_date.push_back(addr);
26b0f687
CH
401 else
402 GlobalLogger.debug() << "DnsCache: do not return "
403 << addr.get_ip().to_string() << " since TTL "
404 << updated_ttl << "s is out of date (thresh="
405 << threshold << "s)";
dbe986b9 406 }
dbe986b9
CH
407 result = result_up_to_date;
408 }
8f00b3df 409 /*GlobalLogger.debug() << "DnsCache: request IPs for " << key.first
26b0f687
CH
410 << " --> " << result.size() << "-list";
411 BOOST_FOREACH( const HostAddress &addr, result )
412 GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string()
fd62d09f 413 << " (TTL " << addr.get_ttl().get_updated_value()
2a4dde8b 414 << "s)"; */
dbe986b9 415 return result;
96779587
CH
416}
417
23f51766
CH
418/**
419 * @returns empty cname if no (up to date cname) for hostname in cache
420 */
421Cname DnsCache::get_cname(const std::string &hostname,
422 const bool check_up_to_date)
ad83004d 423{
8f00b3df 424 cname_map_key_type key = key_for_cname(hostname);
26b0f687 425 Cname result_obj = CnameCache[key];
2a4dde8b 426 /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key
fd62d09f 427 << " --> \"" << result_obj.Host << "\" (TTL "
2a4dde8b 428 << result_obj.Ttl.get_updated_value() << "s)";*/
23f51766
CH
429 if (result_obj.Host.empty())
430 return result_obj;
431
432 else if (check_up_to_date)
dbe986b9 433 {
26b0f687
CH
434 if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>(
435 DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) )
23f51766 436 return result_obj;
dbe986b9
CH
437 else
438 {
23f51766
CH
439 GlobalLogger.debug() << "DnsCache: CNAME is out of date";
440 return Cname(); // same as if there was no cname for hostname
dbe986b9
CH
441 }
442 }
443 else
23f51766 444 return result_obj;
ad83004d
CH
445}
446
dbe986b9
CH
447// underlying assumption in this function: for a hostname, the cache has either
448// a list of IPs saved or a cname saved, but never both
449HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname,
8f00b3df 450 const DnsIpProtocol &protocol,
dbe986b9 451 const bool check_up_to_date)
e18c1337
CH
452{
453 std::string current_host = hostname;
23f51766 454 Cname current_cname;
8f00b3df 455 HostAddressVec result = get_ips(current_host, protocol, check_up_to_date);
dbe986b9 456 int n_recursions = 0;
23f51766 457 uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value
cd71d095
CH
458 int max_recursion_count = DnsMaster::get_instance()
459 ->get_max_recursion_count();
e18c1337
CH
460 while ( result.empty() )
461 {
23f51766 462 current_cname = get_cname(current_host, check_up_to_date);
8d26221d 463 if (current_cname.Host.empty())
8f00b3df
CH
464 break; // no ips (since result.empty()) and no cname
465 // --> will return empty result
8d26221d 466
8f00b3df 467 current_host = current_cname.Host;
cd71d095 468 if (++n_recursions >= max_recursion_count)
dbe986b9 469 {
49b82a1d
CH
470 GlobalLogger.info() << "DnsCache: reached recursion limit of "
471 << n_recursions << " in recursive IP retrieval of "
472 << hostname << "!";
dbe986b9
CH
473 break;
474 }
e18c1337 475 else
23f51766 476 {
fd62d09f
CH
477 min_cname_ttl = min(min_cname_ttl,
478 current_cname.Ttl.get_updated_value());
8f00b3df 479 result = get_ips(current_host, protocol, check_up_to_date);
23f51766
CH
480 }
481 }
482
483 GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in "
484 << result.size() << "-list after " << n_recursions
485 << " recursions";
486
487 // adjust ttl to min of ttl and min_cname_ttl
488 if (n_recursions > 0)
489 {
490 TimeToLive cname_ttl(min_cname_ttl);
491
492 BOOST_FOREACH( HostAddress &addr, result )
493 {
fd62d09f 494 if (addr.get_ttl().get_updated_value() > min_cname_ttl)
8d26221d 495 {
2a4dde8b 496 //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL";
23f51766 497 addr.set_ttl(cname_ttl);
8d26221d 498 }
23f51766 499 }
e18c1337 500 }
23f51766 501
e18c1337
CH
502 return result;
503}
ad83004d 504
23f51766 505/**
9d1b2726
CH
506 * from a list of CNAMEs find the first one that is out of date or empty
507 *
508 * returns the hostname that is out of date or empty if all CNAMEs are
509 * up-to-date
23f51766
CH
510 *
511 * required in ResolverBase::get_skipper
23f51766
CH
512 */
513std::string DnsCache::get_first_outdated_cname(const std::string &hostname,
514 const uint32_t ttl_thresh)
515{
8d26221d 516 std::string first_outdated = hostname;
23f51766
CH
517 Cname cname;
518 int n_recursions = 0;
cd71d095
CH
519 int max_recursion_count = DnsMaster::get_instance()
520 ->get_max_recursion_count();
23f51766
CH
521 while (true)
522 {
cd71d095 523 if (++n_recursions >= max_recursion_count)
23f51766 524 {
49b82a1d
CH
525 GlobalLogger.info() << "DnsCache: reached recursion limit of "
526 << n_recursions << " in search of outdated CNAMEs for "
527 << hostname << "!";
9d1b2726
CH
528 return first_outdated; // not really out of date but currently
529 } // our best guess
23f51766 530
8d26221d
CH
531 cname = get_cname(first_outdated);
532 if (cname.Host.empty())
9d1b2726
CH
533 // reached end of cname list --> everything was up-to-date
534 return "";
23f51766
CH
535 else if (cname.Ttl.get_updated_value() > ttl_thresh)
536 // cname is up to date --> continue looking
8d26221d 537 first_outdated = cname.Host;
23f51766 538 else
9d1b2726
CH
539 // cname is out of date --> return its target
540 return cname.Host;
23f51766 541 }
9d1b2726
CH
542 // reach this point only if cname chain does not end with an IP
543 // --> all are up-to-date
544 return "";
23f51766 545}
e638894d
CH
546
547std::string DnsCache::get_cname_chain_str(const std::string &hostname)
548{
549 std::stringstream temp;
550 temp << hostname;
551 std::string current_host = hostname;
552 Cname current_cname;
553 int n_recursions = 0;
554 int max_recursion_count = DnsMaster::get_instance()
555 ->get_max_recursion_count();
556 while (true)
557 {
558 if (n_recursions >= max_recursion_count)
559 {
560 temp << "...";
561 break;
562 }
563
564 current_cname = get_cname(current_host, false);
565 if (current_cname.Host.empty())
566 break;
567 else
568 {
569 current_host = current_cname.Host;
570 temp << "-->" << current_host;
a176d17a 571 ++n_recursions;
e638894d
CH
572 }
573 }
574 return temp.str();
575}