Commit | Line | Data |
---|---|---|
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 |
41 | using boost::bind; |
42 | using boost::posix_time::seconds; | |
43 | using I2n::Logger::GlobalLogger; | |
44 | ||
45 | namespace Config | |
46 | { | |
47 | int SaveTimerSeconds = 60; | |
dbe986b9 | 48 | int MaxRetrievalRecursions = 10; |
96779587 CH |
49 | } |
50 | ||
96779587 CH |
51 | DnsCache::DnsCache(const IoServiceItem &io_serv, |
52 | const std::string &cache_file) | |
ad83004d CH |
53 | : IpCache() |
54 | , CnameCache() | |
96779587 CH |
55 | , SaveTimer( *io_serv ) |
56 | , CacheFile( cache_file ) | |
57 | , HasChanged( false ) | |
58 | { | |
59 | // load cache from file | |
60 | load_from_cachefile(); | |
61 | ||
62 | // schedule next save | |
63 | (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) ); | |
64 | SaveTimer.async_wait( bind( &DnsCache::schedule_save, this, | |
65 | boost::asio::placeholders::error ) ); | |
66 | } | |
67 | ||
68 | ||
69 | DnsCache::~DnsCache() | |
70 | { | |
e91538f0 CH |
71 | GlobalLogger.info() << "Dns Cache is being destructed"; |
72 | ||
96779587 CH |
73 | // save one last time without re-scheduling the next save |
74 | save_to_cachefile(); | |
75 | ||
76 | // cancel save timer | |
77 | SaveTimer.cancel(); | |
78 | } | |
79 | ||
80 | ||
81 | void DnsCache::schedule_save(const boost::system::error_code &error) | |
82 | { | |
83 | // just in case: ensure SaveTimer is cancelled | |
84 | SaveTimer.cancel(); // (will do nothing if already expired/cancelled) | |
85 | ||
86 | if ( error == boost::asio::error::operation_aborted ) // cancelled | |
87 | { | |
ad83004d | 88 | GlobalLogger.error() << "DnsCache: SaveTimer was cancelled " |
96779587 CH |
89 | << "--> no save and no re-schedule of saving!"; |
90 | return; | |
91 | } | |
92 | else if (error) | |
93 | { | |
ad83004d | 94 | GlobalLogger.error() << "DnsCache: Received error " << error |
96779587 CH |
95 | << " in schedule_save " |
96 | << "--> no save now but re-schedule saving"; | |
97 | } | |
98 | else | |
99 | save_to_cachefile(); | |
100 | ||
101 | // schedule next save | |
102 | (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) ); | |
103 | SaveTimer.async_wait( bind( &DnsCache::schedule_save, this, | |
104 | boost::asio::placeholders::error ) ); | |
105 | } | |
106 | ||
107 | void DnsCache::save_to_cachefile() | |
108 | { | |
109 | if (!HasChanged) | |
110 | { | |
ad83004d | 111 | GlobalLogger.info() << "DnsCache: skip saving because has not changed"; |
96779587 CH |
112 | return; |
113 | } | |
946356e1 CH |
114 | else if (CacheFile.empty()) |
115 | { | |
116 | GlobalLogger.warning() | |
ad83004d | 117 | << "DnsCache: skip saving because file name empty!"; |
946356e1 CH |
118 | return; |
119 | } | |
96779587 | 120 | |
946356e1 CH |
121 | try |
122 | { | |
123 | std::ofstream ofs( CacheFile.c_str() ); | |
124 | boost::archive::xml_oarchive oa(ofs); | |
ad83004d CH |
125 | oa << boost::serialization::make_nvp("IpCache", IpCache); |
126 | oa << boost::serialization::make_nvp("CnameCache", CnameCache); | |
127 | GlobalLogger.info() << "DnsCache: saved to cache file " << CacheFile; | |
946356e1 CH |
128 | |
129 | HasChanged = false; | |
130 | } | |
131 | catch (std::exception &exc) | |
132 | { | |
133 | GlobalLogger.warning() << "Saving failed: " << exc.what(); | |
134 | } | |
96779587 CH |
135 | } |
136 | ||
137 | ||
138 | void DnsCache::load_from_cachefile() | |
139 | { | |
946356e1 CH |
140 | if (CacheFile.empty()) |
141 | { | |
142 | GlobalLogger.warning() | |
ad83004d | 143 | << "DnsCache: cannot load because cache file name is empty!"; |
946356e1 CH |
144 | return; |
145 | } | |
146 | else if ( !I2n::file_exists(CacheFile) ) | |
147 | { | |
ad83004d | 148 | GlobalLogger.warning() << "DnsCache: cannot load because cache file " |
946356e1 CH |
149 | << CacheFile << " does not exist!"; |
150 | return; | |
151 | } | |
152 | try | |
153 | { | |
154 | HostAddressVec cache; | |
155 | ||
156 | std::ifstream ifs( CacheFile.c_str() ); | |
157 | boost::archive::xml_iarchive ia(ifs); | |
158 | ||
ad83004d CH |
159 | ia >> boost::serialization::make_nvp("IpCache", cache); |
160 | ia >> boost::serialization::make_nvp("CnameCache", cache); | |
161 | GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile; | |
946356e1 CH |
162 | } |
163 | catch (boost::archive::archive_exception &exc) | |
164 | { | |
ad83004d | 165 | GlobalLogger.warning() << "DnsCache: archive exception loading from " |
946356e1 CH |
166 | << CacheFile << ": " << exc.what(); |
167 | } | |
168 | catch (std::exception &exc) | |
169 | { | |
ad83004d | 170 | GlobalLogger.warning() << "DnsCache: exception while loading from " |
946356e1 CH |
171 | << CacheFile << ": " << exc.what(); |
172 | } | |
96779587 CH |
173 | } |
174 | ||
96779587 | 175 | void DnsCache::update(const std::string &hostname, |
4e7b6ff9 | 176 | const HostAddressVec &new_data) |
96779587 | 177 | { |
ad83004d | 178 | GlobalLogger.info() << "DnsCache: update IPs for " << hostname |
96779587 | 179 | << " to " << new_data.size() << "-list"; |
ad83004d | 180 | IpCache[hostname] = new_data; |
96779587 CH |
181 | HasChanged = true; |
182 | } | |
183 | ||
184 | ||
ad83004d | 185 | void DnsCache::update(const std::string &hostname, |
dbe986b9 | 186 | const Cname &cname) |
ad83004d CH |
187 | { |
188 | GlobalLogger.info() << "DnsCache: update CNAME for " << hostname | |
dbe986b9 | 189 | << " to " << cname.first; |
ad83004d CH |
190 | CnameCache[hostname] = cname; |
191 | HasChanged = true; | |
192 | } | |
193 | ||
194 | ||
dbe986b9 | 195 | void DnsCache::update(const std::string &hostname, |
ad83004d CH |
196 | const uint32_t new_ttl) |
197 | { | |
198 | GlobalLogger.info() << "DnsCache: ensure TTL for IPs for " << hostname | |
e18c1337 | 199 | << " is below " << new_ttl << "s"; |
ad83004d | 200 | HostAddressVec ips = IpCache[hostname]; |
ad83004d CH |
201 | TimeToLive current_ttl; |
202 | BOOST_FOREACH( HostAddress &addr, ips ) | |
203 | { | |
204 | current_ttl = addr.get_ttl(); | |
205 | if (current_ttl.get_value() > new_ttl) | |
206 | { | |
207 | current_ttl.set_value(new_ttl); | |
208 | addr.set_ttl(current_ttl); | |
209 | HasChanged = true; | |
210 | } | |
211 | } | |
e18c1337 | 212 | IpCache[hostname] = ips; |
ad83004d CH |
213 | } |
214 | ||
215 | ||
dbe986b9 CH |
216 | HostAddressVec DnsCache::get_ips(const std::string &hostname, |
217 | const bool check_up_to_date) | |
96779587 | 218 | { |
dbe986b9 CH |
219 | HostAddressVec result = IpCache[hostname]; |
220 | if (check_up_to_date) | |
221 | { | |
222 | HostAddressVec result_up_to_date; | |
223 | int threshold = DnsMaster::get_instance() | |
224 | ->get_resolved_ip_ttl_threshold(); | |
225 | BOOST_FOREACH( const HostAddress &addr, result ) | |
226 | { | |
227 | if (addr.get_ttl().get_updated_value() > threshold) | |
228 | result_up_to_date.push_back(addr); | |
229 | } | |
230 | GlobalLogger.debug() << "DnsCache: From cached list of size " | |
231 | << result.size() << " return " << result_up_to_date.size() | |
232 | << " since rest out of date"; | |
233 | result = result_up_to_date; | |
234 | } | |
ad83004d | 235 | GlobalLogger.info() << "DnsCache: request IPs for " << hostname |
dbe986b9 CH |
236 | << " --> " << result.size() << "-list"; |
237 | return result; | |
96779587 CH |
238 | } |
239 | ||
dbe986b9 CH |
240 | std::string DnsCache::get_cname(const std::string &hostname, |
241 | const bool check_up_to_date) | |
ad83004d | 242 | { |
dbe986b9 | 243 | Cname result_obj = CnameCache[hostname]; |
ad83004d | 244 | GlobalLogger.info() << "DnsCache: request CNAME for " << hostname |
dbe986b9 CH |
245 | << " --> \"" << result_obj.first << "\""; |
246 | if (check_up_to_date) | |
247 | { | |
248 | if (result_obj.second.get_updated_value() > DnsMaster::get_instance() | |
249 | ->get_resolved_ip_ttl_threshold()) | |
250 | return result_obj.first; | |
251 | else | |
252 | { | |
253 | GlobalLogger.debug() << "DnsCache: Cname is out of date"; | |
254 | return ""; | |
255 | } | |
256 | } | |
257 | else | |
258 | return result_obj.first; | |
ad83004d CH |
259 | } |
260 | ||
dbe986b9 CH |
261 | // underlying assumption in this function: for a hostname, the cache has either |
262 | // a list of IPs saved or a cname saved, but never both | |
263 | HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname, | |
264 | const bool check_up_to_date) | |
e18c1337 CH |
265 | { |
266 | std::string current_host = hostname; | |
dbe986b9 CH |
267 | HostAddressVec result = get_ips(current_host); |
268 | int n_recursions = 0; | |
e18c1337 CH |
269 | while ( result.empty() ) |
270 | { | |
dbe986b9 | 271 | current_host = get_cname(current_host, check_up_to_date); |
e18c1337 CH |
272 | if (current_host.empty()) |
273 | break; | |
dbe986b9 CH |
274 | else if (++n_recursions >= Config::MaxRetrievalRecursions) |
275 | { | |
276 | GlobalLogger.warning() << "DnsCache: reached recursion limit of " | |
277 | << n_recursions << " in recursive IP retrieval!"; | |
278 | break; | |
279 | } | |
e18c1337 | 280 | else |
dbe986b9 | 281 | result = get_ips(current_host, check_up_to_date); |
e18c1337 CH |
282 | } |
283 | return result; | |
284 | } | |
ad83004d | 285 | |
96779587 CH |
286 | // (created using vim -- the world's best text editor) |
287 |