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 | ||
8d26221d | 45 | |
96779587 CH |
46 | namespace Config |
47 | { | |
48 | int SaveTimerSeconds = 60; | |
49 | } | |
50 | ||
f833126b CH |
51 | // ----------------------------------------------------------------------------- |
52 | // Cname | |
53 | // ----------------------------------------------------------------------------- | |
54 | ||
23f51766 CH |
55 | Cname::Cname() |
56 | : Host() | |
57 | , Ttl() | |
58 | {} | |
59 | ||
60 | Cname::Cname(const std::string &host, const uint32_t ttl) | |
61 | : Host( host ) | |
62 | , Ttl( ttl ) | |
63 | {} | |
64 | ||
26b0f687 CH |
65 | Cname::Cname(const std::string &host, const TimeToLive &ttl) |
66 | : Host( host ) | |
67 | , Ttl( ttl ) | |
68 | {} | |
69 | ||
8d26221d | 70 | |
f833126b CH |
71 | // ----------------------------------------------------------------------------- |
72 | // DNS Cache constructor / destructor | |
73 | // ----------------------------------------------------------------------------- | |
74 | ||
8d26221d CH |
75 | const string DnsCache::DoNotUseCacheFile = "do not use cache file!"; |
76 | ||
96779587 | 77 | DnsCache::DnsCache(const IoServiceItem &io_serv, |
f833126b CH |
78 | const std::string &cache_file, |
79 | const uint32_t min_time_between_resolves) | |
ad83004d CH |
80 | : IpCache() |
81 | , CnameCache() | |
96779587 CH |
82 | , SaveTimer( *io_serv ) |
83 | , CacheFile( cache_file ) | |
84 | , HasChanged( false ) | |
f833126b | 85 | , MinTimeBetweenResolves( min_time_between_resolves ) |
96779587 CH |
86 | { |
87 | // load cache from file | |
88 | load_from_cachefile(); | |
89 | ||
90 | // schedule next save | |
91 | (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) ); | |
92 | SaveTimer.async_wait( bind( &DnsCache::schedule_save, this, | |
93 | boost::asio::placeholders::error ) ); | |
94 | } | |
95 | ||
96 | ||
97 | DnsCache::~DnsCache() | |
98 | { | |
26b0f687 | 99 | GlobalLogger.info() << "DnsCache: being destructed"; |
e91538f0 | 100 | |
96779587 CH |
101 | // save one last time without re-scheduling the next save |
102 | save_to_cachefile(); | |
103 | ||
104 | // cancel save timer | |
105 | SaveTimer.cancel(); | |
106 | } | |
107 | ||
108 | ||
f833126b CH |
109 | // ----------------------------------------------------------------------------- |
110 | // LOAD / SAVE | |
111 | // ----------------------------------------------------------------------------- | |
112 | ||
96779587 CH |
113 | void DnsCache::schedule_save(const boost::system::error_code &error) |
114 | { | |
115 | // just in case: ensure SaveTimer is cancelled | |
116 | SaveTimer.cancel(); // (will do nothing if already expired/cancelled) | |
117 | ||
118 | if ( error == boost::asio::error::operation_aborted ) // cancelled | |
119 | { | |
ad83004d | 120 | GlobalLogger.error() << "DnsCache: SaveTimer was cancelled " |
96779587 CH |
121 | << "--> no save and no re-schedule of saving!"; |
122 | return; | |
123 | } | |
124 | else if (error) | |
125 | { | |
ad83004d | 126 | GlobalLogger.error() << "DnsCache: Received error " << error |
96779587 CH |
127 | << " in schedule_save " |
128 | << "--> no save now but re-schedule saving"; | |
129 | } | |
130 | else | |
131 | save_to_cachefile(); | |
132 | ||
133 | // schedule next save | |
134 | (void) SaveTimer.expires_from_now( seconds( Config::SaveTimerSeconds ) ); | |
135 | SaveTimer.async_wait( bind( &DnsCache::schedule_save, this, | |
136 | boost::asio::placeholders::error ) ); | |
137 | } | |
138 | ||
139 | void DnsCache::save_to_cachefile() | |
140 | { | |
141 | if (!HasChanged) | |
ad83004d | 142 | GlobalLogger.info() << "DnsCache: skip saving because has not changed"; |
946356e1 | 143 | else if (CacheFile.empty()) |
946356e1 | 144 | GlobalLogger.warning() |
ad83004d | 145 | << "DnsCache: skip saving because file name empty!"; |
8d26221d | 146 | else if (CacheFile == DoNotUseCacheFile) |
26b0f687 CH |
147 | GlobalLogger.info() << "DnsCache: configured not to use cache file"; |
148 | else | |
946356e1 | 149 | { |
26b0f687 CH |
150 | try |
151 | { | |
152 | std::ofstream ofs( CacheFile.c_str() ); | |
153 | boost::archive::xml_oarchive oa(ofs); | |
154 | //oa << boost::serialization::make_nvp("IpCache", IpCache); | |
155 | //oa << boost::serialization::make_nvp("CnameCache", CnameCache); | |
156 | oa & BOOST_SERIALIZATION_NVP(IpCache); | |
157 | oa & BOOST_SERIALIZATION_NVP(CnameCache); | |
8d26221d CH |
158 | GlobalLogger.info() << "DnsCache: saved to cache file " |
159 | << CacheFile; | |
26b0f687 CH |
160 | |
161 | HasChanged = false; | |
162 | } | |
163 | catch (std::exception &exc) | |
164 | { | |
c1abff61 | 165 | GlobalLogger.warning() << "DnsCache: Saving failed: " << exc.what(); |
26b0f687 | 166 | } |
946356e1 | 167 | } |
96779587 CH |
168 | } |
169 | ||
96779587 CH |
170 | void DnsCache::load_from_cachefile() |
171 | { | |
946356e1 | 172 | if (CacheFile.empty()) |
946356e1 | 173 | GlobalLogger.warning() |
8d26221d CH |
174 | << "DnsCache: cannot load because cache file name is empty!"; |
175 | else if (CacheFile == DoNotUseCacheFile) | |
26b0f687 | 176 | GlobalLogger.info() << "DnsCache: configured not to use cache file"; |
946356e1 | 177 | else if ( !I2n::file_exists(CacheFile) ) |
ad83004d | 178 | GlobalLogger.warning() << "DnsCache: cannot load because cache file " |
946356e1 | 179 | << CacheFile << " does not exist!"; |
26b0f687 | 180 | else |
946356e1 | 181 | { |
26b0f687 CH |
182 | try |
183 | { | |
184 | std::ifstream ifs( CacheFile.c_str() ); | |
185 | boost::archive::xml_iarchive ia(ifs); | |
946356e1 | 186 | |
26b0f687 CH |
187 | ia & BOOST_SERIALIZATION_NVP(IpCache); |
188 | ia & BOOST_SERIALIZATION_NVP(CnameCache); | |
189 | GlobalLogger.info() << "DnsCache: loaded from file " << CacheFile; | |
190 | } | |
191 | catch (boost::archive::archive_exception &exc) | |
192 | { | |
8d26221d CH |
193 | GlobalLogger.warning() |
194 | << "DnsCache: archive exception loading from " << CacheFile | |
195 | << ": " << exc.what(); | |
26b0f687 CH |
196 | } |
197 | catch (std::exception &exc) | |
198 | { | |
199 | GlobalLogger.warning() << "DnsCache: exception while loading from " | |
200 | << CacheFile << ": " << exc.what(); | |
201 | } | |
946356e1 | 202 | } |
96779587 CH |
203 | } |
204 | ||
26b0f687 | 205 | |
f833126b CH |
206 | // ----------------------------------------------------------------------------- |
207 | // UPDATE | |
208 | // ----------------------------------------------------------------------------- | |
209 | ||
26b0f687 CH |
210 | // warn if hostname is empty and remove trailing dot |
211 | std::string DnsCache::key_for_hostname(const std::string &hostname) const | |
96779587 | 212 | { |
26b0f687 CH |
213 | if (hostname.empty()) |
214 | { | |
215 | GlobalLogger.warning() << "DnsCache: empty host!"; | |
216 | return ""; | |
217 | } | |
218 | ||
219 | // check whether last character is a dot | |
220 | if (hostname.rfind('.') == hostname.length()-1) | |
221 | return hostname.substr(0, hostname.length()-1); | |
222 | else | |
223 | return hostname; | |
96779587 CH |
224 | } |
225 | ||
226 | ||
ad83004d | 227 | void DnsCache::update(const std::string &hostname, |
26b0f687 | 228 | const HostAddressVec &new_ips) |
ad83004d | 229 | { |
26b0f687 CH |
230 | std::string key = key_for_hostname(hostname); |
231 | if ( !get_cname(hostname).Host.empty() ) | |
232 | { // ensure that there is never IP and CNAME for the same host | |
233 | GlobalLogger.warning() << "DnsCache: Saving IPs for " << key | |
234 | << " removes CNAME to " << get_cname(hostname).Host << "!"; | |
235 | update(hostname, Cname()); // overwrite with "empty" cname | |
236 | } | |
f833126b CH |
237 | // ensure min ttl of MinTimeBetweenResolves |
238 | HostAddressVec ips_checked; | |
239 | BOOST_FOREACH( const HostAddress &addr, new_ips ) | |
240 | { | |
241 | if ( addr.get_ttl().get_value() < MinTimeBetweenResolves ) | |
242 | { | |
243 | GlobalLogger.info() << "DnsCache: Correcting TTL of IP for " | |
244 | << hostname << " from " << addr.get_ttl().get_value() << "s to " | |
245 | << MinTimeBetweenResolves << "s because was too short"; | |
246 | ips_checked.push_back( HostAddress( addr.get_ip(), | |
247 | MinTimeBetweenResolves) ); | |
248 | } | |
249 | else | |
250 | ips_checked.push_back(addr); | |
251 | } | |
252 | ||
2a4dde8b | 253 | GlobalLogger.info() << "DnsCache: update IPs for " << key |
f833126b CH |
254 | << " to " << ips_checked.size() << "-list"; |
255 | ||
256 | IpCache[key] = ips_checked; | |
ad83004d CH |
257 | HasChanged = true; |
258 | } | |
259 | ||
260 | ||
dbe986b9 | 261 | void DnsCache::update(const std::string &hostname, |
26b0f687 | 262 | const Cname &cname) |
ad83004d | 263 | { |
26b0f687 CH |
264 | std::string key = key_for_hostname(hostname); |
265 | if ( !get_ips(hostname).empty() ) | |
266 | { // ensure that there is never IP and CNAME for the same host | |
267 | GlobalLogger.warning() << "DnsCache: Saving CNAME for " << key | |
268 | << " removes " << get_ips(hostname).size() << " IPs for same host!"; | |
269 | update(hostname, HostAddressVec()); // overwrite with empty IP list | |
ad83004d | 270 | } |
26b0f687 CH |
271 | |
272 | // remove possible trailing dot from cname | |
273 | Cname to_save = Cname(key_for_hostname(cname.Host), | |
274 | cname.Ttl); | |
275 | ||
f833126b CH |
276 | // ensure min ttl of MinTimeBetweenResolves |
277 | if ( to_save.Ttl.get_value() < MinTimeBetweenResolves ) | |
278 | { | |
279 | GlobalLogger.info() << "DnsCache: Correcting TTL of CNAME of " | |
280 | << hostname << " from " << to_save.Ttl.get_value() << "s to " | |
281 | << MinTimeBetweenResolves << "s because was too short"; | |
282 | to_save.Ttl = TimeToLive(MinTimeBetweenResolves); | |
283 | } | |
284 | ||
26b0f687 CH |
285 | GlobalLogger.info() << "DnsCache: update CNAME for " << key |
286 | << " to " << to_save.Host; | |
287 | CnameCache[key] = to_save; | |
288 | HasChanged = true; | |
ad83004d CH |
289 | } |
290 | ||
291 | ||
f833126b CH |
292 | // ----------------------------------------------------------------------------- |
293 | // RETRIEVAL | |
294 | // ----------------------------------------------------------------------------- | |
295 | ||
23f51766 CH |
296 | /** |
297 | * @returns empty list if no (up to date) ips for hostname in cache | |
298 | */ | |
dbe986b9 CH |
299 | HostAddressVec DnsCache::get_ips(const std::string &hostname, |
300 | const bool check_up_to_date) | |
96779587 | 301 | { |
26b0f687 CH |
302 | std::string key = key_for_hostname(hostname); |
303 | HostAddressVec result = IpCache[key]; | |
dbe986b9 CH |
304 | if (check_up_to_date) |
305 | { | |
306 | HostAddressVec result_up_to_date; | |
23f51766 CH |
307 | uint32_t threshold = static_cast<uint32_t>( |
308 | DnsMaster::get_instance()->get_resolved_ip_ttl_threshold() ); | |
26b0f687 | 309 | uint32_t updated_ttl; |
dbe986b9 CH |
310 | BOOST_FOREACH( const HostAddress &addr, result ) |
311 | { | |
26b0f687 CH |
312 | updated_ttl = addr.get_ttl().get_updated_value(); |
313 | if (updated_ttl > threshold) | |
dbe986b9 | 314 | result_up_to_date.push_back(addr); |
26b0f687 CH |
315 | else |
316 | GlobalLogger.debug() << "DnsCache: do not return " | |
317 | << addr.get_ip().to_string() << " since TTL " | |
318 | << updated_ttl << "s is out of date (thresh=" | |
319 | << threshold << "s)"; | |
dbe986b9 | 320 | } |
dbe986b9 CH |
321 | result = result_up_to_date; |
322 | } | |
2a4dde8b | 323 | /*GlobalLogger.debug() << "DnsCache: request IPs for " << key |
26b0f687 CH |
324 | << " --> " << result.size() << "-list"; |
325 | BOOST_FOREACH( const HostAddress &addr, result ) | |
326 | GlobalLogger.debug() << "DnsCache: " << addr.get_ip().to_string() | |
fd62d09f | 327 | << " (TTL " << addr.get_ttl().get_updated_value() |
2a4dde8b | 328 | << "s)"; */ |
dbe986b9 | 329 | return result; |
96779587 CH |
330 | } |
331 | ||
23f51766 CH |
332 | /** |
333 | * @returns empty cname if no (up to date cname) for hostname in cache | |
334 | */ | |
335 | Cname DnsCache::get_cname(const std::string &hostname, | |
336 | const bool check_up_to_date) | |
ad83004d | 337 | { |
26b0f687 CH |
338 | std::string key = key_for_hostname(hostname); |
339 | Cname result_obj = CnameCache[key]; | |
2a4dde8b | 340 | /*GlobalLogger.debug() << "DnsCache: request CNAME for " << key |
fd62d09f | 341 | << " --> \"" << result_obj.Host << "\" (TTL " |
2a4dde8b | 342 | << result_obj.Ttl.get_updated_value() << "s)";*/ |
23f51766 CH |
343 | if (result_obj.Host.empty()) |
344 | return result_obj; | |
345 | ||
346 | else if (check_up_to_date) | |
dbe986b9 | 347 | { |
26b0f687 CH |
348 | if ( result_obj.Ttl.get_updated_value() > static_cast<uint32_t>( |
349 | DnsMaster::get_instance()->get_resolved_ip_ttl_threshold()) ) | |
23f51766 | 350 | return result_obj; |
dbe986b9 CH |
351 | else |
352 | { | |
23f51766 CH |
353 | GlobalLogger.debug() << "DnsCache: CNAME is out of date"; |
354 | return Cname(); // same as if there was no cname for hostname | |
dbe986b9 CH |
355 | } |
356 | } | |
357 | else | |
23f51766 | 358 | return result_obj; |
ad83004d CH |
359 | } |
360 | ||
dbe986b9 CH |
361 | // underlying assumption in this function: for a hostname, the cache has either |
362 | // a list of IPs saved or a cname saved, but never both | |
363 | HostAddressVec DnsCache::get_ips_recursive(const std::string &hostname, | |
364 | const bool check_up_to_date) | |
e18c1337 CH |
365 | { |
366 | std::string current_host = hostname; | |
23f51766 | 367 | Cname current_cname; |
00c81aa0 | 368 | HostAddressVec result = get_ips(current_host, check_up_to_date); |
dbe986b9 | 369 | int n_recursions = 0; |
23f51766 | 370 | uint32_t min_cname_ttl = 0xffff; // largest possible unsigned 4-byte value |
cd71d095 CH |
371 | int max_recursion_count = DnsMaster::get_instance() |
372 | ->get_max_recursion_count(); | |
e18c1337 CH |
373 | while ( result.empty() ) |
374 | { | |
23f51766 | 375 | current_cname = get_cname(current_host, check_up_to_date); |
8d26221d | 376 | if (current_cname.Host.empty()) |
e18c1337 | 377 | break; |
8d26221d CH |
378 | |
379 | current_host = key_for_hostname(current_cname.Host); | |
cd71d095 | 380 | if (++n_recursions >= max_recursion_count) |
dbe986b9 CH |
381 | { |
382 | GlobalLogger.warning() << "DnsCache: reached recursion limit of " | |
383 | << n_recursions << " in recursive IP retrieval!"; | |
384 | break; | |
385 | } | |
e18c1337 | 386 | else |
23f51766 | 387 | { |
fd62d09f CH |
388 | min_cname_ttl = min(min_cname_ttl, |
389 | current_cname.Ttl.get_updated_value()); | |
dbe986b9 | 390 | result = get_ips(current_host, check_up_to_date); |
23f51766 CH |
391 | } |
392 | } | |
393 | ||
394 | GlobalLogger.debug() << "DnsCache: recursive IP retrieval resulted in " | |
395 | << result.size() << "-list after " << n_recursions | |
396 | << " recursions"; | |
397 | ||
398 | // adjust ttl to min of ttl and min_cname_ttl | |
399 | if (n_recursions > 0) | |
400 | { | |
401 | TimeToLive cname_ttl(min_cname_ttl); | |
402 | ||
403 | BOOST_FOREACH( HostAddress &addr, result ) | |
404 | { | |
fd62d09f | 405 | if (addr.get_ttl().get_updated_value() > min_cname_ttl) |
8d26221d | 406 | { |
2a4dde8b | 407 | //GlobalLogger.debug() << "DnsCache: using shorter CNAME TTL"; |
23f51766 | 408 | addr.set_ttl(cname_ttl); |
8d26221d | 409 | } |
23f51766 | 410 | } |
e18c1337 | 411 | } |
23f51766 | 412 | |
e18c1337 CH |
413 | return result; |
414 | } | |
ad83004d | 415 | |
23f51766 | 416 | /** |
9d1b2726 CH |
417 | * from a list of CNAMEs find the first one that is out of date or empty |
418 | * | |
419 | * returns the hostname that is out of date or empty if all CNAMEs are | |
420 | * up-to-date | |
23f51766 CH |
421 | * |
422 | * required in ResolverBase::get_skipper | |
23f51766 CH |
423 | */ |
424 | std::string DnsCache::get_first_outdated_cname(const std::string &hostname, | |
425 | const uint32_t ttl_thresh) | |
426 | { | |
8d26221d | 427 | std::string first_outdated = hostname; |
23f51766 CH |
428 | Cname cname; |
429 | int n_recursions = 0; | |
cd71d095 CH |
430 | int max_recursion_count = DnsMaster::get_instance() |
431 | ->get_max_recursion_count(); | |
23f51766 CH |
432 | while (true) |
433 | { | |
cd71d095 | 434 | if (++n_recursions >= max_recursion_count) |
23f51766 CH |
435 | { |
436 | GlobalLogger.warning() << "DnsCache: reached recursion limit of " | |
437 | << n_recursions << " in search of outdated CNAMEs!"; | |
9d1b2726 CH |
438 | return first_outdated; // not really out of date but currently |
439 | } // our best guess | |
23f51766 | 440 | |
8d26221d CH |
441 | cname = get_cname(first_outdated); |
442 | if (cname.Host.empty()) | |
9d1b2726 CH |
443 | // reached end of cname list --> everything was up-to-date |
444 | return ""; | |
23f51766 CH |
445 | else if (cname.Ttl.get_updated_value() > ttl_thresh) |
446 | // cname is up to date --> continue looking | |
8d26221d | 447 | first_outdated = cname.Host; |
23f51766 | 448 | else |
9d1b2726 CH |
449 | // cname is out of date --> return its target |
450 | return cname.Host; | |
23f51766 | 451 | } |
9d1b2726 CH |
452 | // reach this point only if cname chain does not end with an IP |
453 | // --> all are up-to-date | |
454 | return ""; | |
23f51766 | 455 | } |