tested new DNS with internal server, make more robust against caching; works nicely now
[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, 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     , Protocol( protocol )
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.warning() << LogPrefix << "Received error " << error
89     //                           << " when shutting down socket for DNS";
90     // in IcmpPinger always gave an error system:9 (EBADF: Bad file descriptor)
91     // Here gives error system:107 ENOTCONN: Transport endpoint is not connected
92
93     Socket.close(error);
94     if ( error )
95         GlobalLogger.warning() << 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()
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                          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 << " --> request retry";
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<src_cname_pair> result_cnames;
228     std::vector<string_pair> result_name_servers;
229
230     GlobalLogger.debug() << LogPrefix <<"Checking ANSWERS section of dns reply";
231     gather_results(result_message.answers(), &result_ips, &result_cnames,
232                                                           &result_name_servers);
233     // results should have the logical order
234     // Hostname [ --> cname1 --> cname2 --> ... --> cnameN ] [ --> ips ]
235
236     // remember cname list (if there were any)
237     BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
238         ResolverBase::update_cache(host_and_cname.first, host_and_cname.second);
239
240     if ( !result_ips.empty() )
241         handle_ips( result_ips );
242     else if ( !result_cnames.empty() )
243         // no IPs but at least one cname --> find the "last" cname and
244         // re-start resolving with that
245         handle_cname(result_cnames);
246     else
247     {   // no answers --> cannot proceed
248         GlobalLogger.warning() << LogPrefix << "No IP nor CNAME received! "
249                                << "--> request retry";
250         schedule_retry();
251     }
252 }
253
254 /**
255  * gather IPs, CNAMEs and name servers from list of resource records;
256  *
257  * can be run on anwers(), autorities() and additional() sections of dns reply
258  * messages
259  * 
260  * @param rr_list: input list of resource records
261  * @param result_ips: output vector of ips
262  * @param result_cnames: output vector of cnames
263  * @param result_name_servers: output vector of name servers
264  */
265 void DnsResolver::gather_results(const boost::net::dns::rr_list_t *rr_list,
266                                  std::vector<host_addr_pair> *result_ips,
267                                  std::vector<src_cname_pair> *result_cnames,
268                                  std::vector<string_pair> *result_name_servers)
269                                                                            const
270 {
271     using boost::net::dns::resource_base_t;
272     boost::posix_time::ptime now =boost::posix_time::second_clock::local_time();
273     BOOST_FOREACH( boost::shared_ptr<resource_base_t> rr_item, *rr_list )
274     {
275         boost::net::dns::type_t rr_type = rr_item->rtype();
276         uint32_t ttl = rr_item->ttl();
277         std::string domain = rr_item->domain();
278         std::string expiry =
279                         boost::posix_time::to_simple_string(now + seconds(ttl));
280
281         if (rr_type == boost::net::dns::type_a)
282         {    // 'A' resource records carry IPv4 addresses
283             if (Protocol == DNS_IPv6)
284             {
285                 GlobalLogger.info() << LogPrefix << "Ignoring IPv4 address "
286                         << "because resolver was configured to only use IPv6.";
287                 continue;
288             }
289             boost::asio::ip::address_v4 ip =
290                 ( dynamic_cast<boost::net::dns::a_resource *> (rr_item.get()) )
291                 ->address();
292             result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
293             GlobalLogger.debug() << LogPrefix << domain << ": IPv4 " << ip
294                                  << " with TTL " << ttl << "s (until "
295                                  << expiry << ")";
296         }
297         else if (rr_type == boost::net::dns::type_a6)
298         {   // 'AAAA' resource records carry IPv6 addresses
299             if (Protocol == DNS_IPv4)
300             {
301                 GlobalLogger.info() << LogPrefix << "Ignoring IPv6 address "
302                         << "because resolver was configured to only use IPv4.";
303                 continue;
304             }
305             boost::asio::ip::address_v6 ip =
306                 ( dynamic_cast<boost::net::dns::a6_resource *> (rr_item.get()) )
307                 ->address();
308             result_ips->push_back(host_addr_pair(domain, HostAddress(ip, ttl)));
309             GlobalLogger.debug() << LogPrefix << domain << ": IPv6 " << ip
310                                  << " with TTL " << ttl << "s (until "
311                                  << expiry << ")";
312         }
313         else if (rr_type == boost::net::dns::type_cname)
314         {   // 'CNAME' resource records that carry aliases
315             std::string cname =
316                 (dynamic_cast<boost::net::dns::cname_resource *>(rr_item.get()))
317                 ->canonicalname();
318             result_cnames->push_back( src_cname_pair(domain,
319                                                      Cname(cname, ttl)) );
320             GlobalLogger.debug() << LogPrefix << domain << ": CNAME to "
321                                  << cname << " with TTL " << ttl << "s (until "
322                                  << expiry << ")";
323         }
324         else if (rr_type == boost::net::dns::type_ns)
325         {   // NS (name_server) resource records
326             std::string name_server =
327                 (dynamic_cast<boost::net::dns::ns_resource *>(rr_item.get()))
328                 ->nameserver();
329             result_name_servers->push_back( string_pair(domain, name_server) );
330             GlobalLogger.debug() << LogPrefix << "NameServer " << name_server
331                                  << " for " << domain << " with TTL " << ttl
332                                  << "s (until " << expiry << ")";
333         }
334         else if (rr_type == boost::net::dns::type_soa)
335             GlobalLogger.debug() << LogPrefix << "SOA resource";
336         else if (rr_type == boost::net::dns::type_ptr)
337             GlobalLogger.debug() << LogPrefix << "ptr resource";
338         else if (rr_type == boost::net::dns::type_hinfo)
339             GlobalLogger.debug() << LogPrefix << "hinfo resource";
340         else if (rr_type == boost::net::dns::type_mx)
341             GlobalLogger.debug() << LogPrefix << "mx resource";
342         else if (rr_type == boost::net::dns::type_txt)
343             GlobalLogger.debug() << LogPrefix << "txt resource";
344         else if (rr_type == boost::net::dns::type_srv)
345             GlobalLogger.debug() << LogPrefix << "srv resource";
346         else if (rr_type == boost::net::dns::type_axfr)
347             GlobalLogger.debug() << LogPrefix << "axfr resource";
348         else
349             GlobalLogger.debug() << LogPrefix << "unknown resource type: "
350                                  << std::showbase << std::hex
351                                  << static_cast<unsigned>(rr_item->rtype());
352     }
353 }
354
355
356 void DnsResolver::handle_unavailable()
357 {
358     // schedule new attempt in quite a while
359     StaleDataLongtermTimer.expires_from_now(
360                                      seconds(Config::StaleDataLongtermSeconds));
361     StaleDataLongtermTimer.async_wait(
362             boost::bind( &DnsResolver::wait_timer_timeout_handler,
363                          this, boost::asio::placeholders::error
364             )
365     );
366     LongtermTimerIsActive = true;
367
368     // for now, admit failure
369     bool was_success = false;
370     finalize_resolve(was_success);
371 }
372
373
374 void DnsResolver::handle_ips(const std::vector<host_addr_pair> &result_ips)
375 {
376     // received at least one IP which could be for the queried host name 
377     // or the cname at the "end" of the cname list;
378     // but all IPs should be for the same
379     HostAddressVec addr_list;
380     std::string only_host_for_ips = result_ips[0].first;
381     BOOST_FOREACH( const host_addr_pair &host_and_addr, result_ips)
382     {
383         if ( host_and_addr.first != only_host_for_ips )
384             GlobalLogger.warning() << LogPrefix
385                 << "Received IPs for different hosts " << only_host_for_ips
386                 << " and " << host_and_addr.first << " in one DNS result! "
387                 << "--> ignore second";
388         else
389         {
390             GlobalLogger.notice() << LogPrefix << "Found IP "
391                       << host_and_addr.second.get_ip() << " with TTL "
392                       << host_and_addr.second.get_ttl().get_value() << "s";
393             addr_list.push_back(host_and_addr.second);
394         }
395     }
396     ResolverBase::update_cache( only_host_for_ips, addr_list );
397
398     // clean up
399     bool was_success = true;
400     finalize_resolve(was_success);
401 }
402
403
404 void DnsResolver::handle_cname(const std::vector<src_cname_pair> &result_cnames)
405 {
406     // find the "last" cname in the list
407     // Hostname --> cname1 --> cname2 --> ... --> cnameN
408     // We assume here that this list might not be in order but that all cnames
409     //   form a single list (form one connected list and not several isolated)
410
411     std::string last_cname = "";
412     bool could_be_last;
413     BOOST_REVERSE_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
414     {
415         could_be_last = true;
416         BOOST_REVERSE_FOREACH( const src_cname_pair &other, result_cnames )
417         {
418             if (other.first == host_and_cname.second.Host)
419             {   // found cname for current cname
420                 could_be_last = false;
421                 break;
422             }
423         }
424         if (could_be_last)
425         {
426             last_cname = host_and_cname.second.Host;
427             break;
428         }
429     }
430
431     if (last_cname.empty())
432     {
433         GlobalLogger.error() << LogPrefix
434             << "Could not identify \"last\" CNAME to handle -- "
435             << "maybe we encountered a CNAME loop? Anyway, cannot proceed!";
436         GlobalLogger.info() << LogPrefix << "Result CNAMEs were:";
437         BOOST_FOREACH( const src_cname_pair &host_and_cname, result_cnames )
438             GlobalLogger.info() << LogPrefix << host_and_cname.first << " --> "
439                                              << host_and_cname.second.Host;
440         handle_unavailable();
441     }
442     else
443     {   // check cache for IP for this cname
444         bool check_up_to_date = true;
445         HostAddressVec cached_data = Cache->get_ips_recursive(last_cname,
446                                                               check_up_to_date);
447         if ( !cached_data.empty() )
448         {
449             bool was_success = true;
450             int cname_count = 1;  // define cache access as only 1
451             finalize_resolve(was_success, cname_count);
452         }
453         else
454         {   // get resolver for canonical name
455             ResolverItem resolver = DnsMaster::get_instance()
456                                     ->get_resolver_for(last_cname, Protocol);
457             callback_type callback = boost::bind(
458                                            &DnsResolver::cname_resolve_callback,
459                                            this, _1, _2 );
460             resolver->async_resolve( callback );
461
462             // treat a CNAME as a partial result: not enough to run callbacks
463             // from finalize_resolve, but enough to stop timers and reset
464             // RetryCount --> name resolution can take longer
465             stop_trying(true);
466         }
467     }
468 }
469
470
471 void DnsResolver::cname_resolve_callback(const bool was_success,
472                                          const int cname_count)
473 {
474     if ( OperationCancelled )
475     {   // async_resolve was cancelled --> callbacks already called
476         GlobalLogger.info() << LogPrefix
477                             << "Ignoring CNAME results since we were cancelled";
478         return;
479     }
480     else if (was_success)
481     {
482         GlobalLogger.debug() << LogPrefix << "CNAME resolution succeeded";
483         finalize_resolve(was_success, cname_count+1);
484     }
485     else
486     {
487         GlobalLogger.info() << LogPrefix << "CNAME resolution failed";
488         // no use to schedule retry in this case since cname resolver must have
489         // failed several times and we can only re-start the same procedure with
490         // the same information. But can re-try later
491         handle_unavailable();
492     }
493 }
494
495
496 void DnsResolver::finalize_resolve(const bool was_success,
497                                    const int cname_count)
498 {
499     // some consistency checks; failure might indicate a situation I had not
500     // anticipated during programming but might not be harmfull yet
501     if ( !IsResolving )
502         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
503                                             << "not resolving any more!";
504     if ( OperationCancelled )
505         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
506                                             << " was cancelled!";
507     if ( ResolverBase::CallbackList.empty() )
508         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
509                                             << "no callbacks!";
510     if ( RequestId != 0 )
511         GlobalLogger.warning() << LogPrefix << "Consistency check failed: "
512                                             << "waiting for DNS reply!";
513
514     // stop timers
515     stop_trying(was_success);
516
517     // schedule callbacks, clearing callback list
518     ResolverBase::schedule_callbacks(was_success, cname_count);
519
520     // finalize
521     GlobalLogger.notice() << LogPrefix << "finalized resolve"
522                           << " with success = " << was_success
523                           << " and cname_count = " << cname_count;
524     IsResolving = false;
525 }
526
527
528 /**
529  * arg was_success determines if stop trying forever or just for the moment
530  * --> determines if we cancel StaleDataLongtermTimer or not
531  */
532 void DnsResolver::stop_trying(bool was_success)
533 {
534     // cancel timers
535     GlobalLogger.debug() << LogPrefix << "Cancelling timers";
536     ResolveTimeoutTimer.cancel();
537     PauseBeforeRetryTimer.cancel();
538
539     if (was_success)
540     {
541         StaleDataLongtermTimer.cancel();
542         LongtermTimerIsActive = false;
543     }
544
545     // clean up
546     RetryCount = 0;
547 }
548
549
550 /**
551  * return true if resolver is currently resolving
552  *
553  * Is true from call to async_resolve until callbacks
554  * --> returns true if waiting for result or (short-term) retry
555  *
556  * However, does NOT tell you if the (long-term) stale timeout is active! 
557  *   That timer has no effect on result, need to check is_waiting_to_resolve
558  *   for that
559  */
560 bool DnsResolver::is_resolving() const
561 {
562     return IsResolving;
563 }
564
565
566 /**
567  * returns true if either is_resolving or the long-term timer is active
568  * 
569  * is_resolving returns true if the short-term retry timer is active
570  */
571 bool DnsResolver::is_waiting_to_resolve() const
572 {
573     return IsResolving || LongtermTimerIsActive;
574 }
575
576
577 /**
578  * cancel a earlier call to async_resolve
579  *
580  * callbacks will be called with was_success=false; all internal operations
581  * will be cancelled and internal callbacks (timers, dns results) have no
582  * effect any more; cancels also the long-term stale-data timer
583  */
584 void DnsResolver::cancel_resolve()
585 {
586     if ( !IsResolving && !LongtermTimerIsActive)
587     {
588         GlobalLogger.info() << LogPrefix << "Cancel called on non-resolving, "
589                                          << "non-waiting resolver -- ignore";
590         return;
591     }
592     else if (OperationCancelled)
593     {
594         GlobalLogger.info() << LogPrefix
595                << "Cancel called on cancelled resolver -- ignore";
596         return;
597     }
598     GlobalLogger.info() << LogPrefix << "Cancel resolver";
599
600     // set before finalize_resolve so can check in finalize_resolve that ID is
601     //   always 0; ID is not used any more since handle_dns_result stops if
602     //   OperationCancelled is true
603     RequestId = 0;
604
605     if ( IsResolving )
606     {
607         bool was_success = false;
608         int cname_count = 1;
609         finalize_resolve(was_success, cname_count);
610     }
611
612     // also cancel the long-term timer
613     StaleDataLongtermTimer.cancel();
614     LongtermTimerIsActive = false;
615
616     // set after finalize_resolve, so can check in finalize_resolve that 
617     // OperationCancelled is never true
618     OperationCancelled = true;
619
620 }
621
622
623 void DnsResolver::handle_resolve_timeout(const boost::system::error_code &error)
624 {
625     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
626     {
627         GlobalLogger.debug() << LogPrefix
628                              << "Resolve timeout timer was cancelled!";
629         return;
630     }
631     else if (error)
632     {
633         GlobalLogger.warning() << LogPrefix
634                                << "resolve timeout handler received error "
635                                << error << " --> request retry";
636         schedule_retry();
637     }
638     else if ( OperationCancelled )
639     {   // async_resolve was cancelled --> callbacks already called
640         GlobalLogger.info() << LogPrefix
641                             << "Ignoring DNS timeout since we were cancelled";
642         return;
643     }
644     else
645     {
646         GlobalLogger.notice() << LogPrefix << "DNS resolving timed out";
647         schedule_retry();
648     }
649 }
650
651
652 void DnsResolver::schedule_retry()
653 {
654     // cancel timers
655     ResolveTimeoutTimer.cancel();
656     PauseBeforeRetryTimer.cancel();
657
658     // increment timer
659     ++RetryCount;
660
661     if ( RetryCount > DnsMaster::get_instance()
662                       ->get_max_address_resolution_attempts() )
663     {   // too many re-tries
664         GlobalLogger.info() << LogPrefix << "Not scheduling a retry since "
665                             << "RetryCount " << RetryCount << " too high";
666         handle_unavailable();   // will call stop_trying i.e. reset RetryCount
667     }
668     else
669     {   // schedule retry
670         GlobalLogger.info() << LogPrefix << "Scheduling a retry (RetryCount="
671                             << RetryCount << ")";
672         PauseBeforeRetryTimer.expires_from_now(
673                 seconds(Config::PauseBeforeRetrySeconds));
674         PauseBeforeRetryTimer.async_wait(
675                 boost::bind( &DnsResolver::wait_timer_timeout_handler,
676                              this, boost::asio::placeholders::error) );
677     }
678 }
679
680 void DnsResolver::wait_timer_timeout_handler(
681                                          const boost::system::error_code &error)
682 {
683     if ( error ==  boost::asio::error::operation_aborted )   // cancelled
684     {   // assume that our code cancelled this timer, so callbacks will be
685         // taken care of!
686         GlobalLogger.debug() << LogPrefix
687                              << "Resolve wait timer was cancelled! ";
688     }
689     else if (error)
690     {   // not sure what to do here, but callers waiting forever for a callback
691         // is probably the worst thing to happen, so call finalize_resolve
692         GlobalLogger.warning() << LogPrefix
693                                << "resolve wait handler received error "
694                                << error << "! Try to finalize resolve";
695         bool was_success = false;
696         finalize_resolve(was_success);
697     }
698     else if ( OperationCancelled )
699     {   // async_resolve was cancelled --> callbacks already called
700         GlobalLogger.info() << LogPrefix
701                           << "Ignoring waiting timeout since we were cancelled";
702         return;
703     }
704     else
705     {
706         GlobalLogger.info() << LogPrefix << "Done waiting --> re-try resolve";
707         IsResolving = false;  // will be set to true immediately in do_resolve
708         do_resolve();
709     }
710 }
711
712
713 //==============================================================================
714 // RETRIEVAL
715 //==============================================================================
716
717 HostAddress DnsResolver::get_next_ip(bool check_up_to_date)
718 {
719     // get cached data
720     // (do not use arg check_up_to_date here in order to give NextIpIndex
721     //  a chance to stay above number of outdated IPs)
722     HostAddressVec cached_data = ResolverBase::get_cached_ips_recursively();
723
724     // if no results cached, return default-constructed HostAddress (0.0.0.0)
725     HostAddress return_candidate;
726     if ( cached_data.empty() )
727     {
728         GlobalLogger.debug() << LogPrefix << "Get next IP: nothing cached";
729         return return_candidate;
730     }
731
732     std::size_t n_iter = 0;
733     std::size_t n_ips = cached_data.size();
734     uint32_t ttl_thresh = static_cast<uint32_t>( DnsMaster::get_instance()
735                                             ->get_resolved_ip_ttl_threshold() );
736
737     GlobalLogger.info() << LogPrefix << "Get next IP from cached result of "
738         << n_ips << " IPs; first index to consider is " << NextIpIndex
739         << "; TTL thresh=" << ttl_thresh << "s is used: " << check_up_to_date;
740
741     // loop until we have found a cached result (that is up to date)
742     //   or until we have tried all cached IPs
743     while (true)
744     {
745         // check index since cache size may have changed since last call
746         if (NextIpIndex >= n_ips)
747         {
748             GlobalLogger.debug() << LogPrefix << "Reset NextIpIndex";
749             NextIpIndex = 0;
750         }
751         else if ( n_iter >= n_ips)
752         {
753             GlobalLogger.debug() << LogPrefix << "No IP found";
754             return HostAddress();   // have checked all candidates
755         }
756         else
757         {   // there are candidates left to consider
758             GlobalLogger.debug() << LogPrefix << "Check IP candidate at index "
759                                  << NextIpIndex;
760             return_candidate = cached_data[NextIpIndex++];
761             if (!check_up_to_date)
762             {
763                 GlobalLogger.debug() << LogPrefix << "not checking ttl, accept";
764                 return return_candidate;
765             }
766             else if (return_candidate.get_ttl().get_updated_value()
767                      > ttl_thresh)
768             {
769                 GlobalLogger.debug() << LogPrefix << "is up to date, accept";
770                 return return_candidate;
771             }
772             else
773             {
774                 GlobalLogger.debug() << LogPrefix << "is out of date ("
775                     << return_candidate.get_ttl().get_updated_value()
776                     << "s <= " << ttl_thresh << "s), continue";
777                 ++n_iter;
778             }
779         }
780     }
781 }
782
783 bool DnsResolver::have_up_to_date_ip()
784 {
785     return get_resolved_ip_count(true) > 0;
786 }
787
788 int DnsResolver::get_resolved_ip_count(const bool check_up_to_date)
789 {
790     // run with empty hostname --> uses internal var Hostname
791     return ResolverBase::get_cached_ips_recursively("",check_up_to_date).size();
792 }
793