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