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