f0652237ae3e7c0a23a752ef4e055378a34bdea6
[pingcheck] / src / dns / dnscache.cpp
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
23 #include "dns/dnscache.h"
24
25 #include <fstream>
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>
38
39 using boost::bind;
40 using boost::posix_time::seconds;
41 using I2n::Logger::GlobalLogger;
42
43 namespace Config
44 {
45     int SaveTimerSeconds = 60;
46 }
47
48
49
50 DnsCache::DnsCache(const IoServiceItem &io_serv,
51                    const std::string &cache_file)
52     : IpCache()
53     , CnameCache()
54     , SaveTimer( *io_serv )
55     , CacheFile( cache_file )
56     , HasChanged( false )
57 {
58     // load cache from file
59     load_from_cachefile();
60
61     // schedule next save
62     (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
63     SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
64                                 boost::asio::placeholders::error ) );
65 }
66
67
68 DnsCache::~DnsCache()
69 {
70     GlobalLogger.info() << "Dns Cache is being destructed";
71
72     // save one last time without re-scheduling the next save
73     save_to_cachefile();
74
75     // cancel save timer
76     SaveTimer.cancel();
77 }
78
79
80 void DnsCache::schedule_save(const boost::system::error_code &error)
81 {
82     // just in case: ensure SaveTimer is cancelled
83     SaveTimer.cancel();  // (will do nothing if already expired/cancelled)
84
85     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
86     {
87         GlobalLogger.error() << "DnsCache: SaveTimer was cancelled "
88                              << "--> no save and no re-schedule of saving!";
89         return;
90     }
91     else if (error)
92     {
93         GlobalLogger.error() << "DnsCache: Received error " << error
94                              << " in schedule_save "
95                              << "--> no save now but re-schedule saving";
96     }
97     else
98         save_to_cachefile();
99
100     // schedule next save
101     (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) );
102     SaveTimer.async_wait( bind( &DnsCache::schedule_save, this,
103                                 boost::asio::placeholders::error ) );
104 }
105
106 void DnsCache::save_to_cachefile()
107 {
108     if (!HasChanged)
109     {
110         GlobalLogger.info() << "DnsCache: skip saving because has not changed";
111         return;
112     }
113     else if (CacheFile.empty())
114     {
115         GlobalLogger.warning()
116                            << "DnsCache: skip saving because file name empty!";
117         return;
118     }
119
120     try
121     {
122         std::ofstream ofs( CacheFile.c_str() );
123         boost::archive::xml_oarchive oa(ofs);
124         oa << boost::serialization::make_nvp("IpCache", IpCache);
125         oa << boost::serialization::make_nvp("CnameCache", CnameCache);
126         GlobalLogger.info() << "DnsCache: saved to cache file " << CacheFile;
127
128         HasChanged = false;
129     }
130     catch (std::exception &exc)
131     {
132         GlobalLogger.warning() << "Saving failed: " << exc.what();
133     }
134 }
135
136
137 void DnsCache::load_from_cachefile()
138 {
139     if (CacheFile.empty())
140     {
141         GlobalLogger.warning()
142                   << "DnsCache: cannot load because cache file name is empty!";
143         return;
144     }
145     else if ( !I2n::file_exists(CacheFile) )
146     {
147         GlobalLogger.warning() << "DnsCache: cannot load because cache file "
148                                << CacheFile << " does not exist!";
149         return;
150     }
151     try
152     {
153         HostAddressVec cache;
154
155         std::ifstream ifs( CacheFile.c_str() );
156         boost::archive::xml_iarchive ia(ifs);
157
158         ia >> boost::serialization::make_nvp("IpCache", cache);
159         ia >> boost::serialization::make_nvp("CnameCache", cache);
160         GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile;
161     }
162     catch (boost::archive::archive_exception &exc)
163     {
164         GlobalLogger.warning() << "DnsCache: archive exception loading from "
165                                << CacheFile << ": " << exc.what();
166     }
167     catch (std::exception &exc)
168     {
169         GlobalLogger.warning() << "DnsCache: exception while loading from "
170                                << CacheFile << ": " << exc.what();
171     }
172 }
173
174 void DnsCache::update(const std::string &hostname,
175                       const HostAddressVec &new_data)
176 {
177     GlobalLogger.info() << "DnsCache: update IPs for " << hostname
178                         << " to " << new_data.size() << "-list";
179     IpCache[hostname] = new_data;
180     HasChanged = true;
181 }
182
183
184 void DnsCache::update(const std::string &hostname,
185                       const std::string &cname)
186 {
187     GlobalLogger.info() << "DnsCache: update CNAME for " << hostname
188                         << " to " << cname;
189     CnameCache[hostname] = cname;
190     HasChanged = true;
191 }
192
193
194 void DnsCache::update_ttl(const std::string &hostname,
195                           const uint32_t new_ttl)
196 {
197     GlobalLogger.info() << "DnsCache: ensure TTL for IPs for " << hostname
198                         << " is below " << new_ttl << "s";
199     HostAddressVec ips = IpCache[hostname];
200     TimeToLive current_ttl;
201     BOOST_FOREACH( HostAddress &addr, ips )
202     {
203         current_ttl = addr.get_ttl();
204         if (current_ttl.get_value() > new_ttl)
205         {
206             current_ttl.set_value(new_ttl);
207             addr.set_ttl(current_ttl);
208             HasChanged = true;
209         }
210     }
211     IpCache[hostname] = ips;
212 }
213
214
215 HostAddressVec& DnsCache::get_ips(const std::string &hostname)
216 {
217     GlobalLogger.info() << "DnsCache: request IPs for " << hostname
218                         << " --> " << IpCache[hostname].size() << "-list";
219     return IpCache[hostname];
220 }
221
222 std::string& DnsCache::get_cname(const std::string &hostname)
223 {
224     GlobalLogger.info() << "DnsCache: request CNAME for " << hostname
225                         << " --> \"" << CnameCache[hostname] << "\"";
226     return CnameCache[hostname];
227 }
228
229 HostAddressVec& DnsCache::get_ips_recursive(const std::string &hostname)
230 {
231     std::string current_host = hostname;
232     HostAddressVec& result = get_ips(current_host);
233     while ( result.empty() )
234     {
235         current_host = get_cname(current_host);
236         if (current_host.empty())
237             break;
238         else
239             result = get_ips(current_host);
240     }
241     return result;
242 }
243
244 // (created using vim -- the world's best text editor)
245