merged PingRotate into PingScheduler; fixed save/load of cache to/from file; started...
[pingcheck] / src / dns / dnsresolver.cpp
CommitLineData
36ad976b
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
4e7b6ff9
CH
21
22 with code copied from boost::net::dns::resolve.hpp
23 by Andreas Haberstroh (andreas at ibusy dot com)
24 from https://github.com/softwareace/Boost.DNS
36ad976b
CH
25 */
26
c5b4902d
CH
27#include "dns/dnsresolver.h"
28
29#include <stdint.h>
e91538f0 30#include <sstream>
36ad976b 31
4e7b6ff9
CH
32#include <boost/foreach.hpp>
33#include <boost/bind.hpp>
34#include <boost/function.hpp>
35#include <boost/net/dns.hpp>
36#include <boost/date_time/posix_time/posix_time.hpp>
ad83004d
CH
37#include <boost/uuid/uuid.hpp>
38#include <boost/uuid/uuid_io.hpp>
4e7b6ff9 39
36ad976b 40#include <logfunc.hpp>
4e7b6ff9 41
36ad976b 42using I2n::Logger::GlobalLogger;
4e7b6ff9
CH
43using boost::posix_time::seconds;
44using boost::posix_time::minutes;
36ad976b
CH
45
46namespace Config
47{
dbe986b9 48 const int ResolveTimeoutSeconds = 3;
96779587
CH
49 const int PauseBeforeRetrySeconds = 10;
50 const int StaleDataLongtermMinutes = 15;
36ad976b 51 const int DNS_PORT = 53;
36ad976b
CH
52}
53
4e7b6ff9
CH
54DnsResolver::DnsResolver(IoServiceItem &io_serv,
55 const std::string &hostname,
923626c0 56 const DnsIpProtocol &protocol,
96779587 57 const DnsCacheItem cache,
96779587 58 const boost::asio::ip::address &name_server)
4e7b6ff9 59 : ResolverBase( io_serv, hostname, cache )
e91538f0
CH
60 , Socket( *io_serv, ip::udp::endpoint(ip::udp::v4(), 0))
61 // just connect to anything, will specify sender/receiver later
4e7b6ff9 62 , ReceiveBuffer()
ad83004d 63 , RequestBuffer()
923626c0 64 , Protocol( protocol )
4e7b6ff9 65 , NameServer( name_server, Config::DNS_PORT )
36ad976b
CH
66 , ResolveTimeoutTimer( *io_serv )
67 , PauseBeforeRetryTimer( *io_serv )
68 , StaleDataLongtermTimer( *io_serv )
4e7b6ff9 69 , NextIpIndex( 0 )
36ad976b
CH
70 , RetryCount( 0 )
71 , IsResolving( false )
e91538f0 72 , LogPrefix( "DnsResolver" )
ad83004d
CH
73 , RandomIdGenerator()
74 , RequestId( 0 )
e18c1337 75 , OperationCancelled( false )
e91538f0
CH
76{
77 std::stringstream temp;
78 temp << "Dns(" << ResolverBase::Hostname << "): ";
79 LogPrefix = temp.str();
ad83004d 80
e91538f0 81}
96779587 82
c5b4902d
CH
83DnsResolver::~DnsResolver()
84{
85 boost::system::error_code error;
e18c1337
CH
86 //Socket.shutdown(boost::asio::ip::udp::socket::shutdown_both, error);
87 //if ( error )
88 // GlobalLogger.warning() << LogPrefix << "Received error " << error
89 // << " when shutting down socket for DNS";
90 // in IcmpPinger always gave an error system:9 (EBADF: Bad file descriptor)
91 // Here gives error system:107 ENOTCONN: Transport endpoint is not connected
c5b4902d
CH
92
93 Socket.close(error);
94 if ( error )
e91538f0 95 GlobalLogger.warning() << LogPrefix << "Received error " << error
c5b4902d
CH
96 << " when closing socket for DNS";
97}
98
99
96779587
CH
100
101//==============================================================================
102// ASYNC RESOLVE
103//==============================================================================
36ad976b
CH
104
105/**
c5b4902d 106 * copied here code from boost::net::dns::resolve.hpp, since want async
36ad976b 107 * operation and that is used only internally, there
36ad976b 108 */
4e7b6ff9 109void DnsResolver::do_resolve()
36ad976b 110{
36ad976b 111 // check if resolving already
4e7b6ff9 112 if (IsResolving)
36ad976b 113 {
e91538f0 114 GlobalLogger.info() << LogPrefix
4e7b6ff9 115 << "Call to do_resolve ignored since resolving already";
36ad976b
CH
116 return;
117 }
ad83004d 118 IsResolving = true;
e18c1337 119 OperationCancelled = false;
36ad976b 120
e18c1337
CH
121 GlobalLogger.info() << LogPrefix << "start resolving for IPs of type "
122 << to_string(Protocol) << " using name server " << NameServer;
e91538f0 123
36ad976b
CH
124 // just to be sure: cancel timers
125 ResolveTimeoutTimer.cancel();
126 PauseBeforeRetryTimer.cancel();
127 StaleDataLongtermTimer.cancel();
128
129 // create DNS request
e18c1337 130 boost::net::dns::message dns_message( ResolverBase::Hostname, Protocol );
23f51766 131 dns_message.recursive(true);
4e7b6ff9
CH
132 dns_message.action(boost::net::dns::message::query);
133 dns_message.opcode(boost::net::dns::message::squery);
ad83004d
CH
134
135 // create random ID for message
136 boost::uuids::uuid message_id = RandomIdGenerator();
137 memcpy( &RequestId, message_id.data, sizeof(RequestId) );
138 dns_message.id( RequestId );
139 GlobalLogger.debug() << LogPrefix << "Request has ID "
140 << std::showbase << std::hex << dns_message.id();
36ad976b
CH
141
142 // setup receipt of reply
143 Socket.async_receive_from(
144 boost::asio::buffer(ReceiveBuffer.get_array()),
145 NameServer,
146 boost::bind( &DnsResolver::handle_dns_result, this,
147 boost::asio::placeholders::error,
148 boost::asio::placeholders::bytes_transferred)
4e7b6ff9 149 );
36ad976b
CH
150
151 // schedule timeout
4e7b6ff9
CH
152 (void) ResolveTimeoutTimer.expires_from_now(
153 seconds(Config::ResolveTimeoutSeconds));
154 ResolveTimeoutTimer.async_wait( boost::bind(
155 &DnsResolver::handle_resolve_timeout,
156 this, boost::asio::placeholders::error) );
36ad976b
CH
157
158 // send dns request
ad83004d 159 dns_message.encode(RequestBuffer);
e91538f0
CH
160 size_t bytes_sent;
161 try
162 {
163 bytes_sent = Socket.send_to(
ad83004d 164 boost::asio::buffer(RequestBuffer.get_array()),
e91538f0
CH
165 NameServer );
166 }
167 catch (boost::system::system_error &err)
168 {
169 GlobalLogger.warning() << LogPrefix
170 << "Sending of DNS request message failed: "
171 << err.what();
e18c1337 172 schedule_retry();
e91538f0
CH
173 return;
174 }
175
176 if ( bytes_sent == 0 )
177 {
178 GlobalLogger.warning() << LogPrefix << "Empty DNS request sent!";
e18c1337 179 schedule_retry();
e91538f0
CH
180 return;
181 }
36ad976b
CH
182}
183
184
4e7b6ff9 185void DnsResolver::handle_dns_result(const boost::system::error_code &error,
ad83004d 186 const std::size_t bytes_transferred)
36ad976b 187{
e18c1337 188 if (error)
36ad976b 189 {
e91538f0 190 GlobalLogger.info() << LogPrefix << "DNS resolve resulted in error "
e18c1337
CH
191 << error << " --> try again after a little while";
192 schedule_retry();
193 return;
194 }
195 else if ( OperationCancelled )
196 { // async_resolve was cancelled --> callbacks already called
197 GlobalLogger.info() << LogPrefix
198 << "Ignoring DNS results since we were cancelled";
36ad976b
CH
199 return;
200 }
201
ad83004d
CH
202 GlobalLogger.debug() << LogPrefix << "Handling DNS result ("
203 << bytes_transferred << " bytes transferred)";
204
36ad976b
CH
205 // next 3(+1) lines copied from boost/net/dns/resolver.hpp:
206 // clamp the recvBuffer with the number of bytes transferred or decode buffr
207 ReceiveBuffer.length(bytes_transferred);
4e7b6ff9 208 boost::net::dns::message result_message;
36ad976b
CH
209 result_message.decode( ReceiveBuffer );
210
ad83004d
CH
211 // check ID
212 if (RequestId != result_message.id())
e18c1337 213 GlobalLogger.warning() << LogPrefix << "Received answer for request ID "
ad83004d
CH
214 << std::showbase << std::hex << result_message.id()
215 << " but expected ID " << RequestId;
216 else
217 GlobalLogger.debug() << LogPrefix << "Result has correct ID "
218 << std::showbase << std::hex << RequestId;
e18c1337 219 RequestId = 0;
ad83004d
CH
220
221 // loop over answers, remembering ips and cnames
4e7b6ff9
CH
222 // work with a regular pointer to list of answers since result_message is
223 // owner of data and that exists until end of function
224 // Items in answers list are shared_ptr to resource_base_t
e18c1337 225 std::vector<host_addr_pair> result_ips;
dbe986b9
CH
226 std::vector<src_cname_pair> result_cnames;
227 std::vector<string_pair> result_name_servers;
36ad976b 228
dbe986b9 229 GlobalLogger.debug() << "Checking ANSWERS section of dns reply";
e18c1337 230 gather_results(result_message.answers(), &result_ips, &result_cnames,
dbe986b9 231 &result_name_servers);
e18c1337
CH
232 // results should have the logical order
233 // Hostname [ --> cname1 --> cname2 --> ... --> cnameN ] [ --> ips ]
ad83004d 234
e18c1337 235 // remember cname list (if there were any)
dbe986b9 236 BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
e18c1337 237 ResolverBase::update_cache(host_and_cname.first, host_and_cname.second);
ad83004d 238
e18c1337
CH
239 if ( !result_ips.empty() )
240 handle_ips( result_ips );
ad83004d 241 else if ( !result_cnames.empty() )
e18c1337
CH
242 // no IPs but at least one cname --> find the "last" cname and
243 // re-start resolving with that
244 handle_cname(result_cnames);
ad83004d 245 else
23f51766
CH
246 { // no answers --> cannot proceed
247 GlobalLogger.warning() << LogPrefix << "No IP nor CNAME received! "
248 << "--> schedule retry";
249 schedule_retry();
ad83004d 250 }
ad83004d
CH
251}
252
23f51766
CH
253/**
254 * gather IPs, CNAMEs and name servers from list of resource records;
255 *
256 * can be run on anwers(), autorities() and additional() sections of dns reply
257 * messages
258 *
259 * @param rr_list: input list of resource records
260 * @param result_ips: output vector of ips
261 * @param result_cnames: output vector of cnames
262 * @param result_name_servers: output vector of name servers
263 */
264void DnsResolver::gather_results(const boost::net::dns::rr_list_t *rr_list,
e18c1337 265 std::vector<host_addr_pair> *result_ips,
dbe986b9
CH
266 std::vector<src_cname_pair> *result_cnames,
267 std::vector<string_pair> *result_name_servers)
ad83004d
CH
268 const
269{
4e7b6ff9 270 using boost::net::dns::resource_base_t;
dbe986b9 271 boost::posix_time::ptime now =boost::posix_time::second_clock::local_time();
23f51766 272 BOOST_FOREACH( boost::shared_ptr<resource_base_t> rr_item, *rr_list )
36ad976b 273 {
4e7b6ff9 274 boost::net::dns::type_t rr_type = rr_item->rtype();
ad83004d
CH
275 uint32_t ttl = rr_item->ttl();
276 std::string domain = rr_item->domain();
dbe986b9
CH
277 std::string expiry =
278 boost::posix_time::to_simple_string(now + seconds(ttl));
36ad976b
CH
279
280 if (rr_type == boost::net::dns::type_a)
281 { // 'A' resource records carry IPv4 addresses
923626c0
CH
282 if (Protocol == DNS_IPv6)
283 {
e91538f0
CH
284 GlobalLogger.info() << LogPrefix << "Ignoring IPv4 address "
285 << "because resolver was configured to only use IPv6.";
923626c0
CH
286 continue;
287 }
4e7b6ff9
CH
288 boost::asio::ip::address_v4 ip =
289 ( dynamic_cast<boost::net::dns::a_resource *> (rr_item.get()) )
290 ->address();
e18c1337 291 result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
dbe986b9
CH
292 GlobalLogger.debug() << LogPrefix << domain << ": IPv4 " << ip
293 << " with TTL " << ttl << "s (until "
294 << expiry << ")";
36ad976b
CH
295 }
296 else if (rr_type == boost::net::dns::type_a6)
297 { // 'AAAA' resource records carry IPv6 addresses
923626c0
CH
298 if (Protocol == DNS_IPv4)
299 {
e91538f0
CH
300 GlobalLogger.info() << LogPrefix << "Ignoring IPv6 address "
301 << "because resolver was configured to only use IPv4.";
923626c0
CH
302 continue;
303 }
4e7b6ff9
CH
304 boost::asio::ip::address_v6 ip =
305 ( dynamic_cast<boost::net::dns::a6_resource *> (rr_item.get()) )
306 ->address();
e18c1337 307 result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
dbe986b9
CH
308 GlobalLogger.debug() << LogPrefix << domain << ": IPv6 " << ip
309 << " with TTL " << ttl << "s (until "
310 << expiry << ")";
36ad976b
CH
311 }
312 else if (rr_type == boost::net::dns::type_cname)
313 { // 'CNAME' resource records that carry aliases
4e7b6ff9
CH
314 std::string cname =
315 (dynamic_cast<boost::net::dns::cname_resource *>(rr_item.get()))
316 ->canonicalname();
dbe986b9
CH
317 result_cnames->push_back( src_cname_pair(domain,
318 Cname(cname, ttl)) );
319 GlobalLogger.debug() << LogPrefix << domain << ": CNAME to "
320 << cname << " with TTL " << ttl << "s (until "
321 << expiry << ")";
36ad976b
CH
322 }
323 else if (rr_type == boost::net::dns::type_ns)
dbe986b9
CH
324 { // NS (name_server) resource records
325 std::string name_server =
ad83004d
CH
326 (dynamic_cast<boost::net::dns::ns_resource *>(rr_item.get()))
327 ->nameserver();
dbe986b9
CH
328 result_name_servers->push_back( string_pair(domain, name_server) );
329 GlobalLogger.debug() << LogPrefix << "NameServer " << name_server
330 << " for " << domain << " with TTL " << ttl
331 << "s (until " << expiry << ")";
ad83004d 332 }
36ad976b 333 else if (rr_type == boost::net::dns::type_soa)
e91538f0 334 GlobalLogger.debug() << LogPrefix << "SOA resource";
36ad976b 335 else if (rr_type == boost::net::dns::type_ptr)
e91538f0 336 GlobalLogger.debug() << LogPrefix << "ptr resource";
36ad976b 337 else if (rr_type == boost::net::dns::type_hinfo)
e91538f0 338 GlobalLogger.debug() << LogPrefix << "hinfo resource";
36ad976b 339 else if (rr_type == boost::net::dns::type_mx)
e91538f0 340 GlobalLogger.debug() << LogPrefix << "mx resource";
36ad976b 341 else if (rr_type == boost::net::dns::type_txt)
e91538f0 342 GlobalLogger.debug() << LogPrefix << "txt resource";
36ad976b 343 else if (rr_type == boost::net::dns::type_srv)
e91538f0 344 GlobalLogger.debug() << LogPrefix << "srv resource";
36ad976b 345 else if (rr_type == boost::net::dns::type_axfr)
e91538f0 346 GlobalLogger.debug() << LogPrefix << "axfr resource";
36ad976b 347 else
ad83004d
CH
348 GlobalLogger.debug() << LogPrefix << "unknown resource type: "
349 << std::showbase << std::hex
350 << static_cast<unsigned>(rr_item->rtype());
36ad976b 351 }
36ad976b
CH
352}
353
354
36ad976b
CH
355void DnsResolver::handle_unavailable()
356{
357 // schedule new attempt in quite a while
4e7b6ff9
CH
358 StaleDataLongtermTimer.expires_from_now(
359 minutes(Config::StaleDataLongtermMinutes));
36ad976b 360 StaleDataLongtermTimer.async_wait(
4e7b6ff9
CH
361 boost::bind( &DnsResolver::wait_timer_timeout_handler,
362 this, boost::asio::placeholders::error
36ad976b
CH
363 )
364 );
365
366 // for now, admit failure
367 bool was_success = false;
96779587 368 finalize_resolve(was_success);
36ad976b
CH
369}
370
e18c1337
CH
371
372void DnsResolver::handle_ips(const std::vector<host_addr_pair> &result_ips)
36ad976b 373{
e18c1337
CH
374 // received at least one IP which could be for the queried host name
375 // or the cname at the "end" of the cname list;
376 // but all IPs should be for the same
377 HostAddressVec addr_list;
378 std::string only_host_for_ips = result_ips[0].first;
379 BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips)
380 {
381 if ( host_and_addr.first != only_host_for_ips )
382 GlobalLogger.warning() << LogPrefix
383 << "Received IPs for different hosts " << only_host_for_ips
384 << " and " << host_and_addr.first << " in one DNS result! "
385 << "--> ignore second";
386 else
387 {
388 GlobalLogger.notice() << LogPrefix << "Found IP "
389 << host_and_addr.second.get_ip() << " with TTL "
390 << host_and_addr.second.get_ttl().get_value() << "s";
391 addr_list.push_back(host_and_addr.second);
392 }
393 }
394 ResolverBase::update_cache( only_host_for_ips, addr_list );
36ad976b 395
e18c1337
CH
396 // clean up
397 bool was_success = true;
398 finalize_resolve(was_success);
399}
400
401
dbe986b9 402void DnsResolver::handle_cname(const std::vector<src_cname_pair> &result_cnames)
e18c1337
CH
403{
404 // find the "last" cname in the list
405 // Hostname --> cname1 --> cname2 --> ... --> cnameN
406 // We assume here that this list might not be in order but that all cnames
dbe986b9
CH
407 // form a single list (form one connected list and not several isolated)
408
e18c1337
CH
409 std::string last_cname = "";
410 bool could_be_last;
dbe986b9 411 BOOST_REVERSE_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
e18c1337
CH
412 {
413 could_be_last = true;
dbe986b9 414 BOOST_REVERSE_FOREACH( const src_cname_pair &other, result_cnames )
e18c1337 415 {
23f51766 416 if (other.first == host_and_cname.second.Host)
e18c1337
CH
417 { // found cname for current cname
418 could_be_last = false;
419 break;
420 }
421 }
422 if (could_be_last)
423 {
23f51766 424 last_cname = host_and_cname.second.Host;
e18c1337
CH
425 break;
426 }
427 }
428
429 if (last_cname.empty())
430 {
431 GlobalLogger.error() << LogPrefix
432 << "Could not identify \"last\" CNAME to handle -- "
433 << "maybe we encountered a CNAME loop? Anyway, cannot proceed!";
434 GlobalLogger.info() << LogPrefix << "Result CNAMEs were:";
dbe986b9 435 BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
e18c1337 436 GlobalLogger.info() << LogPrefix << host_and_cname.first << " --> "
23f51766 437 << host_and_cname.second.Host;
e18c1337
CH
438 handle_unavailable();
439 }
440 else
441 { // check cache for IP for this cname
442 bool check_up_to_date = true;
443 HostAddressVec cached_data = Cache->get_ips_recursive(last_cname,
444 check_up_to_date);
445 if ( !cached_data.empty() )
446 {
447 bool was_success = true;
23f51766
CH
448 int cname_count = 1; // define cache access as only 1
449 finalize_resolve(was_success, cname_count);
e18c1337
CH
450 }
451 else
452 { // get resolver for canonical name
e18c1337
CH
453 ResolverItem resolver = DnsMaster::get_instance()
454 ->get_resolver_for(last_cname, Protocol);
455 callback_type callback = boost::bind(
456 &DnsResolver::cname_resolve_callback,
457 this, _1, _2 );
458 resolver->async_resolve( callback );
459
460 // treat a CNAME as a partial result: not enough to run callbacks
461 // from finalize_resolve, but enough to stop timers and reset
462 // RetryCount --> name resolution can take longer
463 stop_trying();
464 }
465 }
36ad976b
CH
466}
467
468
e18c1337 469void DnsResolver::cname_resolve_callback(const bool was_success,
23f51766 470 const int cname_count)
36ad976b 471{
e18c1337
CH
472 if ( OperationCancelled )
473 { // async_resolve was cancelled --> callbacks already called
474 GlobalLogger.info() << LogPrefix
475 << "Ignoring CNAME results since we were cancelled";
476 return;
477 }
478 else if (was_success)
479 GlobalLogger.debug() << LogPrefix << "CNAME resolution succeeded";
96779587 480 else
e18c1337
CH
481 GlobalLogger.info() << LogPrefix << "CNAME resolution failed";
482 // no use to schedule retry in this case since cname resolver must have
23f51766
CH
483 // failed several times and we can only re-start the same procedure with
484 // the same information
96779587 485
e18c1337 486 // cname counts like one more recursion step ...
23f51766 487 finalize_resolve(was_success, cname_count+1);
dbe986b9
CH
488}
489
490
96779587 491void DnsResolver::finalize_resolve(const bool was_success,
23f51766 492 const int cname_count)
96779587 493{
e18c1337
CH
494 // some consistency checks; failure might indicate a situation I had not
495 // anticipated during programming but might not be harmfull yet
496 if ( !IsResolving )
497 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
498 << "not resolving any more!";
499 if ( OperationCancelled )
500 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
501 << " was cancelled!";
502 if ( ResolverBase::CallbackList.empty() )
503 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
504 << "no callbacks!";
505 if ( RequestId != 0 )
506 GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
507 << "waiting for DNS reply!";
508
96779587 509 // stop timers
e18c1337 510 stop_trying();
96779587
CH
511
512 // schedule callbacks, clearing callback list
23f51766 513 ResolverBase::schedule_callbacks(was_success, cname_count);
96779587
CH
514
515 // finalize
e18c1337 516 GlobalLogger.notice() << LogPrefix << "finalized resolve"
96779587 517 << " with success = " << was_success
23f51766 518 << " and cname_count = " << cname_count;
96779587
CH
519 IsResolving = false;
520}
521
e18c1337 522
96779587
CH
523void DnsResolver::stop_trying()
524{
525 // cancel timers
e91538f0 526 GlobalLogger.debug() << LogPrefix << "Cancelling timers";
96779587
CH
527 ResolveTimeoutTimer.cancel();
528 PauseBeforeRetryTimer.cancel();
529 StaleDataLongtermTimer.cancel();
530
531 // clean up
532 RetryCount = 0;
533}
534
e18c1337
CH
535
536bool DnsResolver::is_resolving()
537{
538 return IsResolving;
539}
540
541
dbe986b9
CH
542/**
543 * cancel a earlier call to async_resolve
544 *
545 * callbacks will be called with was_success=false; all internal operations
546 * will be cancelled and internal callbacks (timers, dns results) have no
547 * effect any more
548 */
e18c1337
CH
549void DnsResolver::cancel_resolve()
550{
551 if ( !IsResolving )
552 {
553 GlobalLogger.info() << LogPrefix
554 << "Cancel called on non-resolving resolver -- ignore";
555 return;
556 }
557 else if (OperationCancelled)
558 {
559 GlobalLogger.info() << LogPrefix
560 << "Cancel called on cancelled resolver -- ignore";
561 return;
562 }
563
e18c1337 564 // set before finalize_resolve so can check in finalize_resolve that ID is
dbe986b9 565 // always 0; ID is not used any more since handle_dns_result stops if
e18c1337
CH
566 // OperationCancelled is true
567 RequestId = 0;
568
569 bool was_success = false;
23f51766
CH
570 int cname_count = 1;
571 finalize_resolve(was_success, cname_count);
e18c1337
CH
572
573 // set after finalize_resolve, so can check in finalize_resolve that
dbe986b9 574 // OperationCancelled is never true
e18c1337
CH
575 OperationCancelled = true;
576
577}
578
579
4e7b6ff9 580void DnsResolver::handle_resolve_timeout(const boost::system::error_code &error)
96779587
CH
581{
582 if ( error == boost::asio::error::operation_aborted ) // cancelled
583 {
e91538f0
CH
584 GlobalLogger.warning() << LogPrefix
585 << "Resolve timeout timer was cancelled!";
96779587
CH
586 return;
587 }
588 else if (error)
589 {
e91538f0
CH
590 GlobalLogger.warning() << LogPrefix
591 << "resolve timeout handler received error "
dbe986b9
CH
592 << error << " --> retry";
593 schedule_retry();
96779587 594 }
e18c1337
CH
595 else if ( OperationCancelled )
596 { // async_resolve was cancelled --> callbacks already called
597 GlobalLogger.info() << LogPrefix
dbe986b9 598 << "Ignoring DNS timeout since we were cancelled";
e18c1337
CH
599 return;
600 }
601 else
602 {
603 GlobalLogger.notice() << LogPrefix << "DNS resolving timed out";
e18c1337
CH
604 schedule_retry();
605 }
606}
607
96779587 608
e18c1337
CH
609void DnsResolver::schedule_retry()
610{
23f51766 611 // cancel timers
e18c1337
CH
612 ResolveTimeoutTimer.cancel();
613 PauseBeforeRetryTimer.cancel();
36ad976b 614
96779587
CH
615 // increment timer
616 ++RetryCount;
617
923626c0
CH
618 if ( RetryCount > DnsMaster::get_instance()
619 ->get_max_address_resolution_attempts() )
e18c1337
CH
620 { // too many re-tries
621 GlobalLogger.info() << LogPrefix << "Not scheduling a retry since "
622 << "RetryCount " << RetryCount << " too high";
623 handle_unavailable(); // will call stop_trying i.e. reset RetryCount
96779587
CH
624 }
625 else
626 { // schedule retry
e18c1337
CH
627 GlobalLogger.info() << LogPrefix << "Scheduling a retry (RetryCount="
628 << RetryCount << ")";
96779587 629 PauseBeforeRetryTimer.expires_from_now(
4e7b6ff9 630 seconds(Config::PauseBeforeRetrySeconds));
96779587 631 PauseBeforeRetryTimer.async_wait(
4e7b6ff9
CH
632 boost::bind( &DnsResolver::wait_timer_timeout_handler,
633 this, boost::asio::placeholders::error) );
96779587
CH
634 }
635}
636
637void DnsResolver::wait_timer_timeout_handler(
638 const boost::system::error_code &error)
639{
640 if ( error == boost::asio::error::operation_aborted ) // cancelled
e18c1337
CH
641 { // assume that our code cancelled this timer, so callbacks will be
642 // taken care of!
e91538f0 643 GlobalLogger.warning() << LogPrefix
e18c1337
CH
644 << "Resolve wait timer was cancelled! ";
645 }
96779587 646 else if (error)
e18c1337
CH
647 { // not sure what to do here, but callers waiting forever for a callback
648 // is probably the worst thing to happen, so call finalize_resolve
e91538f0 649 GlobalLogger.warning() << LogPrefix
e18c1337
CH
650 << "resolve wait handler received error "
651 << error << "! Try to finalize resolve";
652 bool was_success = false;
653 finalize_resolve(was_success);
654 }
655 else if ( OperationCancelled )
656 { // async_resolve was cancelled --> callbacks already called
657 GlobalLogger.info() << LogPrefix
658 << "Ignoring waiting timeout since we were cancelled";
659 return;
660 }
96779587
CH
661 else
662 {
e91538f0 663 GlobalLogger.info() << LogPrefix << "Done waiting --> re-try resolve";
4e7b6ff9 664 do_resolve();
96779587 665 }
36ad976b
CH
666}
667
668
96779587
CH
669//==============================================================================
670// RETRIEVAL
671//==============================================================================
672
26b0f687 673HostAddress DnsResolver::get_next_ip(bool check_up_to_date)
36ad976b
CH
674{
675 // get cached data
26b0f687
CH
676 // (do not use arg check_up_to_date here in order to give NextIpIndex
677 // a chance to stay above number of outdate IPs)
e18c1337 678 HostAddressVec cached_data = ResolverBase::get_cached_ips_recursively();
96779587
CH
679
680 // if no results cached, return default-constructed HostAddress (0.0.0.0)
26b0f687 681 HostAddress return_candidate;
96779587 682 if ( cached_data.empty() )
4e7b6ff9 683 {
26b0f687
CH
684 GlobalLogger.debug() << LogPrefix << "Get next IP: nothing cached";
685 return return_candidate;
4e7b6ff9 686 }
36ad976b 687
26b0f687
CH
688 int n_iter = 0;
689 std::size_t n_ips = cached_data.size();
690 uint32_t ttl_thresh = static_cast<uint32_t>( DnsMaster::get_instance()
691 ->get_resolved_ip_ttl_threshold() );
36ad976b 692
26b0f687
CH
693 GlobalLogger.info() << LogPrefix << "Get next IP from cached result of "
694 << n_ips << " IPs; first index to consider is " << NextIpIndex
695 << "; TTL thresh = " << ttl_thresh << " is used: " << check_up_to_date;
696
697 // loop until we have found a cached result (that is up to date)
698 // or until we have tried all cached IPs
699 while (true)
700 {
701 // check index since cache size may have changed since last call
702 if (NextIpIndex >= n_ips)
703 NextIpIndex = 0;
704 else if ( n_iter >= n_ips)
705 return HostAddress(); // have checked all candidates
706 else
707 { // there are candidates left to consider
708 return_candidate = cached_data[NextIpIndex++];
709 if (!check_up_to_date)
710 return return_candidate;
711 else if (cached_data[NextIpIndex].get_ttl().get_updated_value()
712 > ttl_thresh)
713 return cached_data[++NextIpIndex];
714 else
715 ++n_iter;
716 }
717 }
36ad976b
CH
718}
719
923626c0
CH
720bool DnsResolver::have_up_to_date_ip()
721{
26b0f687 722 return get_resolved_ip_count() > 0;
923626c0
CH
723}
724
725int DnsResolver::get_resolved_ip_count()
726{
26b0f687 727 return ResolverBase::get_cached_ips_recursively("", true).size();
923626c0
CH
728}
729
36ad976b
CH
730// (created using vim -- the world's best text editor)
731