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