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